From 7b2870431e2f76ca2ba281c18a9df644fe553a39 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 30 Oct 2017 12:41:34 +0530 Subject: [PATCH 1/9] new doctype payroll_entry --- erpnext/hr/doctype/payroll_entry/__init__.py | 0 .../hr/doctype/payroll_entry/payroll_entry.js | 138 +++ .../doctype/payroll_entry/payroll_entry.json | 827 ++++++++++++++++++ .../hr/doctype/payroll_entry/payroll_entry.py | 437 +++++++++ .../payroll_entry/test_payroll_entry.js | 48 + .../payroll_entry/test_payroll_entry.py | 10 + 6 files changed, 1460 insertions(+) create mode 100644 erpnext/hr/doctype/payroll_entry/__init__.py create mode 100644 erpnext/hr/doctype/payroll_entry/payroll_entry.js create mode 100644 erpnext/hr/doctype/payroll_entry/payroll_entry.json create mode 100644 erpnext/hr/doctype/payroll_entry/payroll_entry.py create mode 100644 erpnext/hr/doctype/payroll_entry/test_payroll_entry.js create mode 100644 erpnext/hr/doctype/payroll_entry/test_payroll_entry.py diff --git a/erpnext/hr/doctype/payroll_entry/__init__.py b/erpnext/hr/doctype/payroll_entry/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/hr/doctype/payroll_entry/payroll_entry.js b/erpnext/hr/doctype/payroll_entry/payroll_entry.js new file mode 100644 index 0000000000..5de8228934 --- /dev/null +++ b/erpnext/hr/doctype/payroll_entry/payroll_entry.js @@ -0,0 +1,138 @@ +// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Payroll Entry', { + onload: function (frm) { + frm.doc.posting_date = frappe.datetime.nowdate(); + frm.toggle_reqd(['payroll_frequency'], !frm.doc.salary_slip_based_on_timesheet); + }, + + refresh: function(frm) { + if (frm.doc.docstatus==1) { + if(frm.doc.payment_account) { + frm.add_custom_button("Make Bank Entry", function() { + make_bank_entry(frm); + }); + } + + frm.add_custom_button("Submit Salary Slip", function() { + submit_salary_slip(frm); + }); + + frm.add_custom_button("View Salary Slip", function() { + frappe.set_route('List', 'Salary Slip', + {posting_date: frm.doc.posting_date}); + }); + } + }, + + setup: function (frm) { + frm.set_query("payment_account", function () { + var account_types = ["Bank", "Cash"]; + return { + filters: { + "account_type": ["in", account_types], + "is_group": 0, + "company": frm.doc.company + } + } + }), + frm.set_query("cost_center", function () { + return { + filters: { + "is_group": 0, + company: frm.doc.company + } + } + }), + frm.set_query("project", function () { + return { + filters: { + company: frm.doc.company + } + } + }) + }, + + payroll_frequency: function (frm) { + frm.trigger("set_start_end_dates"); + }, + + start_date: function (frm) { + if(!in_progress && frm.doc.start_date){ + frm.trigger("set_end_date"); + }else{ + // reset flag + in_progress = false + } + }, + + salary_slip_based_on_timesheet: function (frm) { + frm.toggle_reqd(['payroll_frequency'], !frm.doc.salary_slip_based_on_timesheet); + }, + + + set_start_end_dates: function (frm) { + if (!frm.doc.salary_slip_based_on_timesheet) { + frappe.call({ + method: 'erpnext.hr.doctype.payroll_entry.payroll_entry.get_start_end_dates', + args: { + payroll_frequency: frm.doc.payroll_frequency, + start_date: frm.doc.posting_date + }, + callback: function (r) { + if (r.message) { + in_progress = true; + frm.set_value('start_date', r.message.start_date); + frm.set_value('end_date', r.message.end_date); + } + } + }) + } + }, + + set_end_date: function(frm){ + frappe.call({ + method: 'erpnext.hr.doctype.payroll_entry.payroll_entry.get_end_date', + args: { + frequency: frm.doc.payroll_frequency, + start_date: frm.doc.start_date + }, + callback: function (r) { + if (r.message) { + frm.set_value('end_date', r.message.end_date); + } + } + }) + }, +}) + +// Create salary slips + +cur_frm.cscript.custom_before_submit = function (doc, cdt, cdn) { + return $c('runserverobj', { 'method': 'create_salary_slips', 'docs': doc }); +} + +// Submit salary slips + +submit_salary_slip = function (frm, cdt, cdn) { + doc = frm.doc; + return $c('runserverobj', { 'method': 'submit_salary_slips', 'docs': doc }); +} + +make_bank_entry = function (frm, cdt, cdn) { + doc = frm.doc; + if (doc.company && doc.start_date && doc.end_date) { + return frappe.call({ + doc: cur_frm.doc, + method: "make_payment_entry", + callback: function (r) { + if (r.message) + var doc = frappe.model.sync(r.message)[0]; + frappe.set_route("Form", doc.doctype, doc.name); + } + }); + } else { + frappe.msgprint(__("Company, From Date and To Date is mandatory")); + } +} diff --git a/erpnext/hr/doctype/payroll_entry/payroll_entry.json b/erpnext/hr/doctype/payroll_entry/payroll_entry.json new file mode 100644 index 0000000000..136f35ea94 --- /dev/null +++ b/erpnext/hr/doctype/payroll_entry/payroll_entry.json @@ -0,0 +1,827 @@ +{ + "allow_copy": 1, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "Payroll .####", + "beta": 0, + "creation": "2017-10-23 15:22:29.291323", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "Other", + "editable_grid": 0, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break0", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Select Employees", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break0", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0, + "width": "50%" + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "", + "fieldname": "company", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Company", + "length": 0, + "no_copy": 0, + "options": "Company", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 1, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "Today", + "fieldname": "posting_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Posting Date", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "", + "depends_on": "eval:doc.salary_slip_based_on_timesheet == 0", + "fieldname": "payroll_frequency", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Payroll Frequency", + "length": 0, + "no_copy": 0, + "options": "\nMonthly\nFortnightly\nBimonthly\nWeekly\nDaily", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break1", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0, + "width": "50%" + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "branch", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Branch", + "length": 0, + "no_copy": 0, + "options": "Branch", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "department", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Department", + "length": 0, + "no_copy": 0, + "options": "Department", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "designation", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Designation", + "length": 0, + "no_copy": 0, + "options": "Designation", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_8", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "0", + "fieldname": "salary_slip_based_on_timesheet", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Salary Slip Based on Timesheet", + "length": 0, + "no_copy": 0, + "options": "", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "select_payroll_period", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Select Payroll Period", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "", + "fieldname": "start_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Start Date", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_11", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "", + "fieldname": "end_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "End Date", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_16", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Accounts", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "cost_center", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Cost Center", + "length": 0, + "no_copy": 0, + "options": "Cost Center", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_18", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "project", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Project", + "length": 0, + "no_copy": 0, + "options": "Project", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break2", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0, + "width": "50%" + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "account", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Payment Entry", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "description": "Select Payment Account to make Bank Entry", + "fieldname": "payment_account", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Payment Account", + "length": 0, + "no_copy": 0, + "options": "Account", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break2", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "activity_log", + "fieldtype": "HTML", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Activity Log", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "amended_from", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Amended From", + "length": 0, + "no_copy": 1, + "options": "Payroll Entry", + "permlevel": 0, + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "icon": "fa fa-cog", + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 1, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2017-10-27 12:44:07.378315", + "modified_by": "Administrator", + "module": "HR", + "name": "Payroll Entry", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 0, + "export": 0, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 0, + "read": 1, + "report": 0, + "role": "HR Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 1, + "write": 1 + } + ], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 0, + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/hr/doctype/payroll_entry/payroll_entry.py b/erpnext/hr/doctype/payroll_entry/payroll_entry.py new file mode 100644 index 0000000000..ef9e4ae6d6 --- /dev/null +++ b/erpnext/hr/doctype/payroll_entry/payroll_entry.py @@ -0,0 +1,437 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document +from dateutil.relativedelta import relativedelta +from frappe.utils import cint, flt, nowdate, add_days, getdate, fmt_money, add_to_date, DATE_FORMAT +from frappe import _ +from erpnext.accounts.utils import get_fiscal_year + +class PayrollEntry(Document): + def get_emp_list(self): + """ + Returns list of active employees based on selected criteria + and for which salary structure exists + """ + cond = self.get_filter_condition() + cond += self.get_joining_releiving_condition() + + + condition = '' + if 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 + {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 + %s """% cond, {"sal_struct": sal_struct}) + return emp_list + else: + frappe.msgprint(_("No active or default Salary Structure found for employee {0} for the given dates") + .format(self .employee), title=_('Salary Structure Missing')) + + def get_filter_condition(self): + self.check_mandatory() + + cond = '' + for f in ['company', 'branch', 'department', 'designation']: + if self.get(f): + cond += " and t1." + f + " = '" + self.get(f).replace("'", "\'") + "'" + + return cond + + def get_joining_releiving_condition(self): + cond = """ + and ifnull(t1.date_of_joining, '0000-00-00') <= '%(end_date)s' + and ifnull(t1.relieving_date, '2199-12-31') >= '%(start_date)s' + """ % {"start_date": self.start_date, "end_date": self.end_date} + return cond + + def check_mandatory(self): + for fieldname in ['company', 'start_date', 'end_date']: + if not self.get(fieldname): + frappe.throw(_("Please set {0}").format(self.meta.get_label(fieldname))) + + def create_salary_slips(self): + """ + Creates salary slip for selected employees if already not created + """ + self.check_permission('write') + self.created = 1; + emp_list = self.get_emp_list() + 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 + """, (emp[0], self.start_date, self.end_date, self.company)): + 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_dict = {} + ss_dict["Employee Name"] = ss.employee_name + ss_dict["Total Pay"] = fmt_money(ss.rounded_total,currency = frappe.defaults.get_global_default("currency")) + ss_dict["Salary Slip"] = self.format_as_links(ss.name)[0] + ss_list.append(ss_dict) + return self.create_log(ss_list) + + def create_log(self, ss_list): + if not ss_list or len(ss_list) < 1: + frappe.throw(_("No employee for the above selected criteria OR salary slip already created")) + + def get_sal_slip_list(self, ss_status, as_dict=False): + """ + 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 + 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_slips(self): + """ + Submit all salary slips based on selected criteria + """ + self.check_permission('write') + + # self.create_salary_slips() + + jv_name = "" + ss_list = self.get_sal_slip_list(ss_status=0) + submitted_ss = [] + not_submitted_ss = [] + for ss in ss_list: + ss_obj = frappe.get_doc("Salary Slip",ss[0]) + ss_dict = {} + ss_dict["Employee Name"] = ss_obj.employee_name + ss_dict["Total Pay"] = fmt_money(ss_obj.net_pay, + currency = frappe.defaults.get_global_default("currency")) + ss_dict["Salary Slip"] = self.format_as_links(ss_obj.name)[0] + + if ss_obj.net_pay<0: + not_submitted_ss.append(ss_dict) + else: + try: + ss_obj.submit() + submitted_ss.append(ss_dict) + + except frappe.ValidationError: + not_submitted_ss.append(ss_dict) + if submitted_ss: + jv_name = self.make_accural_jv_entry() + frappe.msgprint(_("Salary Slip submitted from {0} to {1}").format(ss_obj.start_date, ss_obj.end_date)) + + return self.create_submit_log(submitted_ss, not_submitted_ss, jv_name) + + def create_submit_log(self, submitted_ss, not_submitted_ss, jv_name): + + if not submitted_ss and not not_submitted_ss: + frappe.msgprint("No salary slip found to submit for the above selected criteria OR salary slip already submitted") + + if not_submitted_ss: + frappe.msgprint("Not submitted Salary Slip
\ + Possible reasons:
\ + 1. Net pay is less than 0.
\ + 2. Company Email Address specified in employee master is not valid.
") + + def format_as_links(self, salary_slip): + return ['{0}'.format(salary_slip)] + + def get_total_salary_and_loan_amounts(self): + """ + Get total loan principal, loan interest and salary amount from submitted salary slip based on selected criteria + """ + cond = self.get_filter_condition() + totals = frappe.db.sql(""" + select sum(principal_amount) as total_principal_amount, sum(interest_amount) as total_interest_amount, + sum(total_loan_repayment) as total_loan_repayment, sum(rounded_total) as rounded_total from `tabSalary Slip` t1 + where t1.docstatus = 1 and start_date >= %s and end_date <= %s %s + """ % ('%s', '%s', cond), (self.start_date, self.end_date), as_dict=True) + return totals[0] + + def get_loan_accounts(self): + loan_accounts = frappe.get_all("Employee Loan", fields=["employee_loan_account", "interest_income_account"], + filters = {"company": self.company, "docstatus":1}) + if loan_accounts: + return loan_accounts[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 + + 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) + 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: + 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 + return account_dict + + def get_default_payroll_payable_account(self): + payroll_payable_account = frappe.db.get_value("Company", + {"company_name": self.company}, "default_payroll_payable_account") + + if not payroll_payable_account: + frappe.throw(_("Please set Default Payroll Payable Account in Company {0}") + .format(self.company)) + + return payroll_payable_account + + def make_accural_jv_entry(self): + self.check_permission('write') + earnings = self.get_salary_component_total(component_type = "earnings") or {} + deductions = self.get_salary_component_total(component_type = "deductions") or {} + default_payroll_payable_account = self.get_default_payroll_payable_account() + loan_amounts = self.get_total_salary_and_loan_amounts() + loan_accounts = self.get_loan_accounts() + jv_name = "" + precision = frappe.get_precision("Journal Entry Account", "debit_in_account_currency") + + if earnings or deductions: + journal_entry = frappe.new_doc('Journal Entry') + journal_entry.voucher_type = 'Journal Entry' + journal_entry.user_remark = _('Accural Journal Entry for salaries from {0} to {1}')\ + .format(self.start_date, self.end_date) + journal_entry.company = self.company + journal_entry.posting_date = nowdate() + + accounts = [] + payable_amount = 0 + + # Earnings + for acc, amount in earnings.items(): + payable_amount += flt(amount, precision) + accounts.append({ + "account": acc, + "debit_in_account_currency": flt(amount, precision), + "cost_center": self.cost_center, + "project": self.project + }) + + # Deductions + for acc, amount in deductions.items(): + payable_amount -= flt(amount, precision) + accounts.append({ + "account": acc, + "credit_in_account_currency": flt(amount, precision), + "cost_center": self.cost_center, + "project": self.project + }) + + # Employee loan + if loan_amounts.total_loan_repayment: + accounts.append({ + "account": loan_accounts.employee_loan_account, + "credit_in_account_currency": loan_amounts.total_principal_amount + }) + accounts.append({ + "account": loan_accounts.interest_income_account, + "credit_in_account_currency": loan_amounts.total_interest_amount, + "cost_center": self.cost_center, + "project": self.project + }) + payable_amount -= flt(loan_amounts.total_loan_repayment, precision) + + # Payable amount + accounts.append({ + "account": default_payroll_payable_account, + "credit_in_account_currency": flt(payable_amount, precision) + }) + + journal_entry.set("accounts", accounts) + journal_entry.save() + + try: + journal_entry.submit() + jv_name = journal_entry.name + self.update_salary_slip_status(jv_name = jv_name) + except Exception as e: + frappe.msgprint(e) + + return jv_name + + def make_payment_entry(self): + self.check_permission('write') + total_salary_amount = self.get_total_salary_and_loan_amounts() + default_payroll_payable_account = self.get_default_payroll_payable_account() + precision = frappe.get_precision("Journal Entry Account", "debit_in_account_currency") + + if total_salary_amount.rounded_total: + journal_entry = frappe.new_doc('Journal Entry') + journal_entry.voucher_type = 'Bank Entry' + journal_entry.user_remark = _('Payment of salary from {0} to {1}')\ + .format(self.start_date, self.end_date) + journal_entry.company = self.company + journal_entry.posting_date = nowdate() + + payment_amount = flt(total_salary_amount.rounded_total, precision) + + journal_entry.set("accounts", [ + { + "account": self.payment_account, + "credit_in_account_currency": payment_amount + }, + { + "account": default_payroll_payable_account, + "debit_in_account_currency": payment_amount + } + ]) + return journal_entry.as_dict() + else: + frappe.msgprint( + _("There are no submitted Salary Slips to process."), + title="Error", indicator="red" + ) + + 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) + + def set_start_end_dates(self): + self.update(get_start_end_dates(self.payroll_frequency, + self.start_date or self.posting_date, self.company)) + +@frappe.whitelist() +def get_start_end_dates(payroll_frequency, start_date=None, company=None): + '''Returns dict of start and end dates for given payroll frequency based on start_date''' + + if payroll_frequency == "Monthly" or payroll_frequency == "Bimonthly" or payroll_frequency == "": + fiscal_year = get_fiscal_year(start_date, company=company)[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_frequency_kwargs(frequency_name): + frequency_dict = { + 'monthly': {'months': 1}, + 'fortnightly': {'days': 14}, + 'weekly': {'days': 7}, + 'daily': {'days': 1} + } + return frequency_dict.get(frequency_name) + +@frappe.whitelist() +def get_end_date(start_date, frequency): + start_date = getdate(start_date) + frequency = frequency.lower() if frequency else 'monthly' + kwargs = get_frequency_kwargs(frequency) if frequency != 'bimonthly' else get_frequency_kwargs('monthly') + + # weekly, fortnightly and daily intervals have fixed days so no problems + end_date = add_to_date(start_date, **kwargs) - relativedelta(days=1) + if frequency != 'bimonthly': + return dict(end_date=end_date.strftime(DATE_FORMAT)) + + else: + return dict(end_date='') + +def get_month_details(year, month): + ysd = frappe.db.get_value("Fiscal Year", year, "year_start_date") + if ysd: + from dateutil.relativedelta import relativedelta + import calendar, datetime + frappe.msgprint + diff_mnt = cint(month)-cint(ysd.month) + if diff_mnt<0: + diff_mnt = 12-int(ysd.month)+cint(month) + msd = ysd + relativedelta(months=diff_mnt) # month start date + month_days = cint(calendar.monthrange(cint(msd.year) ,cint(month))[1]) # days in month + mid_start = datetime.date(msd.year, cint(month), 16) # month mid start date + mid_end = datetime.date(msd.year, cint(month), 15) # month mid end date + med = datetime.date(msd.year, cint(month), month_days) # month end date + return frappe._dict({ + 'year': msd.year, + 'month_start_date': msd, + 'month_end_date': med, + 'month_mid_start_date': mid_start, + 'month_mid_end_date': mid_end, + 'month_days': month_days + }) + else: + frappe.throw(_("Fiscal Year {0} not found").format(year)) \ No newline at end of file diff --git a/erpnext/hr/doctype/payroll_entry/test_payroll_entry.js b/erpnext/hr/doctype/payroll_entry/test_payroll_entry.js new file mode 100644 index 0000000000..f1b82e0cc3 --- /dev/null +++ b/erpnext/hr/doctype/payroll_entry/test_payroll_entry.js @@ -0,0 +1,48 @@ +QUnit.module('HR') + +QUnit.test("test: Payroll Entry", function (assert) { + assert.expect(5); + let done = assert.async(); + + frappe.run_serially([ + () => { + return frappe.tests.make('Payroll Entry', [ + {company: 'For Testing'}, + {posting_date: frappe.datetime.add_days(frappe.datetime.nowdate(), 0)}, + {payroll_frequency: 'Monthly'}, + // {start_date: }, + {cost_center: 'Main - '+frappe.get_abbr(frappe.defaults.get_default("Company"))} + ]); + }, + + () => frappe.click_button('Submit'), + () => frappe.timeout(1), + () => frappe.click_button('Yes'), + () => frappe.timeout(2), + + () => { + assert.equal(cur_frm.doc.company, 'For Testing'); + assert.equal(cur_frm.doc.posting_date, frappe.datetime.add_days(frappe.datetime.nowdate(), 0)); + assert.equal(cur_frm.doc.cost_center, 'Main - FT'); + }, + + () => frappe.click_button('View Salary Slip'), + () => frappe.timeout(2), + () => assert.equal(cur_list.data[0].docstatus, 0), + + () => frappe.set_route('Form', 'Payroll Entry', 'Payroll 0041'), + () => frappe.click_button('Submit Salary Slip'), + () => frappe.timeout(2), + + () => frappe.click_button('Close'), + () => frappe.timeout(1), + + () => frappe.click_button('View Salary Slip'), + () => frappe.timeout(2), + () => { + assert.ok(cur_list.data[0].docstatus == 1, "Salary slip submitted"); + }, + + () => done() + ]); +}); diff --git a/erpnext/hr/doctype/payroll_entry/test_payroll_entry.py b/erpnext/hr/doctype/payroll_entry/test_payroll_entry.py new file mode 100644 index 0000000000..47aba56a5c --- /dev/null +++ b/erpnext/hr/doctype/payroll_entry/test_payroll_entry.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest + +class TestPayrollEntry(unittest.TestCase): + pass From d73dd056d63b1d337081cffd61331d904c150871 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 30 Oct 2017 12:41:48 +0530 Subject: [PATCH 2/9] test case --- .../hr/doctype/payroll_entry/payroll_entry.js | 47 ++++++++++--------- .../hr/doctype/payroll_entry/payroll_entry.py | 17 ++++--- .../payroll_entry/test_payroll_entry.js | 24 +++++----- .../payroll_entry/test_payroll_entry.py | 1 - erpnext/tests/ui/tests.txt | 1 + 5 files changed, 45 insertions(+), 45 deletions(-) diff --git a/erpnext/hr/doctype/payroll_entry/payroll_entry.js b/erpnext/hr/doctype/payroll_entry/payroll_entry.js index 5de8228934..cc10d7081d 100644 --- a/erpnext/hr/doctype/payroll_entry/payroll_entry.js +++ b/erpnext/hr/doctype/payroll_entry/payroll_entry.js @@ -1,6 +1,8 @@ // Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt +var in_progress = false; + frappe.ui.form.on('Payroll Entry', { onload: function (frm) { frm.doc.posting_date = frappe.datetime.nowdate(); @@ -35,23 +37,23 @@ frappe.ui.form.on('Payroll Entry', { "is_group": 0, "company": frm.doc.company } - } + }; }), - frm.set_query("cost_center", function () { - return { - filters: { - "is_group": 0, - company: frm.doc.company - } + frm.set_query("cost_center", function () { + return { + filters: { + "is_group": 0, + company: frm.doc.company } - }), - frm.set_query("project", function () { - return { - filters: { - company: frm.doc.company - } + }; + }), + frm.set_query("project", function () { + return { + filters: { + company: frm.doc.company } - }) + }; + }); }, payroll_frequency: function (frm) { @@ -63,7 +65,7 @@ frappe.ui.form.on('Payroll Entry', { frm.trigger("set_end_date"); }else{ // reset flag - in_progress = false + in_progress = false; } }, @@ -71,7 +73,6 @@ frappe.ui.form.on('Payroll Entry', { frm.toggle_reqd(['payroll_frequency'], !frm.doc.salary_slip_based_on_timesheet); }, - set_start_end_dates: function (frm) { if (!frm.doc.salary_slip_based_on_timesheet) { frappe.call({ @@ -87,7 +88,7 @@ frappe.ui.form.on('Payroll Entry', { frm.set_value('end_date', r.message.end_date); } } - }) + }); } }, @@ -109,19 +110,19 @@ frappe.ui.form.on('Payroll Entry', { // Create salary slips -cur_frm.cscript.custom_before_submit = function (doc, cdt, cdn) { +cur_frm.cscript.custom_before_submit = function (doc) { return $c('runserverobj', { 'method': 'create_salary_slips', 'docs': doc }); -} +}; // Submit salary slips -submit_salary_slip = function (frm, cdt, cdn) { - doc = frm.doc; +let submit_salary_slip = function (frm) { + var doc = frm.doc; return $c('runserverobj', { 'method': 'submit_salary_slips', 'docs': doc }); } -make_bank_entry = function (frm, cdt, cdn) { - doc = frm.doc; +let make_bank_entry = function (frm) { + var doc = frm.doc; if (doc.company && doc.start_date && doc.end_date) { return frappe.call({ doc: cur_frm.doc, diff --git a/erpnext/hr/doctype/payroll_entry/payroll_entry.py b/erpnext/hr/doctype/payroll_entry/payroll_entry.py index ef9e4ae6d6..a77df95180 100644 --- a/erpnext/hr/doctype/payroll_entry/payroll_entry.py +++ b/erpnext/hr/doctype/payroll_entry/payroll_entry.py @@ -112,8 +112,9 @@ class PayrollEntry(Document): return self.create_log(ss_list) def create_log(self, ss_list): - if not ss_list or len(ss_list) < 1: + if not ss_list or len(ss_list) < 1: frappe.throw(_("No employee for the above selected criteria OR salary slip already created")) + return def get_sal_slip_list(self, ss_status, as_dict=False): """ @@ -147,7 +148,7 @@ class PayrollEntry(Document): ss_dict["Total Pay"] = fmt_money(ss_obj.net_pay, currency = frappe.defaults.get_global_default("currency")) ss_dict["Salary Slip"] = self.format_as_links(ss_obj.name)[0] - + if ss_obj.net_pay<0: not_submitted_ss.append(ss_dict) else: @@ -173,7 +174,7 @@ class PayrollEntry(Document): Possible reasons:
\ 1. Net pay is less than 0.
\ 2. Company Email Address specified in employee master is not valid.
") - + return def format_as_links(self, salary_slip): return ['{0}'.format(salary_slip)] @@ -188,9 +189,9 @@ class PayrollEntry(Document): where t1.docstatus = 1 and start_date >= %s and end_date <= %s %s """ % ('%s', '%s', cond), (self.start_date, self.end_date), as_dict=True) return totals[0] - + def get_loan_accounts(self): - loan_accounts = frappe.get_all("Employee Loan", fields=["employee_loan_account", "interest_income_account"], + loan_accounts = frappe.get_all("Employee Loan", fields=["employee_loan_account", "interest_income_account"], filters = {"company": self.company, "docstatus":1}) if loan_accounts: return loan_accounts[0] @@ -237,7 +238,7 @@ class PayrollEntry(Document): frappe.throw(_("Please set Default Payroll Payable Account in Company {0}") .format(self.company)) - return payroll_payable_account + return payroll_payable_account def make_accural_jv_entry(self): self.check_permission('write') @@ -353,7 +354,7 @@ class PayrollEntry(Document): 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.update(get_start_end_dates(self.payroll_frequency, self.start_date or self.posting_date, self.company)) @frappe.whitelist() @@ -414,9 +415,7 @@ def get_end_date(start_date, frequency): def get_month_details(year, month): ysd = frappe.db.get_value("Fiscal Year", year, "year_start_date") if ysd: - from dateutil.relativedelta import relativedelta import calendar, datetime - frappe.msgprint diff_mnt = cint(month)-cint(ysd.month) if diff_mnt<0: diff_mnt = 12-int(ysd.month)+cint(month) diff --git a/erpnext/hr/doctype/payroll_entry/test_payroll_entry.js b/erpnext/hr/doctype/payroll_entry/test_payroll_entry.js index f1b82e0cc3..69599bdaf4 100644 --- a/erpnext/hr/doctype/payroll_entry/test_payroll_entry.js +++ b/erpnext/hr/doctype/payroll_entry/test_payroll_entry.js @@ -1,38 +1,38 @@ -QUnit.module('HR') +QUnit.module('HR'); QUnit.test("test: Payroll Entry", function (assert) { assert.expect(5); let done = assert.async(); frappe.run_serially([ - () => { + () => { return frappe.tests.make('Payroll Entry', [ {company: 'For Testing'}, {posting_date: frappe.datetime.add_days(frappe.datetime.nowdate(), 0)}, {payroll_frequency: 'Monthly'}, - // {start_date: }, {cost_center: 'Main - '+frappe.get_abbr(frappe.defaults.get_default("Company"))} ]); }, + () => frappe.timeout(1), + () => { + assert.equal(cur_frm.doc.company, 'For Testing'); + assert.equal(cur_frm.doc.posting_date, frappe.datetime.add_days(frappe.datetime.nowdate(), 0)); + assert.equal(cur_frm.doc.cost_center, 'Main - FT'); + }, + () => frappe.click_button('Submit'), () => frappe.timeout(1), () => frappe.click_button('Yes'), () => frappe.timeout(2), - () => { - assert.equal(cur_frm.doc.company, 'For Testing'); - assert.equal(cur_frm.doc.posting_date, frappe.datetime.add_days(frappe.datetime.nowdate(), 0)); - assert.equal(cur_frm.doc.cost_center, 'Main - FT'); - }, - () => frappe.click_button('View Salary Slip'), () => frappe.timeout(2), () => assert.equal(cur_list.data[0].docstatus, 0), - () => frappe.set_route('Form', 'Payroll Entry', 'Payroll 0041'), + () => frappe.set_route('Form', 'Payroll Entry', 'Payroll 0001'), () => frappe.click_button('Submit Salary Slip'), - () => frappe.timeout(2), + () => frappe.timeout(3), () => frappe.click_button('Close'), () => frappe.timeout(1), @@ -40,7 +40,7 @@ QUnit.test("test: Payroll Entry", function (assert) { () => frappe.click_button('View Salary Slip'), () => frappe.timeout(2), () => { - assert.ok(cur_list.data[0].docstatus == 1, "Salary slip submitted"); + assert.ok(cur_list.data[0].docstatus == 1, "Salary slip submitted"); }, () => done() diff --git a/erpnext/hr/doctype/payroll_entry/test_payroll_entry.py b/erpnext/hr/doctype/payroll_entry/test_payroll_entry.py index 47aba56a5c..163a809947 100644 --- a/erpnext/hr/doctype/payroll_entry/test_payroll_entry.py +++ b/erpnext/hr/doctype/payroll_entry/test_payroll_entry.py @@ -3,7 +3,6 @@ # See license.txt from __future__ import unicode_literals -import frappe import unittest class TestPayrollEntry(unittest.TestCase): diff --git a/erpnext/tests/ui/tests.txt b/erpnext/tests/ui/tests.txt index edf1d78ffb..a5dcbcb5cc 100644 --- a/erpnext/tests/ui/tests.txt +++ b/erpnext/tests/ui/tests.txt @@ -78,6 +78,7 @@ erpnext/hr/doctype/training_feedback/test_training_feedback.js erpnext/hr/doctype/loan_type/test_loan_type.js erpnext/hr/doctype/employee_loan_application/test_employee_loan_application.js erpnext/hr/doctype/employee_loan/test_employee_loan.js +erpnext/hr/doctype/payroll_entry/test_payroll_entry.js erpnext/buying/doctype/supplier/test_supplier.js erpnext/buying/doctype/request_for_quotation/tests/test_request_for_quotation.js erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation.js From d1defa5fc7c9de4324dded90c82ad78b8a0023a8 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 30 Oct 2017 17:50:02 +0530 Subject: [PATCH 3/9] set salary components --- .../hr/doctype/payroll_entry/payroll_entry.js | 8 +-- .../hr/doctype/payroll_entry/payroll_entry.py | 54 ++++++++-------- .../test_set_salary_components.js | 61 +++++++++++++++++++ erpnext/tests/ui/tests.txt | 1 + 4 files changed, 91 insertions(+), 33 deletions(-) create mode 100644 erpnext/hr/doctype/payroll_entry/test_set_salary_components.js diff --git a/erpnext/hr/doctype/payroll_entry/payroll_entry.js b/erpnext/hr/doctype/payroll_entry/payroll_entry.js index cc10d7081d..7f4769c9b0 100644 --- a/erpnext/hr/doctype/payroll_entry/payroll_entry.js +++ b/erpnext/hr/doctype/payroll_entry/payroll_entry.js @@ -104,9 +104,9 @@ frappe.ui.form.on('Payroll Entry', { frm.set_value('end_date', r.message.end_date); } } - }) + }); }, -}) +}); // Create salary slips @@ -119,7 +119,7 @@ cur_frm.cscript.custom_before_submit = function (doc) { let submit_salary_slip = function (frm) { var doc = frm.doc; return $c('runserverobj', { 'method': 'submit_salary_slips', 'docs': doc }); -} +}; let make_bank_entry = function (frm) { var doc = frm.doc; @@ -136,4 +136,4 @@ let make_bank_entry = function (frm) { } else { frappe.msgprint(__("Company, From Date and To Date is mandatory")); } -} +}; diff --git a/erpnext/hr/doctype/payroll_entry/payroll_entry.py b/erpnext/hr/doctype/payroll_entry/payroll_entry.py index a77df95180..b36afa3299 100644 --- a/erpnext/hr/doctype/payroll_entry/payroll_entry.py +++ b/erpnext/hr/doctype/payroll_entry/payroll_entry.py @@ -47,9 +47,6 @@ class PayrollEntry(Document): and t1.name = t2.employee %s """% cond, {"sal_struct": sal_struct}) return emp_list - else: - frappe.msgprint(_("No active or default Salary Structure found for employee {0} for the given dates") - .format(self .employee), title=_('Salary Structure Missing')) def get_filter_condition(self): self.check_mandatory() @@ -107,14 +104,9 @@ class PayrollEntry(Document): ss_dict = {} ss_dict["Employee Name"] = ss.employee_name ss_dict["Total Pay"] = fmt_money(ss.rounded_total,currency = frappe.defaults.get_global_default("currency")) - ss_dict["Salary Slip"] = self.format_as_links(ss.name)[0] + ss_dict["Salary Slip"] = format_as_links(ss.name)[0] ss_list.append(ss_dict) - return self.create_log(ss_list) - - def create_log(self, ss_list): - if not ss_list or len(ss_list) < 1: - frappe.throw(_("No employee for the above selected criteria OR salary slip already created")) - return + return create_log(ss_list) def get_sal_slip_list(self, ss_status, as_dict=False): """ @@ -146,8 +138,8 @@ class PayrollEntry(Document): ss_dict = {} ss_dict["Employee Name"] = ss_obj.employee_name ss_dict["Total Pay"] = fmt_money(ss_obj.net_pay, - currency = frappe.defaults.get_global_default("currency")) - ss_dict["Salary Slip"] = self.format_as_links(ss_obj.name)[0] + currency = frappe.defaults.get_global_default("currency")) + ss_dict["Salary Slip"] = format_as_links(ss_obj.name)[0] if ss_obj.net_pay<0: not_submitted_ss.append(ss_dict) @@ -162,21 +154,7 @@ class PayrollEntry(Document): jv_name = self.make_accural_jv_entry() frappe.msgprint(_("Salary Slip submitted from {0} to {1}").format(ss_obj.start_date, ss_obj.end_date)) - return self.create_submit_log(submitted_ss, not_submitted_ss, jv_name) - - def create_submit_log(self, submitted_ss, not_submitted_ss, jv_name): - - if not submitted_ss and not not_submitted_ss: - frappe.msgprint("No salary slip found to submit for the above selected criteria OR salary slip already submitted") - - if not_submitted_ss: - frappe.msgprint("Not submitted Salary Slip
\ - Possible reasons:
\ - 1. Net pay is less than 0.
\ - 2. Company Email Address specified in employee master is not valid.
") - return - def format_as_links(self, salary_slip): - return ['{0}'.format(salary_slip)] + return create_submit_log(submitted_ss, not_submitted_ss, jv_name) def get_total_salary_and_loan_amounts(self): """ @@ -229,7 +207,7 @@ class PayrollEntry(Document): account = self.get_salary_component_account(s) account_dict[account] = account_dict.get(account, 0) + a return account_dict - + def get_default_payroll_payable_account(self): payroll_payable_account = frappe.db.get_value("Company", {"company_name": self.company}, "default_payroll_payable_account") @@ -433,4 +411,22 @@ def get_month_details(year, month): 'month_days': month_days }) else: - frappe.throw(_("Fiscal Year {0} not found").format(year)) \ No newline at end of file + frappe.throw(_("Fiscal Year {0} not found").format(year)) + +def create_log(ss_list): + if not ss_list or len(ss_list) < 1: + frappe.throw(_("No employee for the above selected criteria OR salary slip already created")) + +def format_as_links(salary_slip): + return ['{0}'.format(salary_slip)] + +def create_submit_log(submitted_ss, not_submitted_ss, jv_name): + + if not submitted_ss and not not_submitted_ss: + frappe.msgprint("No salary slip found to submit for the above selected criteria OR salary slip already submitted") + + if not_submitted_ss: + frappe.msgprint("Not submitted Salary Slip
\ + Possible reasons:
\ + 1. Net pay is less than 0.
\ + 2. Company Email Address specified in employee master is not valid.
") \ No newline at end of file diff --git a/erpnext/hr/doctype/payroll_entry/test_set_salary_components.js b/erpnext/hr/doctype/payroll_entry/test_set_salary_components.js new file mode 100644 index 0000000000..8ff55151f6 --- /dev/null +++ b/erpnext/hr/doctype/payroll_entry/test_set_salary_components.js @@ -0,0 +1,61 @@ +QUnit.module('HR'); + +QUnit.test("test: Set Salary Components", function (assert) { + assert.expect(5); + let done = assert.async(); + + frappe.run_serially([ + () => frappe.set_route('Form', 'Salary Component', 'Leave Encashment'), + () => { + var row = frappe.model.add_child(cur_frm.doc, "Salary Component Account", "accounts"); + row.company = 'For Testing'; + row.default_account = 'Salary - FT'; + }, + + () => cur_frm.save(), + () => frappe.timeout(2), + () => assert.equal(cur_frm.doc.accounts[0].default_account, 'Salary - FT'), + + () => frappe.set_route('Form', 'Salary Component', 'Basic'), + () => { + var row = frappe.model.add_child(cur_frm.doc, "Salary Component Account", "accounts"); + row.company = 'For Testing'; + row.default_account = 'Salary - FT'; + }, + + () => cur_frm.save(), + () => frappe.timeout(2), + () => assert.equal(cur_frm.doc.accounts[0].default_account, 'Salary - FT'), + + () => frappe.set_route('Form', 'Salary Component', 'Income Tax'), + () => { + var row = frappe.model.add_child(cur_frm.doc, "Salary Component Account", "accounts"); + row.company = 'For Testing'; + row.default_account = 'Salary - FT'; + }, + + () => cur_frm.save(), + () => frappe.timeout(2), + () => assert.equal(cur_frm.doc.accounts[0].default_account, 'Salary - FT'), + + () => frappe.set_route('Form', 'Salary Component', 'Arrear'), + () => { + var row = frappe.model.add_child(cur_frm.doc, "Salary Component Account", "accounts"); + row.company = 'For Testing'; + row.default_account = 'Salary - FT'; + }, + + () => cur_frm.save(), + () => frappe.timeout(2), + () => assert.equal(cur_frm.doc.accounts[0].default_account, 'Salary - FT'), + + () => frappe.set_route('Form', 'Company', 'For Testing'), + () => cur_frm.set_value('default_payroll_payable_account', 'Payroll Payable - FT'), + () => cur_frm.save(), + () => frappe.timeout(2), + () => assert.equal(cur_frm.doc.default_payroll_payable_account, 'Payroll Payable - FT'), + + () => done() + + ]); +}); diff --git a/erpnext/tests/ui/tests.txt b/erpnext/tests/ui/tests.txt index a5dcbcb5cc..08fc9c31d4 100644 --- a/erpnext/tests/ui/tests.txt +++ b/erpnext/tests/ui/tests.txt @@ -78,6 +78,7 @@ erpnext/hr/doctype/training_feedback/test_training_feedback.js erpnext/hr/doctype/loan_type/test_loan_type.js erpnext/hr/doctype/employee_loan_application/test_employee_loan_application.js erpnext/hr/doctype/employee_loan/test_employee_loan.js +erpnext/hr/doctype/payroll_entry/test_set_salary_components.js erpnext/hr/doctype/payroll_entry/test_payroll_entry.js erpnext/buying/doctype/supplier/test_supplier.js erpnext/buying/doctype/request_for_quotation/tests/test_request_for_quotation.js From b5dff03453686ba214a6ba4f8ebb5ff2b48cca14 Mon Sep 17 00:00:00 2001 From: Shreya Date: Fri, 3 Nov 2017 14:28:21 +0530 Subject: [PATCH 4/9] added changes from process payroll --- .../doctype/payroll_entry/payroll_entry.json | 31 +++++++++++- .../hr/doctype/payroll_entry/payroll_entry.py | 50 +++++++++++-------- erpnext/hr/doctype/salary_slip/salary_slip.js | 2 +- erpnext/hr/doctype/salary_slip/salary_slip.py | 2 +- .../doctype/salary_slip/test_salary_slip.py | 4 +- 5 files changed, 62 insertions(+), 27 deletions(-) diff --git a/erpnext/hr/doctype/payroll_entry/payroll_entry.json b/erpnext/hr/doctype/payroll_entry/payroll_entry.json index 136f35ea94..77e38d140e 100644 --- a/erpnext/hr/doctype/payroll_entry/payroll_entry.json +++ b/erpnext/hr/doctype/payroll_entry/payroll_entry.json @@ -474,6 +474,35 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "data_19", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_on_submit": 0, @@ -788,7 +817,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-10-27 12:44:07.378315", + "modified": "2017-11-30 12:11:30.985647", "modified_by": "Administrator", "module": "HR", "name": "Payroll Entry", diff --git a/erpnext/hr/doctype/payroll_entry/payroll_entry.py b/erpnext/hr/doctype/payroll_entry/payroll_entry.py index b36afa3299..627f13b6b3 100644 --- a/erpnext/hr/doctype/payroll_entry/payroll_entry.py +++ b/erpnext/hr/doctype/payroll_entry/payroll_entry.py @@ -156,23 +156,28 @@ class PayrollEntry(Document): return create_submit_log(submitted_ss, not_submitted_ss, jv_name) - def get_total_salary_and_loan_amounts(self): + def get_loan_details(self): """ - Get total loan principal, loan interest and salary amount from submitted salary slip based on selected criteria + Get loan details from submitted salary slip based on selected criteria """ cond = self.get_filter_condition() - totals = frappe.db.sql(""" - select sum(principal_amount) as total_principal_amount, sum(interest_amount) as total_interest_amount, - sum(total_loan_repayment) as total_loan_repayment, sum(rounded_total) as rounded_total from `tabSalary Slip` t1 + return frappe.db.sql(""" select eld.employee_loan_account, + eld.interest_income_account, eld.principal_amount, eld.interest_amount, eld.total_payment + from + `tabSalary Slip` t1, `tabSalary Slip Loan` eld + where + t1.docstatus = 1 and t1.name = eld.parent and start_date >= %s and end_date <= %s %s + """ % ('%s', '%s', cond), (self.start_date, self.end_date), as_dict=True) or [] + + def get_total_salary_amount(self): + """ + Get total salary amount from submitted salary slip based on selected criteria + """ + cond = self.get_filter_condition() + totals = frappe.db.sql(""" select sum(rounded_total) as rounded_total from `tabSalary Slip` t1 where t1.docstatus = 1 and start_date >= %s and end_date <= %s %s """ % ('%s', '%s', cond), (self.start_date, self.end_date), as_dict=True) - return totals[0] - - def get_loan_accounts(self): - loan_accounts = frappe.get_all("Employee Loan", fields=["employee_loan_account", "interest_income_account"], - filters = {"company": self.company, "docstatus":1}) - if loan_accounts: - return loan_accounts[0] + return totals and totals[0] or None def get_salary_component_account(self, salary_component): account = frappe.db.get_value("Salary Component Account", @@ -223,8 +228,7 @@ class PayrollEntry(Document): earnings = self.get_salary_component_total(component_type = "earnings") or {} deductions = self.get_salary_component_total(component_type = "deductions") or {} default_payroll_payable_account = self.get_default_payroll_payable_account() - loan_amounts = self.get_total_salary_and_loan_amounts() - loan_accounts = self.get_loan_accounts() + loan_details = self.get_loan_details() jv_name = "" precision = frappe.get_precision("Journal Entry Account", "debit_in_account_currency") @@ -260,18 +264,18 @@ class PayrollEntry(Document): }) # Employee loan - if loan_amounts.total_loan_repayment: + for data in loan_details: accounts.append({ - "account": loan_accounts.employee_loan_account, - "credit_in_account_currency": loan_amounts.total_principal_amount + "account": data.employee_loan_account, + "credit_in_account_currency": data.principal_amount }) accounts.append({ - "account": loan_accounts.interest_income_account, - "credit_in_account_currency": loan_amounts.total_interest_amount, + "account": data.interest_income_account, + "credit_in_account_currency": data.interest_amount, "cost_center": self.cost_center, "project": self.project }) - payable_amount -= flt(loan_amounts.total_loan_repayment, precision) + payable_amount -= flt(data.total_payment, precision) # Payable amount accounts.append({ @@ -293,11 +297,11 @@ class PayrollEntry(Document): def make_payment_entry(self): self.check_permission('write') - total_salary_amount = self.get_total_salary_and_loan_amounts() + total_salary_amount = self.get_total_salary_amount() default_payroll_payable_account = self.get_default_payroll_payable_account() precision = frappe.get_precision("Journal Entry Account", "debit_in_account_currency") - if total_salary_amount.rounded_total: + if total_salary_amount and total_salary_amount.rounded_total: journal_entry = frappe.new_doc('Journal Entry') journal_entry.voucher_type = 'Bank Entry' journal_entry.user_remark = _('Payment of salary from {0} to {1}')\ @@ -413,9 +417,11 @@ def get_month_details(year, month): else: frappe.throw(_("Fiscal Year {0} not found").format(year)) +@frappe.whitelist() def create_log(ss_list): if not ss_list or len(ss_list) < 1: frappe.throw(_("No employee for the above selected criteria OR salary slip already created")) + return ss_list def format_as_links(salary_slip): return ['{0}'.format(salary_slip)] diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.js b/erpnext/hr/doctype/salary_slip/salary_slip.js index 4679cbdda9..840387ce2b 100644 --- a/erpnext/hr/doctype/salary_slip/salary_slip.js +++ b/erpnext/hr/doctype/salary_slip/salary_slip.js @@ -37,7 +37,7 @@ frappe.ui.form.on("Salary Slip", { set_end_date: function(frm){ frappe.call({ - method: 'erpnext.hr.doctype.process_payroll.process_payroll.get_end_date', + method: 'erpnext.hr.doctype.payroll_entry.payroll_entry.get_end_date', args: { frequency: frm.doc.payroll_frequency, start_date: frm.doc.start_date diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.py b/erpnext/hr/doctype/salary_slip/salary_slip.py index ea5f35b04a..656a4ace15 100644 --- a/erpnext/hr/doctype/salary_slip/salary_slip.py +++ b/erpnext/hr/doctype/salary_slip/salary_slip.py @@ -8,7 +8,7 @@ from frappe.utils import add_days, cint, cstr, flt, getdate, rounded, date_diff, from frappe.model.naming import make_autoname from frappe import msgprint, _ -from erpnext.hr.doctype.process_payroll.process_payroll import get_start_end_dates +from erpnext.hr.doctype.payroll_entry.payroll_entry 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 frappe.utils.background_jobs import enqueue diff --git a/erpnext/hr/doctype/salary_slip/test_salary_slip.py b/erpnext/hr/doctype/salary_slip/test_salary_slip.py index f136cb532a..493a8251c9 100644 --- a/erpnext/hr/doctype/salary_slip/test_salary_slip.py +++ b/erpnext/hr/doctype/salary_slip/test_salary_slip.py @@ -9,8 +9,8 @@ import calendar from erpnext.accounts.utils import get_fiscal_year from frappe.utils import getdate, nowdate, add_days, add_months, flt from erpnext.hr.doctype.salary_structure.salary_structure import make_salary_slip -from erpnext.hr.doctype.process_payroll.test_process_payroll import get_salary_component_account -from erpnext.hr.doctype.process_payroll.process_payroll import get_month_details +from erpnext.hr.doctype.payroll_entry.payroll_entry import get_salary_component_account +from erpnext.hr.doctype.payroll_entry.payroll_entry import get_month_details class TestSalarySlip(unittest.TestCase): def setUp(self): From 9956f15c80781a8c01249a7c86536afda993d8f1 Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 30 Nov 2017 15:51:37 +0530 Subject: [PATCH 5/9] added py test --- .../payroll_entry/test_payroll_entry.js | 1 + .../payroll_entry/test_payroll_entry.py | 196 +++++++++++++++++- erpnext/tests/ui/tests.txt | 4 +- 3 files changed, 194 insertions(+), 7 deletions(-) diff --git a/erpnext/hr/doctype/payroll_entry/test_payroll_entry.js b/erpnext/hr/doctype/payroll_entry/test_payroll_entry.js index 69599bdaf4..5eee3fdc25 100644 --- a/erpnext/hr/doctype/payroll_entry/test_payroll_entry.js +++ b/erpnext/hr/doctype/payroll_entry/test_payroll_entry.js @@ -31,6 +31,7 @@ QUnit.test("test: Payroll Entry", function (assert) { () => assert.equal(cur_list.data[0].docstatus, 0), () => frappe.set_route('Form', 'Payroll Entry', 'Payroll 0001'), + () => frappe.timeout(2), () => frappe.click_button('Submit Salary Slip'), () => frappe.timeout(3), diff --git a/erpnext/hr/doctype/payroll_entry/test_payroll_entry.py b/erpnext/hr/doctype/payroll_entry/test_payroll_entry.py index 163a809947..2cd6dbfca3 100644 --- a/erpnext/hr/doctype/payroll_entry/test_payroll_entry.py +++ b/erpnext/hr/doctype/payroll_entry/test_payroll_entry.py @@ -1,9 +1,195 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt from __future__ import unicode_literals import unittest -class TestPayrollEntry(unittest.TestCase): - pass +import erpnext +import frappe +from dateutil.relativedelta import relativedelta +from erpnext.accounts.utils import get_fiscal_year, getdate, nowdate +from erpnext.hr.doctype.payroll_entry.payroll_entry import get_start_end_dates, get_end_date + +class TestProcessPayroll(unittest.TestCase): + def test_payroll_entry(self): + month = "11" + fiscal_year = "_Test Fiscal Year 2016" + + for data in frappe.get_all('Salary Component', fields = ["name"]): + if not frappe.db.get_value('Salary Component Account', + {'parent': data.name, 'company': erpnext.get_default_company()}, 'name'): + get_salary_component_account(data.name) + + if not frappe.db.get_value("Salary Slip", {"start_date": "2016-11-01", "end_date": "2016-11-30"}): + make_payroll_entry() + + def test_get_end_date(self): + self.assertEqual(get_end_date('2017-01-01', 'monthly'), {'end_date': '2017-01-31'}) + self.assertEqual(get_end_date('2017-02-01', 'monthly'), {'end_date': '2017-02-28'}) + self.assertEqual(get_end_date('2017-02-01', 'fortnightly'), {'end_date': '2017-02-14'}) + self.assertEqual(get_end_date('2017-02-01', 'bimonthly'), {'end_date': ''}) + self.assertEqual(get_end_date('2017-01-01', 'bimonthly'), {'end_date': ''}) + self.assertEqual(get_end_date('2020-02-15', 'bimonthly'), {'end_date': ''}) + self.assertEqual(get_end_date('2017-02-15', 'monthly'), {'end_date': '2017-03-14'}) + self.assertEqual(get_end_date('2017-02-15', 'daily'), {'end_date': '2017-02-15'}) + + def test_employee_loan(self): + from erpnext.hr.doctype.salary_structure.test_salary_structure import (make_employee, + make_salary_structure) + from erpnext.hr.doctype.employee_loan.test_employee_loan import create_employee_loan + + branch = "Test Employee Branch" + employee = make_employee("test_employee@loan.com") + company = erpnext.get_default_company() + holiday_list = make_holiday("test holiday for loan") + + if not frappe.db.exists('Salary Component', 'Basic Salary'): + frappe.get_doc({ + 'doctype': 'Salary Component', + 'salary_component': 'Basic Salary', + 'salary_component_abbr': 'BS', + 'type': 'Earning', + 'accounts': [{ + 'company': company, + 'default_account': frappe.db.get_value('Account', + {'company': company, 'root_type': 'Expense', 'account_type': ''}, 'name') + }] + }).insert() + + if not frappe.db.get_value('Salary Component Account', + {'parent': 'Basic Salary', 'company': company}): + salary_component = frappe.get_doc('Salary Component', 'Basic Salary') + salary_component.append('accounts', { + 'company': company, + 'default_account': 'Salary - WP' + }) + + company_doc = frappe.get_doc('Company', company) + if not company_doc.default_payroll_payable_account: + company_doc.default_payroll_payable_account = frappe.db.get_value('Account', + {'company': company, 'root_type': 'Liability', 'account_type': ''}, 'name') + company_doc.save() + + if not frappe.db.exists('Branch', branch): + frappe.get_doc({ + 'doctype': 'Branch', + 'branch': branch + }).insert() + + employee_doc = frappe.get_doc('Employee', employee) + employee_doc.branch = branch + employee_doc.holiday_list = holiday_list + employee_doc.save() + + employee_loan = create_employee_loan(employee, + "Personal Loan", 280000, "Repay Over Number of Periods", 20) + employee_loan.repay_from_salary = 1 + employee_loan.submit() + + salary_strcture = "Test Salary Structure for Loan" + if not frappe.db.exists('Salary Structure', salary_strcture): + salary_strcture = make_salary_structure(salary_strcture, [{ + 'employee': employee, + 'from_date': '2017-01-01', + 'base': 30000 + }]) + + salary_strcture = frappe.get_doc('Salary Structure', salary_strcture) + salary_strcture.set('earnings', [{ + 'salary_component': 'Basic Salary', + 'abbr': 'BS', + 'amount_based_on_formula':1, + 'formula': 'base*.5' + }]) + salary_strcture.save() + + dates = get_start_end_dates('Monthly', nowdate()) + make_payroll_entry(start_date=dates.start_date, + end_date=dates.end_date, branch=branch) + + name = frappe.db.get_value('Salary Slip', + {'posting_date': nowdate(), 'employee': employee}, 'name') + + salary_slip = frappe.get_doc('Salary Slip', name) + for row in salary_slip.loans: + if row.employee_loan == employee_loan.name: + interest_amount = (280000 * 8.4)/(12*100) + principal_amount = employee_loan.monthly_repayment_amount - interest_amount + self.assertEqual(row.interest_amount, interest_amount) + self.assertEqual(row.principal_amount, principal_amount) + self.assertEqual(row.total_payment, + interest_amount + principal_amount) + + if salary_slip.docstatus == 0: + frappe.delete_doc('Salary Slip', name) + + employee_loan.cancel() + frappe.delete_doc('Employee Loan', employee_loan.name) + +def get_salary_component_account(sal_comp): + company = erpnext.get_default_company() + sal_comp = frappe.get_doc("Salary Component", sal_comp) + sc = sal_comp.append("accounts") + sc.company = company + sc.default_account = create_account(company) + +def create_account(company): + salary_account = frappe.db.get_value("Account", "Salary - " + frappe.db.get_value('Company', company, 'abbr')) + if not salary_account: + frappe.get_doc({ + "doctype": "Account", + "account_name": "Salary", + "parent_account": "Indirect Expenses - " + frappe.db.get_value('Company', company, 'abbr'), + "company": company + }).insert() + return salary_account + +def make_payroll_entry(**args): + args = frappe._dict(args) + + payroll_entry = frappe.new_doc("Payroll Entry") + payroll_entry.company = erpnext.get_default_company() + payroll_entry.start_date = args.start_date or "2016-11-01" + payroll_entry.end_date = args.end_date or "2016-11-30" + payroll_entry.payment_account = get_payment_account() + payroll_entry.posting_date = nowdate() + payroll_entry.payroll_frequency = "Monthly" + payroll_entry.branch = args.branch or None + payroll_entry.create_salary_slips() + payroll_entry.submit_salary_slips() + if payroll_entry.get_sal_slip_list(ss_status = 1): + r = payroll_entry.make_payment_entry() + + return payroll_entry + +def get_payment_account(): + return frappe.get_value('Account', + {'account_type': 'Cash', 'company': erpnext.get_default_company(),'is_group':0}, "name") + +def make_holiday(holiday_list_name): + if not frappe.db.exists('Holiday List', holiday_list_name): + current_fiscal_year = get_fiscal_year(nowdate(), as_dict=True) + dt = getdate(nowdate()) + + new_year = dt + relativedelta(month=01, day=01, year=dt.year) + republic_day = dt + relativedelta(month=01, day=26, year=dt.year) + test_holiday = dt + relativedelta(month=02, day=02, year=dt.year) + + frappe.get_doc({ + 'doctype': 'Holiday List', + 'from_date': current_fiscal_year.year_start_date, + 'to_date': current_fiscal_year.year_end_date, + 'holiday_list_name': holiday_list_name, + 'holidays': [{ + 'holiday_date': new_year, + 'description': 'New Year' + }, { + 'holiday_date': republic_day, + 'description': 'Republic Day' + }, { + 'holiday_date': test_holiday, + 'description': 'Test Holiday' + }] + }).insert() + + return holiday_list_name diff --git a/erpnext/tests/ui/tests.txt b/erpnext/tests/ui/tests.txt index 08fc9c31d4..b760e98006 100644 --- a/erpnext/tests/ui/tests.txt +++ b/erpnext/tests/ui/tests.txt @@ -78,8 +78,6 @@ erpnext/hr/doctype/training_feedback/test_training_feedback.js erpnext/hr/doctype/loan_type/test_loan_type.js erpnext/hr/doctype/employee_loan_application/test_employee_loan_application.js erpnext/hr/doctype/employee_loan/test_employee_loan.js -erpnext/hr/doctype/payroll_entry/test_set_salary_components.js -erpnext/hr/doctype/payroll_entry/test_payroll_entry.js erpnext/buying/doctype/supplier/test_supplier.js erpnext/buying/doctype/request_for_quotation/tests/test_request_for_quotation.js erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation.js @@ -142,3 +140,5 @@ erpnext/stock/doctype/delivery_note/test_delivery_note_with_margin.js erpnext/selling/doctype/sales_order/tests/test_sales_order_with_margin.js erpnext/selling/doctype/quotation/tests/test_quotation_with_margin.js erpnext/accounts/doctype/sales_invoice/tests/test_sales_invoice_with_margin.js +erpnext/hr/doctype/payroll_entry/test_set_salary_components.js +erpnext/hr/doctype/payroll_entry/test_payroll_entry.js From db332f2c080e8782ce7720b2e9de3225d805a8a3 Mon Sep 17 00:00:00 2001 From: Shreya Shah Date: Thu, 30 Nov 2017 18:02:36 +0530 Subject: [PATCH 6/9] employee_details table added --- .../payroll_employee_detail/__init__.py | 0 .../payroll_employee_detail.json | 194 ++++++++++++++++++ .../payroll_employee_detail.py | 10 + .../hr/doctype/payroll_entry/payroll_entry.js | 8 + .../doctype/payroll_entry/payroll_entry.json | 92 ++++++++- .../hr/doctype/payroll_entry/payroll_entry.py | 13 +- 6 files changed, 314 insertions(+), 3 deletions(-) create mode 100644 erpnext/hr/doctype/payroll_employee_detail/__init__.py create mode 100644 erpnext/hr/doctype/payroll_employee_detail/payroll_employee_detail.json create mode 100644 erpnext/hr/doctype/payroll_employee_detail/payroll_employee_detail.py diff --git a/erpnext/hr/doctype/payroll_employee_detail/__init__.py b/erpnext/hr/doctype/payroll_employee_detail/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/hr/doctype/payroll_employee_detail/payroll_employee_detail.json b/erpnext/hr/doctype/payroll_employee_detail/payroll_employee_detail.json new file mode 100644 index 0000000000..3e1683d910 --- /dev/null +++ b/erpnext/hr/doctype/payroll_employee_detail/payroll_employee_detail.json @@ -0,0 +1,194 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2017-11-30 06:07:33.477781", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "employee", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Employee", + "length": 0, + "no_copy": 0, + "options": "Employee", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "employee_name", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Employee Name", + "length": 0, + "no_copy": 0, + "options": "employee.employee_name", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_3", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "department", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Department", + "length": 0, + "no_copy": 0, + "options": "employee.department", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "designation", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Designation", + "length": 0, + "no_copy": 0, + "options": "employee.designation", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2017-11-30 06:59:34.117930", + "modified_by": "Administrator", + "module": "HR", + "name": "Payroll Employee Detail", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "read_only": 1, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/hr/doctype/payroll_employee_detail/payroll_employee_detail.py b/erpnext/hr/doctype/payroll_employee_detail/payroll_employee_detail.py new file mode 100644 index 0000000000..c52dec1df5 --- /dev/null +++ b/erpnext/hr/doctype/payroll_employee_detail/payroll_employee_detail.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class PayrollEmployeeDetail(Document): + pass diff --git a/erpnext/hr/doctype/payroll_entry/payroll_entry.js b/erpnext/hr/doctype/payroll_entry/payroll_entry.js index 7f4769c9b0..21cc2b32c7 100644 --- a/erpnext/hr/doctype/payroll_entry/payroll_entry.js +++ b/erpnext/hr/doctype/payroll_entry/payroll_entry.js @@ -121,6 +121,14 @@ let submit_salary_slip = function (frm) { return $c('runserverobj', { 'method': 'submit_salary_slips', 'docs': doc }); }; +cur_frm.cscript.get_employee_details = function (doc, cdt, cdn) { + var callback = function (r, rt) { + if (r.message) + cur_frm.refresh_field('employees'); + } + return $c('runserverobj', { 'method': 'fill_employee_details', 'docs': doc }, callback); +} + let make_bank_entry = function (frm) { var doc = frm.doc; if (doc.company && doc.start_date && doc.end_date) { diff --git a/erpnext/hr/doctype/payroll_entry/payroll_entry.json b/erpnext/hr/doctype/payroll_entry/payroll_entry.json index 77e38d140e..6cab18d33b 100644 --- a/erpnext/hr/doctype/payroll_entry/payroll_entry.json +++ b/erpnext/hr/doctype/payroll_entry/payroll_entry.json @@ -321,6 +321,96 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "get_employee_details", + "fieldtype": "Button", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Get Employee Details", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "employees", + "fieldtype": "Table", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Employees", + "length": 0, + "no_copy": 0, + "options": "Payroll Employee Detail", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_12", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_on_submit": 0, @@ -817,7 +907,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-11-30 12:11:30.985647", + "modified": "2017-11-30 06:57:23.860603", "modified_by": "Administrator", "module": "HR", "name": "Payroll Entry", diff --git a/erpnext/hr/doctype/payroll_entry/payroll_entry.py b/erpnext/hr/doctype/payroll_entry/payroll_entry.py index 627f13b6b3..765df9a939 100644 --- a/erpnext/hr/doctype/payroll_entry/payroll_entry.py +++ b/erpnext/hr/doctype/payroll_entry/payroll_entry.py @@ -39,15 +39,24 @@ class PayrollEntry(Document): cond += "and t2.parent IN %(sal_struct)s " emp_list = frappe.db.sql(""" select - t1.name + t1.name as employee, t1.employee_name, t1.department, t1.designation from `tabEmployee` t1, `tabSalary Structure Employee` t2 where t1.docstatus!=2 and t1.name = t2.employee - %s """% cond, {"sal_struct": sal_struct}) + %s """% cond, {"sal_struct": sal_struct}, as_dict=True) return emp_list + def fill_employee_details(self): + self.set('employees', []) + employees = self.get_emp_list() + if not employees: + frappe.throw(_("No employees for the mentioned criteria")) + + for d in employees: + self.append('employees', d) + def get_filter_condition(self): self.check_mandatory() From f8e7bc7c5bd4290f531106f0259222992ce881f8 Mon Sep 17 00:00:00 2001 From: Shreya Date: Fri, 1 Dec 2017 10:42:12 +0530 Subject: [PATCH 7/9] removed process payroll doctype --- erpnext/demo/user/hr.py | 34 +- .../payroll_employee_detail.json | 2 +- .../payroll_employee_detail.py | 1 - .../hr/doctype/payroll_entry/payroll_entry.js | 37 +- .../doctype/payroll_entry/payroll_entry.json | 31 +- .../hr/doctype/payroll_entry/payroll_entry.py | 10 +- .../payroll_entry/test_payroll_entry.py | 10 +- .../hr/doctype/process_payroll/__init__.py | 0 .../process_payroll/process_payroll.js | 159 ---- .../process_payroll/process_payroll.json | 843 ------------------ .../process_payroll/process_payroll.py | 453 ---------- .../process_payroll/test_process_payroll.js | 59 -- .../process_payroll/test_process_payroll.py | 195 ---- erpnext/patches.txt | 1 + erpnext/patches/v7_2/update_salary_slips.py | 2 +- .../patches/v9_2/delete_process_payroll.py | 4 + erpnext/tests/ui/tests.txt | 1 - 17 files changed, 62 insertions(+), 1780 deletions(-) delete mode 100644 erpnext/hr/doctype/process_payroll/__init__.py delete mode 100644 erpnext/hr/doctype/process_payroll/process_payroll.js delete mode 100644 erpnext/hr/doctype/process_payroll/process_payroll.json delete mode 100644 erpnext/hr/doctype/process_payroll/process_payroll.py delete mode 100644 erpnext/hr/doctype/process_payroll/test_process_payroll.js delete mode 100644 erpnext/hr/doctype/process_payroll/test_process_payroll.py create mode 100644 erpnext/patches/v9_2/delete_process_payroll.py diff --git a/erpnext/demo/user/hr.py b/erpnext/demo/user/hr.py index 1570df5963..504478a241 100644 --- a/erpnext/demo/user/hr.py +++ b/erpnext/demo/user/hr.py @@ -17,32 +17,32 @@ def work(): mark_attendance() make_leave_application() - # process payroll + # payroll entry 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.payroll_frequency = 'Monthly' + payroll_entry = frappe.new_doc("Payroll Entry") + payroll_entry.company = frappe.flags.company + payroll_entry.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") + payroll_entry.posting_date = get_last_day(getdate(frappe.flags.current_date) - datetime.timedelta(days=10)) + payroll_entry.payment_account = frappe.get_value('Account', {'account_type': 'Cash', 'company': erpnext.get_default_company(),'is_group':0}, "name") - process_payroll.set_start_end_dates() + payroll_entry.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_accural_jv_entry() - # process_payroll.make_journal_entry(reference_date=frappe.flags.current_date, + payroll_entry.salary_slip_based_on_timesheet = 0 + payroll_entry.create_salary_slips() + payroll_entry.submit_salary_slips() + payroll_entry.make_accural_jv_entry() + # payroll_entry.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_accural_jv_entry() - # process_payroll.make_journal_entry(reference_date=frappe.flags.current_date, + payroll_entry.salary_slip_based_on_timesheet = 1 + payroll_entry.create_salary_slips() + payroll_entry.submit_salary_slips() + payroll_entry.make_accural_jv_entry() + # payroll_entry.make_journal_entry(reference_date=frappe.flags.current_date, # reference_number=random_string(10)) if frappe.db.get_global('demo_hr_user'): diff --git a/erpnext/hr/doctype/payroll_employee_detail/payroll_employee_detail.json b/erpnext/hr/doctype/payroll_employee_detail/payroll_employee_detail.json index 3e1683d910..953cffae21 100644 --- a/erpnext/hr/doctype/payroll_employee_detail/payroll_employee_detail.json +++ b/erpnext/hr/doctype/payroll_employee_detail/payroll_employee_detail.json @@ -176,7 +176,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2017-11-30 06:59:34.117930", + "modified": "2017-11-30 18:25:34.967999", "modified_by": "Administrator", "module": "HR", "name": "Payroll Employee Detail", diff --git a/erpnext/hr/doctype/payroll_employee_detail/payroll_employee_detail.py b/erpnext/hr/doctype/payroll_employee_detail/payroll_employee_detail.py index c52dec1df5..aeb11fd7e2 100644 --- a/erpnext/hr/doctype/payroll_employee_detail/payroll_employee_detail.py +++ b/erpnext/hr/doctype/payroll_employee_detail/payroll_employee_detail.py @@ -3,7 +3,6 @@ # For license information, please see license.txt from __future__ import unicode_literals -import frappe from frappe.model.document import Document class PayrollEmployeeDetail(Document): diff --git a/erpnext/hr/doctype/payroll_entry/payroll_entry.js b/erpnext/hr/doctype/payroll_entry/payroll_entry.js index 21cc2b32c7..4de405268c 100644 --- a/erpnext/hr/doctype/payroll_entry/payroll_entry.js +++ b/erpnext/hr/doctype/payroll_entry/payroll_entry.js @@ -58,6 +58,23 @@ frappe.ui.form.on('Payroll Entry', { payroll_frequency: function (frm) { frm.trigger("set_start_end_dates"); + frm.set_value('employees', []); + }, + + company: function (frm) { + frm.set_value('employees', []); + }, + + department: function (frm) { + frm.set_value('employees', []); + }, + + designation: function (frm) { + frm.set_value('employees', []); + }, + + branch: function (frm) { + frm.set_value('employees', []); }, start_date: function (frm) { @@ -67,6 +84,11 @@ frappe.ui.form.on('Payroll Entry', { // reset flag in_progress = false; } + frm.set_value('employees', []); + }, + + project: function (frm) { + frm.set_value('employees', []); }, salary_slip_based_on_timesheet: function (frm) { @@ -108,12 +130,6 @@ frappe.ui.form.on('Payroll Entry', { }, }); -// Create salary slips - -cur_frm.cscript.custom_before_submit = function (doc) { - return $c('runserverobj', { 'method': 'create_salary_slips', 'docs': doc }); -}; - // Submit salary slips let submit_salary_slip = function (frm) { @@ -121,11 +137,12 @@ let submit_salary_slip = function (frm) { return $c('runserverobj', { 'method': 'submit_salary_slips', 'docs': doc }); }; -cur_frm.cscript.get_employee_details = function (doc, cdt, cdn) { - var callback = function (r, rt) { - if (r.message) +cur_frm.cscript.get_employee_details = function (doc) { + var callback = function (r) { + if (r.docs[0].employees){ cur_frm.refresh_field('employees'); - } + } + }; return $c('runserverobj', { 'method': 'fill_employee_details', 'docs': doc }, callback); } diff --git a/erpnext/hr/doctype/payroll_entry/payroll_entry.json b/erpnext/hr/doctype/payroll_entry/payroll_entry.json index 6cab18d33b..397cb13d36 100644 --- a/erpnext/hr/doctype/payroll_entry/payroll_entry.json +++ b/erpnext/hr/doctype/payroll_entry/payroll_entry.json @@ -564,35 +564,6 @@ "set_only_once": 0, "unique": 0 }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "data_19", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, { "allow_bulk_edit": 0, "allow_on_submit": 0, @@ -907,7 +878,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-11-30 06:57:23.860603", + "modified": "2017-11-30 18:33:38.967104", "modified_by": "Administrator", "module": "HR", "name": "Payroll Entry", diff --git a/erpnext/hr/doctype/payroll_entry/payroll_entry.py b/erpnext/hr/doctype/payroll_entry/payroll_entry.py index 765df9a939..55459c7b4a 100644 --- a/erpnext/hr/doctype/payroll_entry/payroll_entry.py +++ b/erpnext/hr/doctype/payroll_entry/payroll_entry.py @@ -11,6 +11,10 @@ from frappe import _ from erpnext.accounts.utils import get_fiscal_year class PayrollEntry(Document): + + def on_submit(self): + self.create_salary_slips() + def get_emp_list(self): """ Returns list of active employees based on selected criteria @@ -97,15 +101,15 @@ class PayrollEntry(Document): start_date >= %s and end_date <= %s and company = %s - """, (emp[0], self.start_date, self.end_date, self.company)): + """, (emp['employee'], self.start_date, self.end_date, self.company)): 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"), + "employee": emp['employee'], + "employee_name": frappe.get_value("Employee", {"name":emp['employee']}, "employee_name"), "company": self.company, "posting_date": self.posting_date }) diff --git a/erpnext/hr/doctype/payroll_entry/test_payroll_entry.py b/erpnext/hr/doctype/payroll_entry/test_payroll_entry.py index 2cd6dbfca3..f9d4f08da1 100644 --- a/erpnext/hr/doctype/payroll_entry/test_payroll_entry.py +++ b/erpnext/hr/doctype/payroll_entry/test_payroll_entry.py @@ -1,19 +1,15 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals - import unittest - import erpnext import frappe from dateutil.relativedelta import relativedelta from erpnext.accounts.utils import get_fiscal_year, getdate, nowdate from erpnext.hr.doctype.payroll_entry.payroll_entry import get_start_end_dates, get_end_date -class TestProcessPayroll(unittest.TestCase): +class TestPayrollEntry(unittest.TestCase): def test_payroll_entry(self): - month = "11" - fiscal_year = "_Test Fiscal Year 2016" for data in frappe.get_all('Salary Component', fields = ["name"]): if not frappe.db.get_value('Salary Component Account', @@ -132,7 +128,7 @@ def get_salary_component_account(sal_comp): sc = sal_comp.append("accounts") sc.company = company sc.default_account = create_account(company) - + def create_account(company): salary_account = frappe.db.get_value("Account", "Salary - " + frappe.db.get_value('Company', company, 'abbr')) if not salary_account: @@ -158,7 +154,7 @@ def make_payroll_entry(**args): payroll_entry.create_salary_slips() payroll_entry.submit_salary_slips() if payroll_entry.get_sal_slip_list(ss_status = 1): - r = payroll_entry.make_payment_entry() + payroll_entry.make_payment_entry() return payroll_entry diff --git a/erpnext/hr/doctype/process_payroll/__init__.py b/erpnext/hr/doctype/process_payroll/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/hr/doctype/process_payroll/process_payroll.js b/erpnext/hr/doctype/process_payroll/process_payroll.js deleted file mode 100644 index a9ad429354..0000000000 --- a/erpnext/hr/doctype/process_payroll/process_payroll.js +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -// License: GNU General Public License v3. See license.txt - -var in_progress = false; - -frappe.ui.form.on("Process Payroll", { - onload: function (frm) { - frm.doc.posting_date = frappe.datetime.nowdate(); - frm.doc.start_date = ''; - frm.doc.end_date = ''; - frm.doc.payroll_frequency = ''; - frm.toggle_reqd(['payroll_frequency'], !frm.doc.salary_slip_based_on_timesheet); - }, - - setup: function (frm) { - frm.set_query("payment_account", function () { - var account_types = ["Bank", "Cash"]; - return { - filters: { - "account_type": ["in", account_types], - "is_group": 0, - "company": frm.doc.company - } - } - }), - frm.set_query("cost_center", function () { - return { - filters: { - "is_group": 0, - company: frm.doc.company - } - } - }), - frm.set_query("project", function () { - return { - filters: { - company: frm.doc.company - } - } - }) - }, - - refresh: function (frm) { - frm.disable_save(); - }, - - payroll_frequency: function (frm) { - frm.trigger("set_start_end_dates"); - }, - - start_date: function (frm) { - if(!in_progress && frm.doc.start_date){ - frm.trigger("set_end_date"); - }else{ - // reset flag - in_progress = false - } - }, - - salary_slip_based_on_timesheet: function (frm) { - frm.toggle_reqd(['payroll_frequency'], !frm.doc.salary_slip_based_on_timesheet); - }, - - 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.posting_date - }, - callback: function (r) { - if (r.message) { - in_progress = true; - frm.set_value('start_date', r.message.start_date); - frm.set_value('end_date', r.message.end_date); - } - } - }) - } - }, - - set_end_date: function(frm){ - frappe.call({ - method: 'erpnext.hr.doctype.process_payroll.process_payroll.get_end_date', - args: { - frequency: frm.doc.payroll_frequency, - start_date: frm.doc.start_date - }, - callback: function (r) { - if (r.message) { - frm.set_value('end_date', r.message.end_date); - } - } - }) - } -}) - -cur_frm.cscript.display_activity_log = function (msg) { - if (!cur_frm.ss_html) - cur_frm.ss_html = $a(cur_frm.fields_dict['activity_log'].wrapper, 'div'); - if (msg) { - cur_frm.ss_html.innerHTML = - '

' + __("Activity Log:") + '

' + msg + '
'; - } else { - cur_frm.ss_html.innerHTML = ""; - } -} - -// Create salary slip -// ----------------------- -cur_frm.cscript.create_salary_slip = function (doc, cdt, cdn) { - cur_frm.cscript.display_activity_log(""); - var callback = function (r, rt) { - if (r.message) - cur_frm.cscript.display_activity_log(r.message); - } - return $c('runserverobj', { 'method': 'create_salary_slips', 'docs': doc }, callback); -} - -cur_frm.cscript.submit_salary_slip = function (doc, cdt, cdn) { - cur_frm.cscript.display_activity_log(""); - - frappe.confirm(__("Do you really want to Submit all Salary Slip from {0} to {1}", [doc.start_date, doc.end_date]), function () { - // clear all in locals - if (locals["Salary Slip"]) { - $.each(locals["Salary Slip"], function (name, d) { - frappe.model.remove_from_locals("Salary Slip", name); - }); - } - - var callback = function (r, rt) { - if (r.message) - cur_frm.cscript.display_activity_log(r.message); - } - - return $c('runserverobj', { 'method': 'submit_salary_slips', 'docs': doc }, callback); - }); -} - -cur_frm.cscript.make_bank_entry = function (doc, cdt, cdn) { - if (doc.company && doc.start_date && doc.end_date) { - return frappe.call({ - doc: cur_frm.doc, - method: "make_payment_entry", - callback: function (r) { - if (r.message) - var doc = frappe.model.sync(r.message)[0]; - frappe.set_route("Form", doc.doctype, doc.name); - } - }); - } else { - frappe.msgprint(__("Company, From Date and To Date is mandatory")); - } -} \ No newline at end of file diff --git a/erpnext/hr/doctype/process_payroll/process_payroll.json b/erpnext/hr/doctype/process_payroll/process_payroll.json deleted file mode 100644 index 2b847b3ea7..0000000000 --- a/erpnext/hr/doctype/process_payroll/process_payroll.json +++ /dev/null @@ -1,843 +0,0 @@ -{ - "allow_copy": 1, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2012-03-27 14:35:59", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Other", - "editable_grid": 0, - "fields": [ - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break0", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Select Employees", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break0", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0, - "width": "50%" - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "fieldname": "company", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Company", - "length": 0, - "no_copy": 0, - "options": "Company", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 1, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Today", - "fieldname": "posting_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Posting Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "depends_on": "eval:doc.salary_slip_based_on_timesheet == 0", - "fieldname": "payroll_frequency", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Payroll Frequency", - "length": 0, - "no_copy": 0, - "options": "\nMonthly\nFortnightly\nBimonthly\nWeekly\nDaily", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break1", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0, - "width": "50%" - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "branch", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Branch", - "length": 0, - "no_copy": 0, - "options": "Branch", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "department", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Department", - "length": 0, - "no_copy": 0, - "options": "Department", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "designation", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Designation", - "length": 0, - "no_copy": 0, - "options": "Designation", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_8", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "salary_slip_based_on_timesheet", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Salary Slip Based on Timesheet", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "select_payroll_period", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Select Payroll Period", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "fieldname": "start_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Start Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_11", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "fieldname": "end_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "End Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_16", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Accounts", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "cost_center", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Cost Center", - "length": 0, - "no_copy": 0, - "options": "Cost Center", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_18", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "project", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Project", - "length": 0, - "no_copy": 0, - "options": "Project", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "process_payroll", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Process Payroll", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break2", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0, - "width": "50%" - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Creates salary slip for above mentioned criteria.", - "fieldname": "create_salary_slip", - "fieldtype": "Button", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Create Salary Slip", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break3", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0, - "width": "25%" - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Submit all salary slips for the above selected criteria", - "fieldname": "submit_salary_slip", - "fieldtype": "Button", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Submit Salary Slip", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "account", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Payment Entry", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Select Payment Account to make Bank Entry", - "fieldname": "payment_account", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Payment Account", - "length": 0, - "no_copy": 0, - "options": "Account", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "description": "Create Bank Entry for the total salary paid for the above selected criteria", - "fieldname": "make_bank_entry", - "fieldtype": "Button", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Make Bank Entry", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break2", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "activity_log", - "fieldtype": "HTML", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Activity Log", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - } - ], - "hide_heading": 0, - "hide_toolbar": 1, - "icon": "fa fa-cog", - "idx": 1, - "image_view": 0, - "in_create": 0, - "in_dialog": 0, - "is_submittable": 0, - "issingle": 1, - "istable": 0, - "max_attachments": 0, - "modified": "2017-02-08 02:34:59.130475", - "modified_by": "Administrator", - "module": "HR", - "name": "Process Payroll", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 0, - "role": "HR Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 0, - "track_seen": 0 -} \ No newline at end of file diff --git a/erpnext/hr/doctype/process_payroll/process_payroll.py b/erpnext/hr/doctype/process_payroll/process_payroll.py deleted file mode 100644 index f8ac044c80..0000000000 --- a/erpnext/hr/doctype/process_payroll/process_payroll.py +++ /dev/null @@ -1,453 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe -from dateutil.relativedelta import relativedelta -from frappe.utils import cint, flt, nowdate, add_days, getdate, fmt_money, add_to_date, DATE_FORMAT -from frappe import _ -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 - and for which salary structure exists - """ - cond = self.get_filter_condition() - cond += self.get_joining_releiving_condition() - - - condition = '' - if 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 - {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 - %s """% cond, {"sal_struct": sal_struct}) - return emp_list - - def get_filter_condition(self): - self.check_mandatory() - - cond = '' - for f in ['company', 'branch', 'department', 'designation']: - if self.get(f): - cond += " and t1." + f + " = '" + self.get(f).replace("'", "\'") + "'" - - return cond - - def get_joining_releiving_condition(self): - cond = """ - and ifnull(t1.date_of_joining, '0000-00-00') <= '%(end_date)s' - and ifnull(t1.relieving_date, '2199-12-31') >= '%(start_date)s' - """ % {"start_date": self.start_date, "end_date": self.end_date} - return cond - - def check_mandatory(self): - for fieldname in ['company', 'start_date', 'end_date']: - if not self.get(fieldname): - frappe.throw(_("Please set {0}").format(self.meta.get_label(fieldname))) - - def create_salary_slips(self): - """ - Creates salary slip for selected employees if already not created - """ - self.check_permission('write') - - emp_list = self.get_emp_list() - 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 - """, (emp[0], self.start_date, self.end_date, self.company)): - 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_dict = {} - ss_dict["Employee Name"] = ss.employee_name - ss_dict["Total Pay"] = fmt_money(ss.rounded_total,currency = frappe.defaults.get_global_default("currency")) - ss_dict["Salary Slip"] = self.format_as_links(ss.name)[0] - ss_list.append(ss_dict) - return self.create_log(ss_list) - - def create_log(self, ss_list): - if not ss_list or len(ss_list) < 1: - log = "

" + _("No employee for the above selected criteria OR salary slip already created") + "

" - else: - log = frappe.render_template("templates/includes/salary_slip_log.html", - dict(ss_list=ss_list, - keys=sorted(ss_list[0].keys()), - title=_('Created Salary Slips'))) - return log - - def get_sal_slip_list(self, ss_status, as_dict=False): - """ - 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 - 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_slips(self): - """ - Submit all salary slips based on selected criteria - """ - self.check_permission('write') - jv_name = "" - ss_list = self.get_sal_slip_list(ss_status=0) - submitted_ss = [] - not_submitted_ss = [] - for ss in ss_list: - ss_obj = frappe.get_doc("Salary Slip",ss[0]) - ss_dict = {} - ss_dict["Employee Name"] = ss_obj.employee_name - ss_dict["Total Pay"] = fmt_money(ss_obj.net_pay, - currency = frappe.defaults.get_global_default("currency")) - ss_dict["Salary Slip"] = self.format_as_links(ss_obj.name)[0] - - if ss_obj.net_pay<0: - not_submitted_ss.append(ss_dict) - else: - try: - ss_obj.submit() - submitted_ss.append(ss_dict) - except frappe.ValidationError: - not_submitted_ss.append(ss_dict) - if submitted_ss: - jv_name = self.make_accural_jv_entry() - - return self.create_submit_log(submitted_ss, not_submitted_ss, jv_name) - - def create_submit_log(self, submitted_ss, not_submitted_ss, jv_name): - log = '' - if not submitted_ss and not not_submitted_ss: - log = "No salary slip found to submit for the above selected criteria" - - if submitted_ss: - log = frappe.render_template("templates/includes/salary_slip_log.html", - dict(ss_list=submitted_ss, - keys=sorted(submitted_ss[0].keys()), - title=_('Submitted Salary Slips'))) - if jv_name: - log += "" + _("Accural Journal Entry Submitted") + "\ - %s" % '
''{0}'.format(jv_name) - - if not_submitted_ss: - log += frappe.render_template("templates/includes/salary_slip_log.html", - dict(ss_list=not_submitted_ss, - keys=sorted(not_submitted_ss[0].keys()), - title=_('Not Submitted Salary Slips'))) - log += """ - Possible reasons:
\ - 1. Net pay is less than 0
- 2. Company Email Address specified in employee master is not valid.
- """ - return log - - def format_as_links(self, salary_slip): - return ['{0}'.format(salary_slip)] - - def get_loan_details(self): - """ - Get loan details from submitted salary slip based on selected criteria - """ - cond = self.get_filter_condition() - return frappe.db.sql(""" select eld.employee_loan_account, - eld.interest_income_account, eld.principal_amount, eld.interest_amount, eld.total_payment - from - `tabSalary Slip` t1, `tabSalary Slip Loan` eld - where - t1.docstatus = 1 and t1.name = eld.parent and start_date >= %s and end_date <= %s %s - """ % ('%s', '%s', cond), (self.start_date, self.end_date), as_dict=True) or [] - - def get_total_salary_amount(self): - """ - Get total salary amount from submitted salary slip based on selected criteria - """ - cond = self.get_filter_condition() - totals = frappe.db.sql(""" select sum(rounded_total) as rounded_total from `tabSalary Slip` t1 - where t1.docstatus = 1 and start_date >= %s and end_date <= %s %s - """ % ('%s', '%s', cond), (self.start_date, self.end_date), as_dict=True) - return totals and totals[0] or None - - 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 - - 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) - 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: - 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 - return account_dict - - def get_default_payroll_payable_account(self): - payroll_payable_account = frappe.db.get_value("Company", - {"company_name": self.company}, "default_payroll_payable_account") - - if not payroll_payable_account: - frappe.throw(_("Please set Default Payroll Payable Account in Company {0}") - .format(self.company)) - - return payroll_payable_account - - def make_accural_jv_entry(self): - self.check_permission('write') - earnings = self.get_salary_component_total(component_type = "earnings") or {} - deductions = self.get_salary_component_total(component_type = "deductions") or {} - default_payroll_payable_account = self.get_default_payroll_payable_account() - loan_details = self.get_loan_details() - jv_name = "" - precision = frappe.get_precision("Journal Entry Account", "debit_in_account_currency") - - if earnings or deductions: - journal_entry = frappe.new_doc('Journal Entry') - journal_entry.voucher_type = 'Journal Entry' - journal_entry.user_remark = _('Accural Journal Entry for salaries from {0} to {1}')\ - .format(self.start_date, self.end_date) - journal_entry.company = self.company - journal_entry.posting_date = nowdate() - - accounts = [] - payable_amount = 0 - - # Earnings - for acc, amount in earnings.items(): - payable_amount += flt(amount, precision) - accounts.append({ - "account": acc, - "debit_in_account_currency": flt(amount, precision), - "cost_center": self.cost_center, - "project": self.project - }) - - # Deductions - for acc, amount in deductions.items(): - payable_amount -= flt(amount, precision) - accounts.append({ - "account": acc, - "credit_in_account_currency": flt(amount, precision), - "cost_center": self.cost_center, - "project": self.project - }) - - # Employee loan - for data in loan_details: - accounts.append({ - "account": data.employee_loan_account, - "credit_in_account_currency": data.principal_amount - }) - accounts.append({ - "account": data.interest_income_account, - "credit_in_account_currency": data.interest_amount, - "cost_center": self.cost_center, - "project": self.project - }) - payable_amount -= flt(data.total_payment, precision) - - # Payable amount - accounts.append({ - "account": default_payroll_payable_account, - "credit_in_account_currency": flt(payable_amount, precision) - }) - - journal_entry.set("accounts", accounts) - journal_entry.save() - - try: - journal_entry.submit() - jv_name = journal_entry.name - self.update_salary_slip_status(jv_name = jv_name) - except Exception as e: - frappe.msgprint(e) - - return jv_name - - def make_payment_entry(self): - self.check_permission('write') - total_salary_amount = self.get_total_salary_amount() - default_payroll_payable_account = self.get_default_payroll_payable_account() - precision = frappe.get_precision("Journal Entry Account", "debit_in_account_currency") - - if total_salary_amount and total_salary_amount.rounded_total: - journal_entry = frappe.new_doc('Journal Entry') - journal_entry.voucher_type = 'Bank Entry' - journal_entry.user_remark = _('Payment of salary from {0} to {1}')\ - .format(self.start_date, self.end_date) - journal_entry.company = self.company - journal_entry.posting_date = nowdate() - - payment_amount = flt(total_salary_amount.rounded_total, precision) - - journal_entry.set("accounts", [ - { - "account": self.payment_account, - "credit_in_account_currency": payment_amount - }, - { - "account": default_payroll_payable_account, - "debit_in_account_currency": payment_amount - } - ]) - return journal_entry.as_dict() - else: - frappe.msgprint( - _("There are no submitted Salary Slips to process."), - title="Error", indicator="red" - ) - - 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) - - def set_start_end_dates(self): - self.update(get_start_end_dates(self.payroll_frequency, - self.start_date or self.posting_date, self.company)) - -@frappe.whitelist() -def get_start_end_dates(payroll_frequency, start_date=None, company=None): - '''Returns dict of start and end dates for given payroll frequency based on start_date''' - - if payroll_frequency == "Monthly" or payroll_frequency == "Bimonthly" or payroll_frequency == "": - fiscal_year = get_fiscal_year(start_date, company=company)[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_frequency_kwargs(frequency_name): - frequency_dict = { - 'monthly': {'months': 1}, - 'fortnightly': {'days': 14}, - 'weekly': {'days': 7}, - 'daily': {'days': 1} - } - return frequency_dict.get(frequency_name) - -@frappe.whitelist() -def get_end_date(start_date, frequency): - start_date = getdate(start_date) - frequency = frequency.lower() if frequency else 'monthly' - kwargs = get_frequency_kwargs(frequency) if frequency != 'bimonthly' else get_frequency_kwargs('monthly') - - # weekly, fortnightly and daily intervals have fixed days so no problems - end_date = add_to_date(start_date, **kwargs) - relativedelta(days=1) - if frequency != 'bimonthly': - return dict(end_date=end_date.strftime(DATE_FORMAT)) - - else: - return dict(end_date='') - -def get_month_details(year, month): - ysd = frappe.db.get_value("Fiscal Year", year, "year_start_date") - if ysd: - from dateutil.relativedelta import relativedelta - import calendar, datetime - diff_mnt = cint(month)-cint(ysd.month) - if diff_mnt<0: - diff_mnt = 12-int(ysd.month)+cint(month) - msd = ysd + relativedelta(months=diff_mnt) # month start date - month_days = cint(calendar.monthrange(cint(msd.year) ,cint(month))[1]) # days in month - mid_start = datetime.date(msd.year, cint(month), 16) # month mid start date - mid_end = datetime.date(msd.year, cint(month), 15) # month mid end date - med = datetime.date(msd.year, cint(month), month_days) # month end date - return frappe._dict({ - 'year': msd.year, - 'month_start_date': msd, - 'month_end_date': med, - 'month_mid_start_date': mid_start, - 'month_mid_end_date': mid_end, - 'month_days': month_days - }) - else: - frappe.throw(_("Fiscal Year {0} not found").format(year)) diff --git a/erpnext/hr/doctype/process_payroll/test_process_payroll.js b/erpnext/hr/doctype/process_payroll/test_process_payroll.js deleted file mode 100644 index bc61150463..0000000000 --- a/erpnext/hr/doctype/process_payroll/test_process_payroll.js +++ /dev/null @@ -1,59 +0,0 @@ -QUnit.module('hr'); - -QUnit.test("Test: Process Payroll [HR]", function (assert) { - assert.expect(5); - let done = assert.async(); - let net_pay; - - let check_amounts = (employee_name,net_amt,gross_amt) => { - frappe.run_serially([ - // Retrieving the actual amount from salary slip - () => frappe.db.get_value('Salary Slip', {'employee_name': employee_name}, 'net_pay'), - (r) => { - net_pay=r.message.net_pay; - }, - () => frappe.db.get_value('Salary Slip', {'employee_name': employee_name}, 'gross_pay'), - - // Checking if amounts are correctly calculated - (r) => { - assert.ok(net_pay==net_amt, - 'Net Pay is correctly calculated for '+employee_name); - assert.ok(r.message.gross_pay==gross_amt, - 'Gross Pay is correctly calculated for '+employee_name); - }, - ]); - }; - frappe.run_serially([ - - // Deleting the already generated Salary Slips for employees - () => frappe.set_route('List','Salary Slip'), - () => frappe.timeout(2), - () => { $('input.list-row-checkbox').click();}, - () => frappe.click_button('Delete'), - () => frappe.click_button('Yes'), - () => frappe.timeout(2), - () => assert.ok(cur_list.data.length==0,"Salary Slips successfully deleted"), - () => frappe.timeout(3), - - - // Creating Process Payroll for specific company - () => frappe.set_route('Form','Process Payroll'), - () => { - cur_frm.set_value('company','For Testing'), - frappe.timeout(1), - cur_frm.set_value('payroll_frequency','Monthly'), - cur_frm.set_value('start_date','2017-08-01'), - frappe.timeout(1), - cur_frm.set_value('end_date','2017-08-31'), - cur_frm.set_value('cost_center','Main-TC'), - frappe.timeout(1), - frappe.click_button('Create Salary Slip'); - }, - () => frappe.timeout(3), - () => check_amounts('Test Employee 1','19200','24000'), - () => frappe.timeout(3), - () => check_amounts('Test Employee 3','23040','28800'), - () => frappe.timeout(4), - () => done() - ]); -}); diff --git a/erpnext/hr/doctype/process_payroll/test_process_payroll.py b/erpnext/hr/doctype/process_payroll/test_process_payroll.py deleted file mode 100644 index 91b60b4e68..0000000000 --- a/erpnext/hr/doctype/process_payroll/test_process_payroll.py +++ /dev/null @@ -1,195 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals - -import unittest - -import erpnext -import frappe -from dateutil.relativedelta import relativedelta -from erpnext.accounts.utils import get_fiscal_year, getdate, nowdate -from erpnext.hr.doctype.process_payroll.process_payroll import get_start_end_dates, get_end_date - -class TestProcessPayroll(unittest.TestCase): - def test_process_payroll(self): - month = "11" - fiscal_year = "_Test Fiscal Year 2016" - - for data in frappe.get_all('Salary Component', fields = ["name"]): - if not frappe.db.get_value('Salary Component Account', - {'parent': data.name, 'company': erpnext.get_default_company()}, 'name'): - get_salary_component_account(data.name) - - if not frappe.db.get_value("Salary Slip", {"start_date": "2016-11-01", "end_date": "2016-11-30"}): - make_process_payroll() - - def test_get_end_date(self): - self.assertEqual(get_end_date('2017-01-01', 'monthly'), {'end_date': '2017-01-31'}) - self.assertEqual(get_end_date('2017-02-01', 'monthly'), {'end_date': '2017-02-28'}) - self.assertEqual(get_end_date('2017-02-01', 'fortnightly'), {'end_date': '2017-02-14'}) - self.assertEqual(get_end_date('2017-02-01', 'bimonthly'), {'end_date': ''}) - self.assertEqual(get_end_date('2017-01-01', 'bimonthly'), {'end_date': ''}) - self.assertEqual(get_end_date('2020-02-15', 'bimonthly'), {'end_date': ''}) - self.assertEqual(get_end_date('2017-02-15', 'monthly'), {'end_date': '2017-03-14'}) - self.assertEqual(get_end_date('2017-02-15', 'daily'), {'end_date': '2017-02-15'}) - - def test_employee_loan(self): - from erpnext.hr.doctype.salary_structure.test_salary_structure import (make_employee, - make_salary_structure) - from erpnext.hr.doctype.employee_loan.test_employee_loan import create_employee_loan - - branch = "Test Employee Branch" - employee = make_employee("test_employee@loan.com") - company = erpnext.get_default_company() - holiday_list = make_holiday("test holiday for loan") - - if not frappe.db.exists('Salary Component', 'Basic Salary'): - frappe.get_doc({ - 'doctype': 'Salary Component', - 'salary_component': 'Basic Salary', - 'salary_component_abbr': 'BS', - 'type': 'Earning', - 'accounts': [{ - 'company': company, - 'default_account': frappe.db.get_value('Account', - {'company': company, 'root_type': 'Expense', 'account_type': ''}, 'name') - }] - }).insert() - - if not frappe.db.get_value('Salary Component Account', - {'parent': 'Basic Salary', 'company': company}): - salary_component = frappe.get_doc('Salary Component', 'Basic Salary') - salary_component.append('accounts', { - 'company': company, - 'default_account': 'Salary - WP' - }) - - company_doc = frappe.get_doc('Company', company) - if not company_doc.default_payroll_payable_account: - company_doc.default_payroll_payable_account = frappe.db.get_value('Account', - {'company': company, 'root_type': 'Liability', 'account_type': ''}, 'name') - company_doc.save() - - if not frappe.db.exists('Branch', branch): - frappe.get_doc({ - 'doctype': 'Branch', - 'branch': branch - }).insert() - - employee_doc = frappe.get_doc('Employee', employee) - employee_doc.branch = branch - employee_doc.holiday_list = holiday_list - employee_doc.save() - - employee_loan = create_employee_loan(employee, - "Personal Loan", 280000, "Repay Over Number of Periods", 20) - employee_loan.repay_from_salary = 1 - employee_loan.submit() - - salary_strcture = "Test Salary Structure for Loan" - if not frappe.db.exists('Salary Structure', salary_strcture): - salary_strcture = make_salary_structure(salary_strcture, [{ - 'employee': employee, - 'from_date': '2017-01-01', - 'base': 30000 - }]) - - salary_strcture = frappe.get_doc('Salary Structure', salary_strcture) - salary_strcture.set('earnings', [{ - 'salary_component': 'Basic Salary', - 'abbr': 'BS', - 'amount_based_on_formula':1, - 'formula': 'base*.5' - }]) - salary_strcture.save() - - dates = get_start_end_dates('Monthly', nowdate()) - make_process_payroll(start_date=dates.start_date, - end_date=dates.end_date, branch=branch) - - name = frappe.db.get_value('Salary Slip', - {'posting_date': nowdate(), 'employee': employee}, 'name') - - salary_slip = frappe.get_doc('Salary Slip', name) - for row in salary_slip.loans: - if row.employee_loan == employee_loan.name: - interest_amount = (280000 * 8.4)/(12*100) - principal_amount = employee_loan.monthly_repayment_amount - interest_amount - self.assertEqual(row.interest_amount, interest_amount) - self.assertEqual(row.principal_amount, principal_amount) - self.assertEqual(row.total_payment, - interest_amount + principal_amount) - - if salary_slip.docstatus == 0: - frappe.delete_doc('Salary Slip', name) - - employee_loan.cancel() - frappe.delete_doc('Employee Loan', employee_loan.name) - -def get_salary_component_account(sal_comp): - company = erpnext.get_default_company() - sal_comp = frappe.get_doc("Salary Component", sal_comp) - sc = sal_comp.append("accounts") - sc.company = company - sc.default_account = create_account(company) - -def create_account(company): - salary_account = frappe.db.get_value("Account", "Salary - " + frappe.db.get_value('Company', company, 'abbr')) - if not salary_account: - frappe.get_doc({ - "doctype": "Account", - "account_name": "Salary", - "parent_account": "Indirect Expenses - " + frappe.db.get_value('Company', company, 'abbr'), - "company": company - }).insert() - return salary_account - -def make_process_payroll(**args): - args = frappe._dict(args) - - process_payroll = frappe.get_doc("Process Payroll", "Process Payroll") - process_payroll.company = erpnext.get_default_company() - process_payroll.start_date = args.start_date or "2016-11-01" - process_payroll.end_date = args.end_date or "2016-11-30" - process_payroll.payment_account = get_payment_account() - process_payroll.posting_date = nowdate() - process_payroll.payroll_frequency = "Monthly" - process_payroll.branch = args.branch or None - process_payroll.create_salary_slips() - process_payroll.submit_salary_slips() - if process_payroll.get_sal_slip_list(ss_status = 1): - r = process_payroll.make_payment_entry() - - return process_payroll - -def get_payment_account(): - return frappe.get_value('Account', - {'account_type': 'Cash', 'company': erpnext.get_default_company(),'is_group':0}, "name") - -def make_holiday(holiday_list_name): - if not frappe.db.exists('Holiday List', holiday_list_name): - current_fiscal_year = get_fiscal_year(nowdate(), as_dict=True) - dt = getdate(nowdate()) - - new_year = dt + relativedelta(month=01, day=01, year=dt.year) - republic_day = dt + relativedelta(month=01, day=26, year=dt.year) - test_holiday = dt + relativedelta(month=02, day=02, year=dt.year) - - frappe.get_doc({ - 'doctype': 'Holiday List', - 'from_date': current_fiscal_year.year_start_date, - 'to_date': current_fiscal_year.year_end_date, - 'holiday_list_name': holiday_list_name, - 'holidays': [{ - 'holiday_date': new_year, - 'description': 'New Year' - }, { - 'holiday_date': republic_day, - 'description': 'Republic Day' - }, { - 'holiday_date': test_holiday, - 'description': 'Test Holiday' - }] - }).insert() - - return holiday_list_name diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 54f4dfd13f..1755ec0329 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -475,3 +475,4 @@ erpnext.patches.v9_2.remove_company_from_patient erpnext.patches.v9_2.set_item_name_in_production_order erpnext.patches.v10_0.update_lft_rgt_for_employee erpnext.patches.v9_2.rename_net_weight_in_item_master +erpnext.patches.v9_2.delete_process_payroll diff --git a/erpnext/patches/v7_2/update_salary_slips.py b/erpnext/patches/v7_2/update_salary_slips.py index b7a405043e..22bb1d82dc 100644 --- a/erpnext/patches/v7_2/update_salary_slips.py +++ b/erpnext/patches/v7_2/update_salary_slips.py @@ -1,5 +1,5 @@ import frappe -from erpnext.hr.doctype.process_payroll.process_payroll import get_month_details +from erpnext.hr.doctype.payroll_entry.payroll_entry import get_month_details from frappe.utils import cint def execute(): diff --git a/erpnext/patches/v9_2/delete_process_payroll.py b/erpnext/patches/v9_2/delete_process_payroll.py new file mode 100644 index 0000000000..e9e1b99c06 --- /dev/null +++ b/erpnext/patches/v9_2/delete_process_payroll.py @@ -0,0 +1,4 @@ +import frappe + +def execute(): + frappe.delete_doc("DocType", "Process Payroll") diff --git a/erpnext/tests/ui/tests.txt b/erpnext/tests/ui/tests.txt index b760e98006..9040b12054 100644 --- a/erpnext/tests/ui/tests.txt +++ b/erpnext/tests/ui/tests.txt @@ -64,7 +64,6 @@ erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_receipt.js erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_transfer.js erpnext/hr/doctype/salary_structure/test_salary_structure.js erpnext/hr/doctype/salary_slip/test_salary_slip.js -erpnext/hr/doctype/process_payroll/test_process_payroll.js erpnext/hr/doctype/job_opening/test_job_opening.js erpnext/hr/doctype/job_applicant/test_job_applicant.js erpnext/hr/doctype/offer_letter/test_offer_letter.js From e570e4a0497d88ae555b04305bc5d2a4d74cd185 Mon Sep 17 00:00:00 2001 From: Shreya Date: Fri, 1 Dec 2017 12:22:22 +0530 Subject: [PATCH 8/9] fixed a test --- .../hr/doctype/payroll_entry/payroll_entry.js | 2 +- .../payroll_entry/test_payroll_entry.py | 2 +- .../doctype/salary_slip/test_salary_slip.py | 2 +- .../stock/doctype/item_price/item_price.json | 26 ++++++++++++++----- 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/erpnext/hr/doctype/payroll_entry/payroll_entry.js b/erpnext/hr/doctype/payroll_entry/payroll_entry.js index 4de405268c..a26283d9a6 100644 --- a/erpnext/hr/doctype/payroll_entry/payroll_entry.js +++ b/erpnext/hr/doctype/payroll_entry/payroll_entry.js @@ -144,7 +144,7 @@ cur_frm.cscript.get_employee_details = function (doc) { } }; return $c('runserverobj', { 'method': 'fill_employee_details', 'docs': doc }, callback); -} +}; let make_bank_entry = function (frm) { var doc = frm.doc; diff --git a/erpnext/hr/doctype/payroll_entry/test_payroll_entry.py b/erpnext/hr/doctype/payroll_entry/test_payroll_entry.py index f9d4f08da1..21b0d58a22 100644 --- a/erpnext/hr/doctype/payroll_entry/test_payroll_entry.py +++ b/erpnext/hr/doctype/payroll_entry/test_payroll_entry.py @@ -9,7 +9,7 @@ from erpnext.accounts.utils import get_fiscal_year, getdate, nowdate from erpnext.hr.doctype.payroll_entry.payroll_entry import get_start_end_dates, get_end_date class TestPayrollEntry(unittest.TestCase): - def test_payroll_entry(self): + def test_payroll_entry(self): # pylint: disable=no-self-use for data in frappe.get_all('Salary Component', fields = ["name"]): if not frappe.db.get_value('Salary Component Account', diff --git a/erpnext/hr/doctype/salary_slip/test_salary_slip.py b/erpnext/hr/doctype/salary_slip/test_salary_slip.py index 493a8251c9..0b240f0095 100644 --- a/erpnext/hr/doctype/salary_slip/test_salary_slip.py +++ b/erpnext/hr/doctype/salary_slip/test_salary_slip.py @@ -9,7 +9,7 @@ import calendar from erpnext.accounts.utils import get_fiscal_year from frappe.utils import getdate, nowdate, add_days, add_months, flt from erpnext.hr.doctype.salary_structure.salary_structure import make_salary_slip -from erpnext.hr.doctype.payroll_entry.payroll_entry import get_salary_component_account +from erpnext.hr.doctype.payroll_entry.test_payroll_entry import get_salary_component_account from erpnext.hr.doctype.payroll_entry.payroll_entry import get_month_details class TestSalarySlip(unittest.TestCase): diff --git a/erpnext/stock/doctype/item_price/item_price.json b/erpnext/stock/doctype/item_price/item_price.json index fee229f761..fdc2df2f10 100644 --- a/erpnext/stock/doctype/item_price/item_price.json +++ b/erpnext/stock/doctype/item_price/item_price.json @@ -1,5 +1,6 @@ { - "allow_copy": 0, + "allow_copy": 0, + "allow_guest_to_view": 0, "allow_import": 1, "allow_rename": 0, "autoname": "ITEM-PRICE-.#####", @@ -14,6 +15,7 @@ "engine": "InnoDB", "fields": [ { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -43,6 +45,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -72,6 +75,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -100,6 +104,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -128,6 +133,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -156,6 +162,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -165,7 +172,8 @@ "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, - "in_filter": 0, + "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, "label": "", @@ -184,6 +192,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 1, "collapsible": 0, @@ -213,6 +222,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -244,6 +254,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -275,6 +286,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -302,6 +314,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -330,6 +343,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -357,19 +371,19 @@ "set_only_once": 0, "unique": 0 } - ], + ], + "has_web_view": 0, "hide_heading": 0, "hide_toolbar": 0, "icon": "fa fa-flag", "idx": 1, "image_view": 0, "in_create": 0, - "in_dialog": 0, - "is_submittable": 0, + "is_submittable": 0, "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-09-28 03:56:20.814993", + "modified": "2017-12-01 13:03:53.397382", "modified_by": "Administrator", "module": "Stock", "name": "Item Price", From 97b8a7b237afe68f2727bdff581b9c7d166c70dd Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 6 Dec 2017 19:55:57 +0530 Subject: [PATCH 9/9] Minor fixes in payroll entry --- .../journal_entry_account/journal_entry_account.json | 4 ++-- erpnext/hr/doctype/payroll_entry/payroll_entry.json | 4 ++-- erpnext/hr/doctype/payroll_entry/payroll_entry.py | 9 ++++++--- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json index 48d5ed2ca9..2954f72fdc 100644 --- a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json +++ b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json @@ -618,7 +618,7 @@ "label": "Reference Type", "length": 0, "no_copy": 0, - "options": "\nSales Invoice\nPurchase Invoice\nJournal Entry\nSales Order\nPurchase Order\nExpense Claim\nAsset\nEmployee Loan", + "options": "\nSales Invoice\nPurchase Invoice\nJournal Entry\nSales Order\nPurchase Order\nExpense Claim\nAsset\nEmployee Loan\nPayroll Entry", "permlevel": 0, "precision": "", "print_hide": 0, @@ -827,7 +827,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2017-08-30 08:44:54.295493", + "modified": "2017-12-06 19:54:19.851534", "modified_by": "Administrator", "module": "Accounts", "name": "Journal Entry Account", diff --git a/erpnext/hr/doctype/payroll_entry/payroll_entry.json b/erpnext/hr/doctype/payroll_entry/payroll_entry.json index 397cb13d36..5cd1e53a6d 100644 --- a/erpnext/hr/doctype/payroll_entry/payroll_entry.json +++ b/erpnext/hr/doctype/payroll_entry/payroll_entry.json @@ -747,7 +747,7 @@ }, { "allow_bulk_edit": 0, - "allow_on_submit": 0, + "allow_on_submit": 1, "bold": 0, "collapsible": 0, "columns": 0, @@ -878,7 +878,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-11-30 18:33:38.967104", + "modified": "2017-12-06 19:47:03.235021", "modified_by": "Administrator", "module": "HR", "name": "Payroll Entry", diff --git a/erpnext/hr/doctype/payroll_entry/payroll_entry.py b/erpnext/hr/doctype/payroll_entry/payroll_entry.py index 55459c7b4a..db423399dc 100644 --- a/erpnext/hr/doctype/payroll_entry/payroll_entry.py +++ b/erpnext/hr/doctype/payroll_entry/payroll_entry.py @@ -165,7 +165,8 @@ class PayrollEntry(Document): not_submitted_ss.append(ss_dict) if submitted_ss: jv_name = self.make_accural_jv_entry() - frappe.msgprint(_("Salary Slip submitted from {0} to {1}").format(ss_obj.start_date, ss_obj.end_date)) + frappe.msgprint(_("Salary Slip submitted for period from {0} to {1}") + .format(ss_obj.start_date, ss_obj.end_date)) return create_submit_log(submitted_ss, not_submitted_ss, jv_name) @@ -331,7 +332,9 @@ class PayrollEntry(Document): }, { "account": default_payroll_payable_account, - "debit_in_account_currency": payment_amount + "debit_in_account_currency": payment_amount, + "reference_type": self.doctype, + "reference_name": self.name } ]) return journal_entry.as_dict() @@ -445,7 +448,7 @@ def create_submit_log(submitted_ss, not_submitted_ss, jv_name): frappe.msgprint("No salary slip found to submit for the above selected criteria OR salary slip already submitted") if not_submitted_ss: - frappe.msgprint("Not submitted Salary Slip
\ + frappe.msgprint("Could not submit any Salary Slip
\ Possible reasons:
\ 1. Net pay is less than 0.
\ 2. Company Email Address specified in employee master is not valid.
") \ No newline at end of file