From 7b2870431e2f76ca2ba281c18a9df644fe553a39 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 30 Oct 2017 12:41:34 +0530 Subject: [PATCH] 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 00000000000..e69de29bb2d 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 00000000000..5de82289345 --- /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 00000000000..136f35ea940 --- /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 00000000000..ef9e4ae6d62 --- /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 00000000000..f1b82e0cc36 --- /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 00000000000..47aba56a5c5 --- /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