diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index cec01a0842..8e59d8f99b 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -110,7 +110,7 @@ def get_balance_on(account=None, date=None, party_type=None, party=None, company
if party_type and party:
cond.append("""gle.party_type = "%s" and gle.party = "%s" """ %
(frappe.db.escape(party_type), frappe.db.escape(party, percent=False)))
-
+
if company:
cond.append("""gle.company = "%s" """ % (frappe.db.escape(company, percent=False)))
@@ -178,7 +178,7 @@ def get_count_on(account, fieldname, date):
voucher_type, voucher_no, against_voucher_type, against_voucher
FROM `tabGL Entry` gle
WHERE {0}""".format(" and ".join(cond)), as_dict=True)
-
+
count = 0
for gle in entries:
if fieldname not in ('invoiced_amount','payables'):
@@ -196,12 +196,12 @@ def get_count_on(account, fieldname, date):
WHERE docstatus < 2 and posting_date <= %(date)s and against_voucher = %(voucher_no)s
and party = %(party)s and name != %(name)s""".format(select_fields),
{"date": date, "voucher_no": gle.voucher_no, "party": gle.party, "name": gle.name})[0][0]
-
+
outstanding_amount = flt(gle.get(dr_or_cr)) - flt(gle.get(cr_or_dr)) - payment_amount
currency_precision = get_currency_precision() or 2
if abs(flt(outstanding_amount)) > 0.1/10**currency_precision:
count += 1
-
+
return count
@frappe.whitelist()
@@ -209,7 +209,7 @@ def add_ac(args=None):
if not args:
args = frappe.local.form_dict
args.pop("cmd")
-
+
ac = frappe.new_doc("Account")
if args.get("ignore_permissions"):
@@ -220,7 +220,7 @@ def add_ac(args=None):
if not ac.parent_account:
ac.parent_account = args.get("parent")
-
+
ac.old_parent = ""
ac.freeze_account = "No"
if cint(ac.get("is_root")):
@@ -252,10 +252,10 @@ def reconcile_against_document(args):
Cancel JV, Update aginst document, split if required and resubmit jv
"""
for d in args:
-
- check_if_advance_entry_modified(d)
+
+ check_if_advance_entry_modified(d)
validate_allocated_amount(d)
-
+
# cancel advance entry
doc = frappe.get_doc(d.voucher_type, d.voucher_no)
@@ -289,13 +289,13 @@ def check_if_advance_entry_modified(args):
else:
party_account_field = "paid_from" if args.party_type == "Customer" else "paid_to"
if args.voucher_detail_no:
- ret = frappe.db.sql("""select t1.name
- from `tabPayment Entry` t1, `tabPayment Entry Reference` t2
+ ret = frappe.db.sql("""select t1.name
+ from `tabPayment Entry` t1, `tabPayment Entry Reference` t2
where
- t1.name = t2.parent and t1.docstatus = 1
+ t1.name = t2.parent and t1.docstatus = 1
and t1.name = %(voucher_no)s and t2.name = %(voucher_detail_no)s
and t1.party_type = %(party_type)s and t1.party = %(party)s and t1.{0} = %(account)s
- and t2.reference_doctype in ("", "Sales Order", "Purchase Order")
+ and t2.reference_doctype in ("", "Sales Order", "Purchase Order")
and t2.allocated_amount = %(unadjusted_amount)s
""".format(party_account_field), args)
else:
@@ -367,7 +367,7 @@ def update_reference_in_journal_entry(d, jv_obj):
# will work as update after submit
jv_obj.flags.ignore_validate_update_after_submit = True
jv_obj.save(ignore_permissions=True)
-
+
def update_reference_in_payment_entry(d, payment_entry):
reference_details = {
"reference_doctype": d.against_voucher_type,
@@ -377,44 +377,44 @@ def update_reference_in_payment_entry(d, payment_entry):
"allocated_amount": d.allocated_amount,
"exchange_rate": d.exchange_rate
}
-
+
if d.voucher_detail_no:
existing_row = payment_entry.get("references", {"name": d["voucher_detail_no"]})[0]
original_row = existing_row.as_dict().copy()
existing_row.update(reference_details)
-
+
if d.allocated_amount < original_row.allocated_amount:
new_row = payment_entry.append("references")
new_row.docstatus = 1
for field in reference_details.keys():
new_row.set(field, original_row[field])
-
+
new_row.allocated_amount = original_row.allocated_amount - d.allocated_amount
else:
new_row = payment_entry.append("references")
new_row.docstatus = 1
new_row.update(reference_details)
-
+
payment_entry.flags.ignore_validate_update_after_submit = True
payment_entry.setup_party_account_field()
payment_entry.set_missing_values()
payment_entry.set_amounts()
payment_entry.save(ignore_permissions=True)
-
+
def unlink_ref_doc_from_payment_entries(ref_doc):
remove_ref_doc_link_from_jv(ref_doc.doctype, ref_doc.name)
remove_ref_doc_link_from_pe(ref_doc.doctype, ref_doc.name)
-
+
frappe.db.sql("""update `tabGL Entry`
set against_voucher_type=null, against_voucher=null,
modified=%s, modified_by=%s
where against_voucher_type=%s and against_voucher=%s
and voucher_no != ifnull(against_voucher, '')""",
(now(), frappe.session.user, ref_doc.doctype, ref_doc.name))
-
+
if ref_doc.doctype in ("Sales Invoice", "Purchase Invoice"):
ref_doc.set("advances", [])
-
+
frappe.db.sql("""delete from `tab{0} Advance` where parent = %s"""
.format(ref_doc.doctype), ref_doc.name)
@@ -430,7 +430,7 @@ def remove_ref_doc_link_from_jv(ref_type, ref_no):
and docstatus < 2""", (now(), frappe.session.user, ref_type, ref_no))
frappe.msgprint(_("Journal Entries {0} are un-linked".format("\n".join(linked_jv))))
-
+
def remove_ref_doc_link_from_pe(ref_type, ref_no):
linked_pe = frappe.db.sql_list("""select parent from `tabPayment Entry Reference`
where reference_doctype=%s and reference_name=%s and docstatus < 2""", (ref_type, ref_no))
@@ -440,18 +440,18 @@ def remove_ref_doc_link_from_pe(ref_type, ref_no):
set allocated_amount=0, modified=%s, modified_by=%s
where reference_doctype=%s and reference_name=%s
and docstatus < 2""", (now(), frappe.session.user, ref_type, ref_no))
-
+
for pe in linked_pe:
pe_doc = frappe.get_doc("Payment Entry", pe)
pe_doc.set_total_allocated_amount()
pe_doc.set_unallocated_amount()
pe_doc.clear_unallocated_reference_document_rows()
-
- frappe.db.sql("""update `tabPayment Entry` set total_allocated_amount=%s,
- base_total_allocated_amount=%s, unallocated_amount=%s, modified=%s, modified_by=%s
- where name=%s""", (pe_doc.total_allocated_amount, pe_doc.base_total_allocated_amount,
+
+ frappe.db.sql("""update `tabPayment Entry` set total_allocated_amount=%s,
+ base_total_allocated_amount=%s, unallocated_amount=%s, modified=%s, modified_by=%s
+ where name=%s""", (pe_doc.total_allocated_amount, pe_doc.base_total_allocated_amount,
pe_doc.unallocated_amount, now(), frappe.session.user, pe))
-
+
frappe.msgprint(_("Payment Entries {0} are un-linked".format("\n".join(linked_pe))))
@frappe.whitelist()
@@ -562,7 +562,7 @@ def get_outstanding_invoices(party_type, party, account, condition=None):
from
`tabGL Entry` invoice_gl_entry
where
- party_type = %(party_type)s and party = %(party)s
+ party_type = %(party_type)s and party = %(party)s
and account = %(account)s and {dr_or_cr} > 0
{condition}
and ((voucher_type = 'Journal Entry'
@@ -590,9 +590,9 @@ def get_outstanding_invoices(party_type, party, account, condition=None):
'outstanding_amount': flt(d.invoice_amount - d.payment_amount, precision),
'due_date': frappe.db.get_value(d.voucher_type, d.voucher_no, "due_date"),
}))
-
+
outstanding_invoices = sorted(outstanding_invoices, key=lambda k: k['due_date'] or getdate(nowdate()))
-
+
return outstanding_invoices
diff --git a/erpnext/demo/setup/setup_data.py b/erpnext/demo/setup/setup_data.py
index 278c398c2a..902bbabd1e 100644
--- a/erpnext/demo/setup/setup_data.py
+++ b/erpnext/demo/setup/setup_data.py
@@ -2,7 +2,7 @@ from __future__ import unicode_literals
import random, json
import frappe, erpnext
-from frappe.utils import flt, now_datetime, cstr
+from frappe.utils import flt, now_datetime, cstr, random_string
from frappe.utils.make_random import add_random_children, get_random
from erpnext.demo.domains import data
from frappe import _
@@ -14,8 +14,15 @@ def setup(domain):
setup_holiday_list()
setup_user()
setup_employee()
- setup_salary_structure()
- setup_salary_structure_for_timesheet()
+
+ employees = frappe.get_all('Employee', fields=['name', 'date_of_joining'])
+
+ # monthly salary
+ setup_salary_structure(employees[:5], 0)
+
+ # based on timesheet
+ setup_salary_structure(employees[5:], 1)
+
setup_leave_allocation()
setup_user_roles()
setup_customer()
@@ -29,7 +36,7 @@ def setup(domain):
setup_account_to_expense_type()
setup_budget()
setup_pos_profile()
-
+
frappe.db.commit()
frappe.clear_cache()
@@ -111,30 +118,44 @@ def setup_employee():
frappe.db.set_value("HR Settings", None, "emp_created_by", "Naming Series")
frappe.db.commit()
+ for d in frappe.get_all('Salary Component'):
+ salary_component = frappe.get_doc('Salary Component', d.name)
+ salary_component.append('accounts', dict(
+ company=erpnext.get_default_company(),
+ default_account=frappe.get_value('Account', dict(account_name=('like', 'Salary%')))
+ ))
+ salary_component.save()
+
import_json('Employee')
-def setup_salary_structure():
+def setup_salary_structure(employees, salary_slip_based_on_timesheet=0):
f = frappe.get_doc('Fiscal Year', frappe.defaults.get_global_default('fiscal_year'))
ss = frappe.new_doc('Salary Structure')
- ss.name = "Sample Salary Structure - " + str(f.year_start_date)
- for e in frappe.get_all('Employee', fields=['name', 'date_of_joining']):
+ ss.name = "Sample Salary Structure - " + random_string(5)
+ for e in employees:
ss.append('employees', {
'employee': e.name,
'base': random.random() * 10000
})
- if not e.date_of_joining:
- continue
+ ss.from_date = e.date_of_joining if (e.date_of_joining
+ and e.date_of_joining > f.year_start_date) else f.year_start_date
+ ss.to_date = f.year_end_date
+ ss.salary_slip_based_on_timesheet = salary_slip_based_on_timesheet
+
+ if salary_slip_based_on_timesheet:
+ ss.salary_component = 'Basic'
+ ss.hour_rate = flt(random.random() * 10, 2)
+ else:
+ ss.payroll_frequency = 'Monthly'
+
+ ss.payment_account = frappe.get_value('Account',
+ {'account_type': 'Cash', 'company': erpnext.get_default_company(),'is_group':0}, "name")
- ss.from_date = e.date_of_joining if (e.date_of_joining
- and e.date_of_joining > f.year_start_date) else f.year_start_date
- ss.to_date = f.year_end_date
- ss.payment_account = frappe.get_value('Account', {'account_type': 'Cash', 'company': erpnext.get_default_company(),'is_group':0}, "name")
ss.append('earnings', {
'salary_component': 'Basic',
"abbr":'B',
- 'condition': 'base > 5000',
'formula': 'base*.2',
'amount_based_on_formula': 1,
"idx": 1
@@ -142,20 +163,14 @@ def setup_salary_structure():
ss.append('deductions', {
'salary_component': 'Income Tax',
"abbr":'IT',
- 'condition': 'base > 5000',
+ 'condition': 'base > 1000',
'amount': random.random() * 1000,
"idx": 1
})
ss.insert()
-def setup_salary_structure_for_timesheet():
- for e in frappe.get_all('Salary Structure', fields=['name'], filters={'is_active': 'Yes'}, limit=2):
- ss_doc = frappe.get_doc("Salary Structure", e.name)
- ss_doc.salary_slip_based_on_timesheet = 1
- ss_doc.salary_component = 'Basic'
- ss_doc.hour_rate = flt(random.random() * 10, 2)
- ss_doc.save(ignore_permissions=True)
+ return ss
def setup_user_roles():
user = frappe.get_doc('User', 'demo@erpnext.com')
@@ -208,11 +223,11 @@ def setup_user_roles():
user = frappe.get_doc('User', 'aromn@example.com')
user.add_roles('Academics User')
frappe.db.set_global('demo_schools_user', user.name)
-
+
#Add Expense Approver
user = frappe.get_doc('User', 'WanMai@example.com')
user.add_roles('Expense Approver')
-
+
def setup_leave_allocation():
year = now_datetime().year
for employee in frappe.get_all('Employee', fields=['name']):
@@ -345,7 +360,7 @@ def setup_pos_profile():
})
pos.insert()
-
+
def import_json(doctype, submit=False, values=None):
frappe.flags.in_import = True
data = json.loads(open(frappe.get_app_path('erpnext', 'demo', 'data',
diff --git a/erpnext/demo/user/accounts.py b/erpnext/demo/user/accounts.py
index 392d13db92..1a41482f42 100644
--- a/erpnext/demo/user/accounts.py
+++ b/erpnext/demo/user/accounts.py
@@ -23,25 +23,32 @@ def work():
report = "Ordered Items to be Billed"
for so in list(set([r[0] for r in query_report.run(report)["result"]
if r[0]!="Total"]))[:random.randint(1, 5)]:
- si = frappe.get_doc(make_sales_invoice(so))
- si.posting_date = frappe.flags.current_date
- for d in si.get("items"):
- if not d.income_account:
- d.income_account = "Sales - {}".format(frappe.db.get_value('Company', si.company, 'abbr'))
- si.insert()
- si.submit()
- frappe.db.commit()
+ try:
+ si = frappe.get_doc(make_sales_invoice(so))
+ si.posting_date = frappe.flags.current_date
+ for d in si.get("items"):
+ if not d.income_account:
+ d.income_account = "Sales - {}".format(frappe.db.get_value('Company', si.company, 'abbr'))
+ si.insert()
+ si.submit()
+ frappe.db.commit()
+ except frappe.ValidationError:
+ pass
if random.random() <= 0.6:
report = "Received Items to be Billed"
for pr in list(set([r[0] for r in query_report.run(report)["result"]
if r[0]!="Total"]))[:random.randint(1, 5)]:
- pi = frappe.get_doc(make_purchase_invoice(pr))
- pi.posting_date = frappe.flags.current_date
- pi.bill_no = random_string(6)
- pi.insert()
- pi.submit()
- frappe.db.commit()
+ try:
+ pi = frappe.get_doc(make_purchase_invoice(pr))
+ pi.posting_date = frappe.flags.current_date
+ pi.bill_no = random_string(6)
+ pi.insert()
+ pi.submit()
+ frappe.db.commit()
+ except frappe.ValidationError:
+ pass
+
if random.random() < 0.5:
make_payment_entries("Sales Invoice", "Accounts Receivable")
@@ -67,7 +74,7 @@ def work():
def make_payment_entries(ref_doctype, report):
outstanding_invoices = list(set([r[3] for r in query_report.run(report,
{"report_date": frappe.flags.current_date })["result"] if r[2]==ref_doctype]))
-
+
# make Payment Entry
for inv in outstanding_invoices[:random.randint(1, 2)]:
pe = get_payment_entry(ref_doctype, inv)
diff --git a/erpnext/demo/user/hr.py b/erpnext/demo/user/hr.py
index 2686987473..cfe31197bc 100644
--- a/erpnext/demo/user/hr.py
+++ b/erpnext/demo/user/hr.py
@@ -1,40 +1,50 @@
from __future__ import unicode_literals
import frappe, erpnext
import random
-from frappe.utils import random_string, add_days, cint
+import datetime
+from frappe.utils import random_string, add_days, get_last_day, getdate
from erpnext.projects.doctype.timesheet.test_timesheet import make_timesheet
from erpnext.projects.doctype.timesheet.timesheet import make_salary_slip, make_sales_invoice
from frappe.utils.make_random import get_random
from erpnext.hr.doctype.expense_claim.expense_claim import get_expense_approver, make_bank_entry
-from erpnext.hr.doctype.leave_application.leave_application import get_leave_balance_on, OverlapError
+from erpnext.hr.doctype.leave_application.leave_application import (get_leave_balance_on,
+ OverlapError, AttendanceAlreadyMarkedError)
def work():
frappe.set_user(frappe.db.get_global('demo_hr_user'))
year, month = frappe.flags.current_date.strftime("%Y-%m").split("-")
- prev_month = str(cint(month)- 1).zfill(2)
- if month=="01":
- prev_month = "12"
-
mark_attendance()
make_leave_application()
# process payroll
- if not frappe.db.get_value("Salary Slip", {"month": prev_month, "fiscal_year": year}):
+ if not frappe.db.sql('select name from `tabSalary Slip` where month(adddate(start_date, interval 1 month))=month(curdate())'):
+ # process payroll for previous month
process_payroll = frappe.get_doc("Process Payroll", "Process Payroll")
process_payroll.company = frappe.flags.company
- process_payroll.month = prev_month
- process_payroll.fiscal_year = year
- process_payroll.from_date = frappe.flags.current_date
- process_payroll.to_date = add_days(frappe.flags.current_date, random.randint(0, 30))
- process_payroll.reference_number = "DemoRef23"
- process_payroll.reference_date = frappe.flags.current_date
+ process_payroll.payroll_frequency = 'Monthly'
+
+ # select a posting date from the previous month
+ process_payroll.posting_date = get_last_day(getdate(frappe.flags.current_date) - datetime.timedelta(days=10))
process_payroll.payment_account = frappe.get_value('Account', {'account_type': 'Cash', 'company': erpnext.get_default_company(),'is_group':0}, "name")
- process_payroll.submit_salary_slip()
- process_payroll.make_journal_entry()
-
+
+ process_payroll.set_start_end_dates()
+
+ # based on frequency
+ process_payroll.salary_slip_based_on_timesheet = 0
+ process_payroll.create_salary_slips()
+ process_payroll.submit_salary_slips()
+ process_payroll.make_journal_entry(reference_date=frappe.flags.current_date,
+ reference_number=random_string(10))
+
+ process_payroll.salary_slip_based_on_timesheet = 1
+ process_payroll.create_salary_slips()
+ process_payroll.submit_salary_slips()
+ process_payroll.make_journal_entry(reference_date=frappe.flags.current_date,
+ reference_number=random_string(10))
+
if frappe.db.get_global('demo_hr_user'):
make_timesheet_records()
-
+
#expense claim
expense_claim = frappe.new_doc("Expense Claim")
expense_claim.extend('expenses', get_expenses())
@@ -100,7 +110,10 @@ def get_timesheet_based_salary_slip_employee():
select employee from `tabSalary Structure Employee`
where parent IN %(sal_struct)s""", {"sal_struct": sal_struct}, as_dict=True)
return employees
-
+
+ else:
+ return []
+
def make_timesheet_records():
employees = get_timesheet_based_salary_slip_employee()
for e in employees:
@@ -134,10 +147,10 @@ def make_sales_invoice_for_timesheet(name):
sales_invoice.insert()
sales_invoice.submit()
frappe.db.commit()
-
+
def make_leave_application():
allocated_leaves = frappe.get_all("Leave Allocation", fields=['employee', 'leave_type'])
-
+
for allocated_leave in allocated_leaves:
leave_balance = get_leave_balance_on(allocated_leave.employee, allocated_leave.leave_type, frappe.flags.current_date,
consider_all_leaves_in_the_allocation_period=True)
@@ -146,7 +159,7 @@ def make_leave_application():
to_date = frappe.flags.current_date
else:
to_date = add_days(frappe.flags.current_date, random.randint(0, leave_balance-1))
-
+
leave_application = frappe.get_doc({
"doctype": "Leave Application",
"employee": allocated_leave.employee,
@@ -159,13 +172,13 @@ def make_leave_application():
leave_application.insert()
leave_application.submit()
frappe.db.commit()
- except (OverlapError):
+ except (OverlapError, AttendanceAlreadyMarkedError):
frappe.db.rollback()
-
+
def mark_attendance():
att_date = frappe.flags.current_date
for employee in frappe.get_all('Employee', fields=['name'], filters = {'status': 'Active'}):
-
+
if not frappe.db.get_value("Attendance", {"employee": employee.name, "att_date": att_date}):
attendance = frappe.get_doc({
"doctype": "Attendance",
@@ -175,11 +188,11 @@ def mark_attendance():
leave = frappe.db.sql("""select name from `tabLeave Application`
where employee = %s and %s between from_date and to_date and status = 'Approved'
and docstatus = 1""", (employee.name, att_date))
-
+
if leave:
attendance.status = "Absent"
else:
attendance.status = "Present"
attendance.save()
- attendance.submit()
+ attendance.submit()
frappe.db.commit()
diff --git a/erpnext/demo/user/sales.py b/erpnext/demo/user/sales.py
index 10df14334f..ddd36efc36 100644
--- a/erpnext/demo/user/sales.py
+++ b/erpnext/demo/user/sales.py
@@ -55,12 +55,13 @@ def make_opportunity():
"enquiry_from": "Customer",
"customer": get_random("Customer"),
"enquiry_type": "Sales",
+ "with_items": 1,
"transaction_date": frappe.flags.current_date,
})
add_random_children(b, "items", rows=4, randomize = {
"qty": (1, 5),
- "item_code": ("Item", {"has_variants": "0", "is_fixed_asset": 0})
+ "item_code": ("Item", {"has_variants": 0, "is_fixed_asset": 0})
}, unique="item_code")
b.insert()
@@ -68,7 +69,7 @@ def make_opportunity():
def make_quotation():
# get open opportunites
- opportunity = get_random("Opportunity", {"status": "Open"})
+ opportunity = get_random("Opportunity", {"status": "Open", "with_items": 1})
if opportunity:
from erpnext.crm.doctype.opportunity.opportunity import make_quotation
diff --git a/erpnext/demo/user/stock.py b/erpnext/demo/user/stock.py
index ea23853fea..0bd5ce3d96 100644
--- a/erpnext/demo/user/stock.py
+++ b/erpnext/demo/user/stock.py
@@ -85,7 +85,7 @@ def make_stock_reconciliation():
def submit_draft_stock_entries():
from erpnext.stock.doctype.stock_entry.stock_entry import IncorrectValuationRateError, \
- DuplicateEntryForProductionOrderError, OperationsNotCompleteError
+ DuplicateEntryForProductionOrderError, OperationsNotCompleteError
# try posting older drafts (if exists)
frappe.db.commit()
@@ -102,7 +102,7 @@ def submit_draft_stock_entries():
def make_sales_return_records():
for data in frappe.get_all('Delivery Note', fields=["name"], filters={"docstatus": 1}):
- if random.random() < 0.2:
+ if random.random() < 0.1:
try:
dn = make_sales_return(data.name)
dn.insert()
@@ -113,7 +113,7 @@ def make_sales_return_records():
def make_purchase_return_records():
for data in frappe.get_all('Purchase Receipt', fields=["name"], filters={"docstatus": 1}):
- if random.random() < 0.2:
+ if random.random() < 0.1:
try:
pr = make_purchase_return(data.name)
pr.insert()
diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py
index 4cc2e100d1..5c836df8c9 100755
--- a/erpnext/hr/doctype/leave_application/leave_application.py
+++ b/erpnext/hr/doctype/leave_application/leave_application.py
@@ -16,6 +16,7 @@ class LeaveDayBlockedError(frappe.ValidationError): pass
class OverlapError(frappe.ValidationError): pass
class InvalidLeaveApproverError(frappe.ValidationError): pass
class LeaveApproverIdentityError(frappe.ValidationError): pass
+class AttendanceAlreadyMarkedError(frappe.ValidationError): pass
from frappe.model.document import Document
class LeaveApplication(Document):
@@ -100,16 +101,16 @@ class LeaveApplication(Document):
def validate_salary_processed_days(self):
if not frappe.db.get_value("Leave Type", self.leave_type, "is_lwp"):
return
-
+
last_processed_pay_slip = frappe.db.sql("""
select start_date, end_date from `tabSalary Slip`
where docstatus = 1 and employee = %s
- and ((%s between start_date and end_date) or (%s between start_date and end_date))
+ and ((%s between start_date and end_date) or (%s between start_date and end_date))
order by modified desc limit 1
""",(self.employee, self.to_date, self.from_date))
if last_processed_pay_slip:
- frappe.throw(_("Salary already processed for period between {0} and {1}, Leave application period cannot be between this date range.").format(formatdate(last_processed_pay_slip[0][0]),
+ frappe.throw(_("Salary already processed for period between {0} and {1}, Leave application period cannot be between this date range.").format(formatdate(last_processed_pay_slip[0][0]),
formatdate(last_processed_pay_slip[0][1])))
@@ -213,13 +214,14 @@ class LeaveApplication(Document):
elif self.docstatus==1 and len(leave_approvers) and self.leave_approver != frappe.session.user:
frappe.throw(_("Only the selected Leave Approver can submit this Leave Application"),
LeaveApproverIdentityError)
-
+
def validate_attendance(self):
attendance = frappe.db.sql("""select name from `tabAttendance` where employee = %s and (att_date between %s and %s)
and docstatus = 1""",
(self.employee, self.from_date, self.to_date))
if attendance:
- frappe.throw(_("Attendance for employee {0} is already marked for this day").format(self.employee))
+ frappe.throw(_("Attendance for employee {0} is already marked for this day").format(self.employee),
+ AttendanceAlreadyMarkedError)
def notify_employee(self, status):
employee = frappe.get_doc("Employee", self.employee)
diff --git a/erpnext/hr/doctype/process_payroll/process_payroll.js b/erpnext/hr/doctype/process_payroll/process_payroll.js
index 3265f88f82..0ad8cec549 100644
--- a/erpnext/hr/doctype/process_payroll/process_payroll.js
+++ b/erpnext/hr/doctype/process_payroll/process_payroll.js
@@ -3,21 +3,24 @@
frappe.ui.form.on("Process Payroll", {
onload: function(frm) {
- frm.doc.posting_date = frm.doc.start_date = frm.doc.end_date = frappe.datetime.nowdate()
+ frm.doc.posting_date = frappe.datetime.nowdate();
+ frm.doc.start_date = '';
+ frm.doc.end_date = '';
+ frm.doc.payroll_frequency = '';
},
refresh: function(frm) {
frm.disable_save();
},
-
+
payroll_frequency: function(frm) {
frm.trigger("set_start_end_dates");
},
-
+
start_date: function(frm) {
frm.trigger("set_start_end_dates");
},
-
+
end_date: function(frm) {
frm.trigger("set_start_end_dates");
},
@@ -25,20 +28,19 @@ frappe.ui.form.on("Process Payroll", {
payment_account: function(frm) {
frm.toggle_display(['make_bank_entry'], (frm.doc.payment_account!="" && frm.doc.payment_account!="undefined"));
},
-
+
set_start_end_dates: function(frm) {
if (!frm.doc.salary_slip_based_on_timesheet){
frappe.call({
method:'erpnext.hr.doctype.process_payroll.process_payroll.get_start_end_dates',
args:{
payroll_frequency: frm.doc.payroll_frequency,
- start_date: frm.doc.start_date,
- end_date: frm.doc.end_date
+ start_date: frm.doc.start_date || frm.doc.posting_date
},
callback: function(r){
if (r.message){
frm.set_value('start_date', r.message.start_date);
- frm.set_value('end_date', r.message.end_date);
+ frm.set_value('end_date', r.message.end_date);
}
}
})
@@ -75,7 +77,7 @@ cur_frm.cscript.create_salary_slip = function(doc, cdt, cdn) {
if (r.message)
cur_frm.cscript.display_activity_log(r.message);
}
- return $c('runserverobj', args={'method':'create_sal_slip','docs':doc},callback);
+ return $c('runserverobj', args={'method':'create_salary_slips','docs':doc},callback);
}
cur_frm.cscript.submit_salary_slip = function(doc, cdt, cdn) {
@@ -94,7 +96,7 @@ cur_frm.cscript.submit_salary_slip = function(doc, cdt, cdn) {
cur_frm.cscript.display_activity_log(r.message);
}
- return $c('runserverobj', args={'method':'submit_salary_slip','docs':doc},callback);
+ return $c('runserverobj', args={'method':'submit_salary_slips','docs':doc},callback);
});
}
@@ -111,15 +113,15 @@ cur_frm.cscript.reference_entry = function(doc,cdt,cdn){
title: __("Bank Transaction Reference"),
fields: [
{
- "label": __("Reference Number"),
+ "label": __("Reference Number"),
"fieldname": "reference_number",
- "fieldtype": "Data",
+ "fieldtype": "Data",
"reqd": 1
},
{
- "label": __("Reference Date"),
+ "label": __("Reference Date"),
"fieldname": "reference_date",
- "fieldtype": "Date",
+ "fieldtype": "Date",
"reqd": 1,
"default": get_today()
}
diff --git a/erpnext/hr/doctype/process_payroll/process_payroll.json b/erpnext/hr/doctype/process_payroll/process_payroll.json
index 8e0d39577a..7d84910efe 100644
--- a/erpnext/hr/doctype/process_payroll/process_payroll.json
+++ b/erpnext/hr/doctype/process_payroll/process_payroll.json
@@ -127,7 +127,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
- "default": "Monthly",
+ "default": "",
"depends_on": "eval:doc.salary_slip_based_on_timesheet == 0",
"fieldname": "payroll_frequency",
"fieldtype": "Select",
@@ -352,7 +352,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
- "default": "Today",
+ "default": "",
"fieldname": "start_date",
"fieldtype": "Date",
"hidden": 0,
@@ -408,7 +408,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
- "default": "Today",
+ "default": "",
"fieldname": "end_date",
"fieldtype": "Date",
"hidden": 0,
@@ -722,7 +722,7 @@
"issingle": 1,
"istable": 0,
"max_attachments": 0,
- "modified": "2016-11-26 01:14:51.691057",
+ "modified": "2016-12-14 01:48:22.326326",
"modified_by": "Administrator",
"module": "HR",
"name": "Process Payroll",
diff --git a/erpnext/hr/doctype/process_payroll/process_payroll.py b/erpnext/hr/doctype/process_payroll/process_payroll.py
index 97e3876e02..45030cba13 100644
--- a/erpnext/hr/doctype/process_payroll/process_payroll.py
+++ b/erpnext/hr/doctype/process_payroll/process_payroll.py
@@ -5,15 +5,11 @@ from __future__ import unicode_literals
import frappe
from frappe.utils import cint, flt, nowdate, add_days, getdate
from frappe import _
-import collections
-from collections import defaultdict
-from calendar import monthrange
from erpnext.accounts.utils import get_fiscal_year
from frappe.model.document import Document
class ProcessPayroll(Document):
-
def get_emp_list(self):
"""
Returns list of active employees based on selected criteria
@@ -21,24 +17,33 @@ class ProcessPayroll(Document):
"""
cond = self.get_filter_condition()
cond += self.get_joining_releiving_condition()
-
-
- struct_cond = ''
+
+
+ condition = ''
if self.payroll_frequency:
- struct_cond = """and payroll_frequency = '%(payroll_frequency)s'""" % {"payroll_frequency": self.payroll_frequency}
-
+ condition = """and payroll_frequency = '%(payroll_frequency)s'""" % {"payroll_frequency": self.payroll_frequency}
+
sal_struct = frappe.db.sql("""
- select name from `tabSalary Structure`
- where docstatus != 2 and is_active = 'Yes' and company = %(company)s and
- ifnull(salary_slip_based_on_timesheet,0) = %(salary_slip_based_on_timesheet)s""",
+ select
+ name from `tabSalary Structure`
+ where
+ docstatus != 2 and
+ is_active = 'Yes'
+ and company = %(company)s and
+ ifnull(salary_slip_based_on_timesheet,0) = %(salary_slip_based_on_timesheet)s
+ {condition}""".format(condition=condition),
{"company": self.company, "salary_slip_based_on_timesheet":self.salary_slip_based_on_timesheet})
-
+
if sal_struct:
cond += "and t2.parent IN %(sal_struct)s "
emp_list = frappe.db.sql("""
- select t1.name
- from `tabEmployee` t1, `tabSalary Structure Employee` t2
- where t1.docstatus!=2 and t1.name = t2.employee
+ select
+ t1.name
+ from
+ `tabEmployee` t1, `tabSalary Structure Employee` t2
+ where
+ t1.docstatus!=2
+ and t1.name = t2.employee
%s """% cond, {"sal_struct": sal_struct})
return emp_list
@@ -67,7 +72,7 @@ class ProcessPayroll(Document):
if not self.get(fieldname):
frappe.throw(_("Please set {0}").format(self.meta.get_label(fieldname)))
- def create_sal_slip(self):
+ def create_salary_slips(self):
"""
Creates salary slip for selected employees if already not created
"""
@@ -77,31 +82,26 @@ class ProcessPayroll(Document):
ss_list = []
if emp_list:
for emp in emp_list:
- if not frappe.db.sql("""select name from `tabSalary Slip`
- where docstatus!= 2 and employee = %s and start_date >= %s and end_date <= %s and company = %s
+ if not frappe.db.sql("""select
+ name from `tabSalary Slip`
+ where
+ docstatus!= 2 and
+ employee = %s and
+ start_date >= %s and
+ end_date <= %s and
+ company = %s
""", (emp[0], self.start_date, self.end_date, self.company)):
- if self.payroll_frequency == "Monthly" or self.payroll_frequency == '':
- ss = frappe.get_doc({
- "doctype": "Salary Slip",
- "salary_slip_based_on_timesheet": self.salary_slip_based_on_timesheet,
- "employee": emp[0],
- "employee_name": frappe.get_value("Employee", {"name":emp[0]}, "employee_name"),
- "company": self.company,
- "posting_date": self.posting_date,
- "payroll_frequency": self.payroll_frequency
- })
- else:
- ss = frappe.get_doc({
- "doctype": "Salary Slip",
- "salary_slip_based_on_timesheet": self.salary_slip_based_on_timesheet,
- "start_date": self.start_date,
- "end_date": self.end_date,
- "employee": emp[0],
- "employee_name": frappe.get_value("Employee", {"name":emp[0]}, "employee_name"),
- "company": self.company,
- "posting_date": self.posting_date,
- "payroll_frequency": self.payroll_frequency
- })
+ ss = frappe.get_doc({
+ "doctype": "Salary Slip",
+ "salary_slip_based_on_timesheet": self.salary_slip_based_on_timesheet,
+ "payroll_frequency": self.payroll_frequency,
+ "start_date": self.start_date,
+ "end_date": self.end_date,
+ "employee": emp[0],
+ "employee_name": frappe.get_value("Employee", {"name":emp[0]}, "employee_name"),
+ "company": self.company,
+ "posting_date": self.posting_date
+ })
ss.insert()
ss_list.append(ss.name)
return self.create_log(ss_list)
@@ -120,16 +120,16 @@ class ProcessPayroll(Document):
Returns list of salary slips based on selected criteria
"""
cond = self.get_filter_condition()
-
+
ss_list = frappe.db.sql("""
select t1.name, t1.salary_structure from `tabSalary Slip` t1
- where t1.docstatus = %s and t1.start_date >= %s and t1.end_date <= %s
+ where t1.docstatus = %s and t1.start_date >= %s and t1.end_date <= %s
and (t1.journal_entry is null or t1.journal_entry = "") and ifnull(salary_slip_based_on_timesheet,0) = %s %s
""" % ('%s', '%s', '%s','%s', cond), (ss_status, self.start_date, self.end_date, self.salary_slip_based_on_timesheet), as_dict=as_dict)
return ss_list
- def submit_salary_slip(self):
+ def submit_salary_slips(self):
"""
Submit all salary slips based on selected criteria
"""
@@ -144,12 +144,11 @@ class ProcessPayroll(Document):
else:
try:
ss_obj.submit()
- except Exception,e:
+ except frappe.ValidationError:
not_submitted_ss.append(ss[0])
return self.create_submit_log(ss_list, not_submitted_ss)
-
def create_submit_log(self, all_ss, not_submitted_ss):
log = ''
if not all_ss:
@@ -167,12 +166,9 @@ class ProcessPayroll(Document):
log += """
Not Submitted Salary Slips: \
%s
\
- Reason:
\
- May be net pay is less than 0
- May be company email id specified in employee master is not valid.
\
- Please mention correct email id in employee master or if you don't want to \
- send mail, uncheck 'Send Email' checkbox.
\
- Then try to submit Salary Slip again.
+ Possible reasons:
\
+ 1. Net pay is less than 0
+ 2. Company email id specified in employee master is not valid.
\
"""% ('
'.join(not_submitted_ss))
return log
@@ -191,42 +187,42 @@ class ProcessPayroll(Document):
""" % ('%s', '%s', cond), (self.start_date, self.end_date))
return flt(tot[0][0])
-
+
def get_salary_component_account(self, salary_component):
account = frappe.db.get_value("Salary Component Account",
{"parent": salary_component, "company": self.company}, "default_account")
-
+
if not account:
frappe.throw(_("Please set default account in Salary Component {0}")
.format(salary_component))
-
- return account
-
+
+ return account
+
def get_salary_components(self, component_type):
salary_slips = self.get_sal_slip_list(ss_status = 1, as_dict = True)
if salary_slips:
salary_components = frappe.db.sql("""select salary_component, amount, parentfield
from `tabSalary Detail` where parentfield = '%s' and parent in (%s)""" %
- (component_type, ', '.join(['%s']*len(salary_slips))), tuple([d.name for d in salary_slips]), as_dict=True)
+ (component_type, ', '.join(['%s']*len(salary_slips))), tuple([d.name for d in salary_slips]), as_dict=True)
return salary_components
-
+
def get_salary_component_total(self, component_type = None):
salary_components = self.get_salary_components(component_type)
if salary_components:
component_dict = {}
- for item in salary_components:
+ for item in salary_components:
component_dict[item['salary_component']] = component_dict.get(item['salary_component'], 0) + item['amount']
account_details = self.get_account(component_dict = component_dict)
return account_details
-
+
def get_account(self, component_dict = None):
account_dict = {}
for s, a in component_dict.items():
account = self.get_salary_component_account(s)
- account_dict[account] = account_dict.get(account, 0) + a
+ account_dict[account] = account_dict.get(account, 0) + a
return account_dict
-
-
+
+
def make_journal_entry(self, reference_number = None, reference_date = None):
self.check_permission('write')
earnings = self.get_salary_component_total(component_type = "earnings") or {}
@@ -240,7 +236,7 @@ class ProcessPayroll(Document):
self.end_date)
journal_entry.company = self.company
journal_entry.posting_date = nowdate()
-
+
account_amt_list = []
adjustment_amt = 0
for acc, amt in earnings.items():
@@ -258,7 +254,7 @@ class ProcessPayroll(Document):
account_amt_list.append({
"account": self.payment_account,
"credit_in_account_currency": adjustment_amt
- })
+ })
journal_entry.set("accounts", account_amt_list)
journal_entry.cheque_no = reference_number
journal_entry.cheque_date = reference_date
@@ -271,24 +267,60 @@ class ProcessPayroll(Document):
except Exception, e:
frappe.msgprint(e)
return self.create_jv_log(jv_name)
-
+
def create_jv_log(self, jv_name):
log = "
" + _("No submitted Salary Slip found") + "
" if jv_name: log = "" + _("Journal Entry Submitted") + "\ %s" % '