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" % '
''{0}'.format(jv_name) - return log - + return log + def update_salary_slip_status(self, jv_name = None): ss_list = self.get_sal_slip_list(ss_status=1) for ss in ss_list: ss_obj = frappe.get_doc("Salary Slip",ss[0]) frappe.db.set_value("Salary Slip", ss_obj.name, "status", "Paid") - frappe.db.set_value("Salary Slip", ss_obj.name, "journal_entry", jv_name) + frappe.db.set_value("Salary Slip", ss_obj.name, "journal_entry", jv_name) + + def set_start_end_dates(self): + self.update(get_start_end_dates(self.payroll_frequency, self.start_date or self.posting_date)) @frappe.whitelist() +def get_start_end_dates(payroll_frequency, start_date=None): + '''Returns dict of start and end dates for given payroll frequency based on start_date''' + if not payroll_frequency: + frappe.throw(_("Please set Payroll Frequency first")) + + if payroll_frequency == "Monthly" or payroll_frequency == "Bimonthly": + fiscal_year = get_fiscal_year(start_date)[0] + month = "%02d" % getdate(start_date).month + m = get_month_details(fiscal_year, month) + if payroll_frequency == "Bimonthly": + if getdate(start_date).day <= 15: + start_date = m['month_start_date'] + end_date = m['month_mid_end_date'] + else: + start_date = m['month_mid_start_date'] + end_date = m['month_end_date'] + else: + start_date = m['month_start_date'] + end_date = m['month_end_date'] + + if payroll_frequency == "Weekly": + end_date = add_days(start_date, 6) + + if payroll_frequency == "Fortnightly": + end_date = add_days(start_date, 13) + + if payroll_frequency == "Daily": + end_date = start_date + + return frappe._dict({ + 'start_date': start_date, 'end_date': end_date + }) + def get_month_details(year, month): ysd = frappe.db.get_value("Fiscal Year", year, "year_start_date") if ysd: @@ -312,32 +344,3 @@ def get_month_details(year, month): }) else: frappe.throw(_("Fiscal Year {0} not found").format(year)) - -@frappe.whitelist() -def get_start_end_dates(payroll_frequency, start_date, end_date): - if payroll_frequency == "Monthly" or payroll_frequency == "Bimonthly": - fiscal_year = get_fiscal_year(start_date)[0] or get_fiscal_year(end_date)[0] - month = "%02d" % getdate(start_date).month or "%02d" % getdate(end_date).month - m = get_month_details(fiscal_year, month) - if payroll_frequency == "Bimonthly": - if getdate(start_date).day <= 15: - start_date = m['month_start_date'] - end_date = m['month_mid_end_date'] - else: - start_date = m['month_mid_start_date'] - end_date = m['month_end_date'] - else: - start_date = m['month_start_date'] - end_date = m['month_end_date'] - - if payroll_frequency == "Weekly": - end_date = add_days(start_date, 6) - - if payroll_frequency == "Fortnightly": - end_date = add_days(start_date, 13) - - if payroll_frequency == "Daily": - end_date = start_date - return frappe._dict({ - 'start_date': start_date, 'end_date': end_date - }) \ No newline at end of file diff --git a/erpnext/hr/doctype/process_payroll/test_process_payroll.py b/erpnext/hr/doctype/process_payroll/test_process_payroll.py index 5167365eb0..2eb1c6b76b 100644 --- a/erpnext/hr/doctype/process_payroll/test_process_payroll.py +++ b/erpnext/hr/doctype/process_payroll/test_process_payroll.py @@ -26,8 +26,8 @@ class TestProcessPayroll(unittest.TestCase): process_payroll.payment_account = payment_account process_payroll.posting_date = nowdate() process_payroll.payroll_frequency = "Monthly" - process_payroll.create_sal_slip() - process_payroll.submit_salary_slip() + process_payroll.create_salary_slips() + process_payroll.submit_salary_slips() if process_payroll.get_sal_slip_list(ss_status = 1): r = process_payroll.make_journal_entry(reference_number=random_string(10),reference_date=nowdate()) diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.json b/erpnext/hr/doctype/salary_slip/salary_slip.json index 062f41f8f3..e558d73bcf 100644 --- a/erpnext/hr/doctype/salary_slip/salary_slip.json +++ b/erpnext/hr/doctype/salary_slip/salary_slip.json @@ -44,6 +44,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "default": "Today", "fieldname": "posting_date", "fieldtype": "Date", "hidden": 0, @@ -421,7 +422,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "default": "Today", + "default": "", "fieldname": "start_date", "fieldtype": "Date", "hidden": 0, @@ -450,7 +451,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "default": "Today", + "default": "", "depends_on": "", "fieldname": "end_date", "fieldtype": "Date", @@ -1361,7 +1362,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2016-12-08 12:03:31.602913", + "modified": "2016-12-14 08:26:31.400930", "modified_by": "Administrator", "module": "HR", "name": "Salary Slip", diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.py b/erpnext/hr/doctype/salary_slip/salary_slip.py index ffd1136553..eeec6e8e76 100644 --- a/erpnext/hr/doctype/salary_slip/salary_slip.py +++ b/erpnext/hr/doctype/salary_slip/salary_slip.py @@ -4,18 +4,15 @@ from __future__ import unicode_literals import frappe -from frappe.utils import add_days, cint, cstr, flt, getdate, nowdate, rounded, date_diff, money_in_words +from frappe.utils import add_days, cint, cstr, flt, getdate, rounded, date_diff, money_in_words from frappe.model.naming import make_autoname from frappe import msgprint, _ -from erpnext.accounts.utils import get_fiscal_year from erpnext.setup.utils import get_company_currency -from erpnext.hr.doctype.process_payroll.process_payroll import get_month_details, get_start_end_dates +from erpnext.hr.doctype.process_payroll.process_payroll import get_start_end_dates from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee from erpnext.utilities.transaction_base import TransactionBase -from datetime import timedelta - class SalarySlip(TransactionBase): def autoname(self): self.name = make_autoname('Sal Slip/' +self.employee + '/.#####') @@ -36,7 +33,7 @@ class SalarySlip(TransactionBase): company_currency = get_company_currency(self.company) self.total_in_words = money_in_words(self.rounded_total, company_currency) - + if frappe.db.get_single_value("HR Settings", "max_working_hours_against_timesheet"): max_working_hours = frappe.db.get_single_value("HR Settings", "max_working_hours_against_timesheet") if self.salary_slip_based_on_timesheet and (self.total_working_hours > int(max_working_hours)): @@ -46,7 +43,7 @@ class SalarySlip(TransactionBase): def validate_dates(self): if date_diff(self.end_date, self.start_date) < 0: frappe.throw(_("To date cannot be before From date")) - + def calculate_component_amounts(self): if not getattr(self, '_salary_structure_doc', None): self._salary_structure_doc = frappe.get_doc('Salary Structure', self.salary_structure) @@ -54,17 +51,17 @@ class SalarySlip(TransactionBase): data = self.get_data_for_eval() for key in ('earnings', 'deductions'): for struct_row in self._salary_structure_doc.get(key): - amount = self.eval_condition_and_formula(struct_row, data) + amount = self.eval_condition_and_formula(struct_row, data) if amount: self.update_component_row(struct_row, amount, key) - - + + def update_component_row(self, struct_row, amount, key): component_row = None for d in self.get(key): if d.salary_component == struct_row.salary_component: component_row = d - + if not component_row: self.append(key, { 'amount': amount, @@ -74,12 +71,12 @@ class SalarySlip(TransactionBase): }) else: component_row.amount = amount - + def eval_condition_and_formula(self, d, data): try: if d.condition: if not eval(d.condition, None, data): - return None + return None amount = d.amount if d.amount_based_on_formula: if d.formula: @@ -87,23 +84,23 @@ class SalarySlip(TransactionBase): if amount: data[d.abbr] = amount return amount - + except NameError as err: frappe.throw(_("Name error: {0}".format(err))) except SyntaxError as err: frappe.throw(_("Syntax error in formula or condition: {0}".format(err))) except: frappe.throw(_("Error in formula or condition")) - raise - + raise + def get_data_for_eval(self): '''Returns data for evaluating formula''' data = frappe._dict() - + for d in self._salary_structure_doc.employees: if d.employee == self.employee: data.base, data.variable = d.base, d.variable - + data.update(frappe.get_doc("Employee", self.employee).as_dict()) data.update(self.as_dict()) @@ -111,9 +108,9 @@ class SalarySlip(TransactionBase): salary_components = frappe.get_all("Salary Component", fields=["salary_component_abbr"]) for salary_component in salary_components: data[salary_component.salary_component_abbr] = 0 - + return data - + def get_emp_and_leave_details(self): '''First time, load all the components from salary structure''' @@ -148,16 +145,16 @@ class SalarySlip(TransactionBase): }) def get_date_details(self): - date_details = get_start_end_dates(self.payroll_frequency, self.start_date, self.end_date) - self.start_date = date_details.start_date - self.end_date = date_details.end_date - + if not self.end_date: + date_details = get_start_end_dates(self.payroll_frequency, self.start_date or self.posting_date) + self.start_date = date_details.start_date + self.end_date = date_details.end_date def check_sal_struct(self, joining_date, relieving_date): cond = '' if self.payroll_frequency: cond = """and payroll_frequency = '%(payroll_frequency)s'""" % {"payroll_frequency": self.payroll_frequency} - + st_name = frappe.db.sql("""select parent from `tabSalary Structure Employee` where employee=%s and parent in (select name from `tabSalary Structure` @@ -165,7 +162,7 @@ class SalarySlip(TransactionBase): and (from_date <= %s or from_date <= %s) and (to_date is null or to_date >= %s or to_date >= %s) %s) """% ('%s', '%s', '%s','%s','%s', cond),(self.employee, self.start_date, joining_date, self.end_date, relieving_date)) - + if st_name: if len(st_name) > 1: frappe.msgprint(_("Multiple active Salary Structures found for employee {0} for the given dates") @@ -174,7 +171,7 @@ class SalarySlip(TransactionBase): else: self.salary_structure = None frappe.msgprint(_("No active or default Salary Structure found for employee {0} for the given dates") - .format(self.employee), title=_('Salary Structure Missing')) + .format(self.employee), title=_('Salary Structure Missing')) def pull_sal_struct(self): from erpnext.hr.doctype.salary_structure.salary_structure import make_salary_slip @@ -185,16 +182,13 @@ class SalarySlip(TransactionBase): self.hour_rate = self._salary_structure_doc.hour_rate self.total_working_hours = sum([d.working_hours or 0.0 for d in self.timesheets]) or 0.0 self.add_earning_for_hourly_wages(self._salary_structure_doc.salary_component) - - - + def process_salary_structure(self): '''Calculate salary after salary structure details have been updated''' self.get_date_details() self.pull_emp_details() self.get_leave_details() self.calculate_net_pay() - def add_earning_for_hourly_wages(self, salary_component): default_type = False @@ -233,10 +227,10 @@ class SalarySlip(TransactionBase): lwp = actual_lwp elif lwp != actual_lwp: frappe.msgprint(_("Leave Without Pay does not match with approved Leave Application records")) - + self.total_working_days = working_days self.leave_without_pay = lwp - + payment_days = flt(self.get_payment_days(joining_date, relieving_date)) - flt(lwp) self.payment_days = payment_days > 0 and payment_days or 0 @@ -278,7 +272,7 @@ class SalarySlip(TransactionBase): holidays = [cstr(i) for i in holidays] return holidays - + def calculate_lwp(self, holidays, working_days): lwp = 0 holidays = "','".join(holidays) @@ -297,7 +291,7 @@ class SalarySlip(TransactionBase): """.format(holidays), {"employee": self.employee, "dt": dt}) if leave: lwp = cint(leave[0][1]) and (lwp + 0.5) or (lwp + 1) - return lwp + return lwp def check_existing(self): if not self.salary_slip_based_on_timesheet: @@ -371,7 +365,7 @@ class SalarySlip(TransactionBase): timesheet.flags.ignore_validate_update_after_submit = True timesheet.set_status() timesheet.save() - + def set_status(self, status=None): '''Get and update status''' if not status: @@ -387,7 +381,7 @@ class SalarySlip(TransactionBase): status = "Paid" elif self.docstatus == 2: status = "Cancelled" - return status + return status def unlink_ref_doc_from_salary_slip(ref_no): linked_ss = frappe.db.sql_list("""select name from `tabSalary Slip` diff --git a/erpnext/hr/doctype/salary_slip/salary_slip_list.js b/erpnext/hr/doctype/salary_slip/salary_slip_list.js index 17f13d6bea..33d5bd786f 100644 --- a/erpnext/hr/doctype/salary_slip/salary_slip_list.js +++ b/erpnext/hr/doctype/salary_slip/salary_slip_list.js @@ -1,3 +1,3 @@ frappe.listview_settings['Salary Slip'] = { - add_fields: ["employee", "employee_name", "fiscal_year", "month"], + add_fields: ["employee", "employee_name"], }; diff --git a/erpnext/hr/doctype/salary_slip/test_salary_slip.py b/erpnext/hr/doctype/salary_slip/test_salary_slip.py index 4025db7a9f..dfa704aae6 100644 --- a/erpnext/hr/doctype/salary_slip/test_salary_slip.py +++ b/erpnext/hr/doctype/salary_slip/test_salary_slip.py @@ -16,7 +16,7 @@ class TestSalarySlip(unittest.TestCase): def setUp(self): make_earning_salary_component(["Basic Salary", "Allowance", "HRA"]) make_deduction_salary_component(["Professional Tax", "TDS"]) - + for dt in ["Leave Application", "Leave Allocation", "Salary Slip"]: frappe.db.sql("delete from `tab%s`" % dt) @@ -65,7 +65,7 @@ class TestSalarySlip(unittest.TestCase): self.assertEquals(ss.deductions[1].amount, 2500) self.assertEquals(ss.gross_pay, 10500) self.assertEquals(ss.net_pay, 3000) - + def test_payment_days(self): no_of_days = self.get_no_of_days() # Holidays not included in working days @@ -86,10 +86,13 @@ class TestSalarySlip(unittest.TestCase): date_of_joining = getdate(nowdate()) relieving_date = getdate(nowdate()) - frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "date_of_joining", date_of_joining) - frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "relieving_date", None) - frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "status", "Active") - + frappe.db.set_value("Employee", frappe.get_value("Employee", + {"employee_name":"test_employee@salary.com"}, "name"), "date_of_joining", date_of_joining) + frappe.db.set_value("Employee", frappe.get_value("Employee", + {"employee_name":"test_employee@salary.com"}, "name"), "relieving_date", None) + frappe.db.set_value("Employee", frappe.get_value("Employee", + {"employee_name":"test_employee@salary.com"}, "name"), "status", "Active") + ss = frappe.get_doc("Salary Slip", self.make_employee_salary_slip("test_employee@salary.com", "Monthly")) @@ -101,7 +104,7 @@ class TestSalarySlip(unittest.TestCase): frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "relieving_date", relieving_date) frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "status", "Left") ss.save() - + self.assertEquals(ss.total_working_days, no_of_days[0]) self.assertEquals(ss.payment_days, getdate(relieving_date).day) @@ -190,7 +193,7 @@ class TestSalarySlip(unittest.TestCase): "from_date": fiscal_year[1], "to_date": fiscal_year[2], "weekly_off": "Sunday" - }).insert() + }).insert() holiday_list.get_weekly_off_dates() holiday_list.save() @@ -198,12 +201,11 @@ class TestSalarySlip(unittest.TestCase): employee = frappe.db.get_value("Employee", {"user_id": user}) salary_structure = make_salary_structure(payroll_frequency + " Salary Structure Test for Salary Slip", payroll_frequency, employee) salary_slip = frappe.db.get_value("Salary Slip", {"employee": frappe.db.get_value("Employee", {"user_id": user})}) - + if not salary_slip: salary_slip = make_salary_slip(salary_structure, employee = employee) salary_slip.employee_name = frappe.get_value("Employee", {"name":frappe.db.get_value("Employee", {"user_id": user})}, "employee_name") salary_slip.payroll_frequency = payroll_frequency - salary_slip.start_date = nowdate() salary_slip.posting_date = nowdate() salary_slip.insert() # salary_slip.submit() @@ -220,9 +222,10 @@ class TestSalarySlip(unittest.TestCase): def get_no_of_days(self): no_of_days_in_month = calendar.monthrange(getdate(nowdate()).year, - getdate(nowdate()).month) + getdate(nowdate()).month) no_of_holidays_in_month = len([1 for i in calendar.monthcalendar(getdate(nowdate()).year, - getdate(nowdate()).month) if i[6] != 0]) + getdate(nowdate()).month) if i[6] != 0]) + return [no_of_days_in_month[1], no_of_holidays_in_month] @@ -280,7 +283,7 @@ def get_employee_details(employee): } ] -def get_earnings_component(): +def get_earnings_component(): return [ { "salary_component": 'Basic Salary', @@ -310,8 +313,8 @@ def get_earnings_component(): "idx": 4 }, ] - -def get_deductions_component(): + +def get_deductions_component(): return [ { "salary_component": 'Professional Tax', diff --git a/erpnext/hr/doctype/salary_structure/salary_structure.json b/erpnext/hr/doctype/salary_structure/salary_structure.json index 5c0a635901..8db8e93ef7 100644 --- a/erpnext/hr/doctype/salary_structure/salary_structure.json +++ b/erpnext/hr/doctype/salary_structure/salary_structure.json @@ -100,7 +100,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "default": "", + "default": "Monthly", "depends_on": "eval:(!doc.salary_slip_based_on_timesheet)", "fieldname": "payroll_frequency", "fieldtype": "Select", @@ -894,7 +894,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2016-12-07 14:57:22.083825", + "modified": "2016-12-14 02:02:10.848614", "modified_by": "Administrator", "module": "HR", "name": "Salary Structure", diff --git a/erpnext/setup/doctype/currency_exchange/currency_exchange.py b/erpnext/setup/doctype/currency_exchange/currency_exchange.py index 7f1a43c0ee..ab892fb41b 100644 --- a/erpnext/setup/doctype/currency_exchange/currency_exchange.py +++ b/erpnext/setup/doctype/currency_exchange/currency_exchange.py @@ -7,15 +7,17 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils import get_datetime, get_datetime_str, formatdate +from frappe.utils import get_datetime_str, formatdate, nowdate class CurrencyExchange(Document): - def autoname(self): - self.name = formatdate(get_datetime_str(self.date),"yyyy-MM-dd") + "-" + self.from_currency + "-" + self.to_currency - #self.name = self.date + "-" + self.from_currency + "-" + self.to_currency + def autoname(self): + if not self.date: + self.date = nowdate() + self.name = '{0}-{1}-{2}'.format(formatdate(get_datetime_str(self.date), "yyyy-MM-dd"), + self.from_currency, self.to_currency) - def validate(self): - self.validate_value("exchange_rate", ">", 0) + def validate(self): + self.validate_value("exchange_rate", ">", 0) - if self.from_currency == self.to_currency: - frappe.throw(_("From Currency and To Currency cannot be same")) + if self.from_currency == self.to_currency: + frappe.throw(_("From Currency and To Currency cannot be same")) diff --git a/erpnext/setup/setup_wizard/install_fixtures.py b/erpnext/setup/setup_wizard/install_fixtures.py index a80e6d95c2..1589b3b8f0 100644 --- a/erpnext/setup/setup_wizard/install_fixtures.py +++ b/erpnext/setup/setup_wizard/install_fixtures.py @@ -31,8 +31,8 @@ def install(country=None): 'is_group': 0, 'parent_item_group': _('All Item Groups') }, # salary component - {'doctype': 'Salary Component', 'salary_component': _('Income Tax'), 'description': _('Income Tax')}, - {'doctype': 'Salary Component', 'salary_component': _('Basic'), 'description': _('Basic')}, + {'doctype': 'Salary Component', 'salary_component': _('Income Tax'), 'description': _('Income Tax'), 'type': 'Deduction'}, + {'doctype': 'Salary Component', 'salary_component': _('Basic'), 'description': _('Basic'), 'type': 'Earning'}, # expense claim type {'doctype': 'Expense Claim Type', 'name': _('Calls'), 'expense_type': _('Calls')}, @@ -186,9 +186,6 @@ def install(country=None): {'doctype': "Print Heading", 'print_heading': _("Credit Note")}, {'doctype': "Print Heading", 'print_heading': _("Debit Note")}, - - {"doctype": "Salary Component", "salary_component": _("Basic")}, - {"doctype": "Salary Component", "salary_component": _("Income Tax")}, ] from erpnext.setup.setup_wizard.industry_type import get_industry_types