Payroll entry ux improvements and processing via background jobs

This commit is contained in:
Nabin Hait 2018-07-30 16:31:34 +05:30
parent 04d5412754
commit 4f135c9805
7 changed files with 413 additions and 281 deletions

View File

@ -46,39 +46,6 @@
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "company",
"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": "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": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
@ -153,28 +120,29 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "salary_component",
"fetch_from": "employee.department",
"fieldname": "department",
"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": 1,
"label": "Salary Component",
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Department",
"length": 0,
"no_copy": 0,
"options": "Salary Component",
"options": "Department",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 1,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
@ -186,29 +154,27 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "salary_component.type",
"fieldname": "type",
"fieldtype": "Data",
"fieldname": "payroll_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_list_view": 1,
"in_standard_filter": 0,
"label": "Type",
"label": "Payroll Date",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"reqd": 1,
"search_index": 1,
"set_only_once": 0,
"translatable": 0,
"unique": 0
@ -251,18 +217,19 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "payroll_date",
"fieldtype": "Date",
"fieldname": "salary_component",
"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": "Payroll Date",
"in_standard_filter": 1,
"label": "Salary Component",
"length": 0,
"no_copy": 0,
"options": "Salary Component",
"permlevel": 0,
"precision": "",
"print_hide": 0,
@ -315,8 +282,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "employee.department",
"fieldname": "department",
"fieldname": "company",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
@ -325,18 +291,18 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Department",
"label": "Company",
"length": 0,
"no_copy": 0,
"options": "Department",
"options": "Company",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
@ -375,6 +341,72 @@
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "salary_component.type",
"fieldname": "type",
"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,
"label": "Salary Component Type",
"length": 0,
"no_copy": 0,
"options": "",
"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,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "overwrite_salary_structure_amount",
"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": "Overwrite Salary Structure Amount",
"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,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
@ -418,7 +450,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-07-28 17:50:25.725444",
"modified": "2018-07-30 16:02:01.538750",
"modified_by": "Administrator",
"module": "HR",
"name": "Additional Salary",
@ -474,4 +506,4 @@
"track_changes": 1,
"track_seen": 0,
"track_views": 0
}
}

View File

@ -37,11 +37,12 @@ class AdditionalSalary(Document):
@frappe.whitelist()
def get_additional_salary_component(employee, start_date, end_date):
additional_components = frappe.db.sql("""
select salary_component, sum(amount) as amount from `tabAdditional Salary`
select salary_component, sum(amount) as amount, overwrite_salary_structure_amount from `tabAdditional Salary`
where employee=%(employee)s
and docstatus = 1
and payroll_date between %(from_date)s and %(to_date)s
group by salary_component
group by salary_component, overwrite_salary_structure_amount
order by salary_component, overwrite_salary_structure_amount
""", {
'employee': employee,
'from_date': start_date,
@ -58,6 +59,7 @@ def get_additional_salary_component(employee, start_date, end_date):
additional_components_list.append({
'amount': d.amount,
'type': component.type,
'struct_row': struct_row
'struct_row': struct_row,
'overwrite': d.overwrite_salary_structure_amount
})
return additional_components_list

View File

@ -20,48 +20,61 @@ frappe.ui.form.on('Payroll Entry', {
},
refresh: function(frm) {
if (frm.doc.docstatus == 0) {
if(!frm.is_new()) {
frm.page.clear_primary_action();
frm.add_custom_button(__("Get Employees"),
function() {
frm.events.get_employee_details(frm);
}
).toggleClass('btn-primary', !(frm.doc.employees || []).length);
}
if ((frm.doc.employees || []).length) {
frm.page.set_primary_action(__('Create Salary Slips'), () => {
frm.save('Submit');
});
}
}
if (frm.doc.docstatus == 1) {
if (frm.custom_buttons) frm.clear_custom_buttons();
frm.events.add_context_buttons(frm);
}
},
add_context_buttons: function(frm) {
frappe.call({
method: 'erpnext.hr.doctype.payroll_entry.payroll_entry.payroll_entry_has_created_slips',
args: {
'name': frm.doc.name
},
get_employee_details: function (frm) {
return frappe.call({
doc: frm.doc,
method: 'fill_employee_details',
callback: function(r) {
if(r.message) {
frm.events.add_salary_slip_buttons(frm, r.message);
if(r.message.submitted){
frm.events.add_bank_entry_button(frm);
if (r.docs[0].employees){
frm.save();
frm.refresh();
if(r.docs[0].validate_attendance){
render_employee_attendance(frm, r.message);
}
}
}
});
})
},
add_salary_slip_buttons: function(frm, slip_status) {
if (!slip_status.draft && !slip_status.submitted) {
return;
} else {
frm.add_custom_button(__("View Salary Slips"),
function() {
frappe.set_route(
'List', 'Salary Slip', {posting_date: frm.doc.posting_date}
);
}
);
}
create_salary_slips: function(frm) {
frm.call({
doc: frm.doc,
method: "create_salary_slips",
callback: function(r) {
frm.refresh();
frm.toolbar.refresh();
}
})
},
if (slip_status.draft) {
frm.add_custom_button(__("Submit Salary Slip"),
function() {
submit_salary_slip(frm);
}
).addClass("btn-primary");
add_context_buttons: function(frm) {
if(frm.doc.salary_slips_submitted) {
frm.events.add_bank_entry_button(frm);
} else if(frm.doc.salary_slips_created) {
frm.add_custom_button(__("Submit Salary Slip"), function() {
submit_salary_slip(frm);
}).addClass("btn-primary");
}
},
@ -73,13 +86,9 @@ frappe.ui.form.on('Payroll Entry', {
},
callback: function(r) {
if (r.message && !r.message.submitted) {
frm.add_custom_button("Bank Entry",
function() {
make_bank_entry(frm);
},
__('Make')
);
frm.page.set_inner_btn_group_as_primary(__('Make'));
frm.add_custom_button("Make Bank Entry", function() {
make_bank_entry(frm);
}).addClass("btn-primary");
}
}
});
@ -115,23 +124,23 @@ frappe.ui.form.on('Payroll Entry', {
payroll_frequency: function (frm) {
frm.trigger("set_start_end_dates");
frm.set_value('employees', []);
frm.events.clear_employee_table(frm);
},
company: function (frm) {
frm.set_value('employees', []);
frm.events.clear_employee_table(frm);
},
department: function (frm) {
frm.set_value('employees', []);
frm.events.clear_employee_table(frm);
},
designation: function (frm) {
frm.set_value('employees', []);
frm.events.clear_employee_table(frm);
},
branch: function (frm) {
frm.set_value('employees', []);
frm.events.clear_employee_table(frm);
},
start_date: function (frm) {
@ -141,11 +150,11 @@ frappe.ui.form.on('Payroll Entry', {
// reset flag
in_progress = false;
}
frm.set_value('employees', []);
frm.events.clear_employee_table(frm);
},
project: function (frm) {
frm.set_value('employees', []);
frm.events.clear_employee_table(frm);
},
salary_slip_based_on_timesheet: function (frm) {
@ -201,7 +210,12 @@ frappe.ui.form.on('Payroll Entry', {
}else{
frm.fields_dict.attendance_detail_html.html("");
}
}
},
clear_employee_table: function (frm) {
frm.clear_table('employees');
frm.refresh();
},
});
// Submit salary slips
@ -227,18 +241,6 @@ const submit_salary_slip = function (frm) {
);
};
cur_frm.cscript.get_employee_details = function (doc) {
var callback = function (r) {
if (r.docs[0].employees){
cur_frm.refresh_field('employees');
if(r.docs[0].validate_attendance){
render_employee_attendance(cur_frm, r.message);
}
}
};
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 && doc.payment_account) {
@ -247,7 +249,7 @@ let make_bank_entry = function (frm) {
method: "make_payment_entry",
callback: function() {
frappe.set_route(
'List', 'Journal Entry', {posting_date: frm.doc.posting_date}
'List', 'Journal Entry', {"Journal Entry Account.reference_name": frm.doc.name}
);
},
freeze: true,

View File

@ -77,40 +77,6 @@
"unique": 0,
"width": "50%"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 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,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
@ -211,6 +177,73 @@
"unique": 0,
"width": "50%"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 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,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": "",
"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,
"label": "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,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
@ -277,6 +310,37 @@
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_10",
"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,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
@ -317,8 +381,8 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_8",
"fieldtype": "Section Break",
"fieldname": "number_of_employees",
"fieldtype": "Int",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@ -326,13 +390,14 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Number Of Employees",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
@ -348,8 +413,8 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "get_employee_details",
"fieldtype": "Button",
"fieldname": "sec_break20",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@ -357,7 +422,6 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Get Employee Details",
"length": 0,
"no_copy": 0,
"permlevel": 0,
@ -389,7 +453,7 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Employees",
"label": "Employee Details",
"length": 0,
"no_copy": 0,
"options": "Payroll Employee Detail",
@ -797,6 +861,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "company.cost_center",
"fieldname": "cost_center",
"fieldtype": "Link",
"hidden": 0,
@ -1016,38 +1081,6 @@
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 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,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
@ -1079,6 +1112,70 @@
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "salary_slips_created",
"fieldtype": "Check",
"hidden": 1,
"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 Slips Created",
"length": 0,
"no_copy": 0,
"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,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "salary_slips_submitted",
"fieldtype": "Check",
"hidden": 1,
"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 Slips Submitted",
"length": 0,
"no_copy": 0,
"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,
"translatable": 0,
"unique": 0
}
],
"has_web_view": 0,
@ -1092,7 +1189,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-06-28 13:55:48.295327",
"modified": "2018-07-30 14:57:37.601430",
"modified_by": "Administrator",
"module": "HR",
"name": "Payroll Entry",
@ -1126,5 +1223,6 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 0,
"track_seen": 0
"track_seen": 0,
"track_views": 0
}

View File

@ -66,6 +66,7 @@ class PayrollEntry(Document):
for d in employees:
self.append('employees', d)
self.number_of_employees = len(employees)
if self.validate_attendance:
return self.validate_employee_attendance()
@ -108,9 +109,9 @@ class PayrollEntry(Document):
"posting_date": self.posting_date,
"deduct_tax_for_unclaimed_employee_benefits": self.deduct_tax_for_unclaimed_employee_benefits,
"deduct_tax_for_unsubmitted_tax_exemption_proof": self.deduct_tax_for_unsubmitted_tax_exemption_proof,
"payroll_entry": self.payroll_entry
"payroll_entry": self.name
})
if len(emp_list) > 50:
if len(emp_list) > 30:
frappe.enqueue(create_salary_slips_for_employees, timeout=600, employees=emp_list, args=args)
else:
create_salary_slips_for_employees(emp_list, args, publish_progress=False)
@ -129,41 +130,12 @@ class PayrollEntry(Document):
return ss_list
def submit_salary_slips(self):
"""
Submit all salary slips based on selected criteria
"""
self.check_permission('write')
ss_list = self.get_sal_slip_list(ss_status=0)
submitted_ss = []
not_submitted_ss = []
frappe.flags.via_payroll_entry = True
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"] = 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_obj)
except frappe.ValidationError:
not_submitted_ss.append(ss_dict)
if submitted_ss:
self.make_accrual_jv_entry()
frappe.msgprint(_("Salary Slip submitted for period from {0} to {1}")
.format(ss_obj.start_date, ss_obj.end_date))
self.email_salary_slip(submitted_ss)
return create_submit_log(submitted_ss, not_submitted_ss)
if len(ss_list) > 30:
frappe.enqueue(submit_salary_slips_for_employees, timeout=600, payroll_entry=self, salary_slips=ss_list)
else:
submit_salary_slips_for_employees(self, ss_list, publish_progress=False)
def email_salary_slip(self, submitted_ss):
if frappe.db.get_single_value("HR Settings", "email_salary_slip_to_employee"):
@ -490,49 +462,6 @@ def get_month_details(year, month):
else:
frappe.throw(_("Fiscal Year {0} not found").format(year))
def format_as_links(salary_slip):
return ['<a href="#Form/Salary Slip/{0}">{0}</a>'.format(salary_slip)]
def create_submit_log(submitted_ss, not_submitted_ss):
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(_("Could not submit some Salary Slips <br>\
Possible reasons: <br>\
1. Net pay is less than 0. <br>\
2. Company Email Address specified in employee master is not valid. <br>"))
def get_salary_slip_list(name, docstatus, as_dict=0):
payroll_entry = frappe.get_doc('Payroll Entry', name)
salary_slip_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",
(docstatus, payroll_entry.start_date, payroll_entry.end_date),
as_dict=as_dict
)
return salary_slip_list
@frappe.whitelist()
def payroll_entry_has_created_slips(name):
response = {}
draft_salary_slips = get_salary_slip_list(name, docstatus=0)
submitted_salary_slips = get_salary_slip_list(name, docstatus=1)
response['draft'] = 1 if draft_salary_slips else 0
response['submitted'] = 1 if submitted_salary_slips else 0
return response
def get_payroll_entry_bank_entries(payroll_entry_name):
journal_entries = frappe.db.sql(
'select name from `tabJournal Entry Account` '
@ -548,7 +477,6 @@ def get_payroll_entry_bank_entries(payroll_entry_name):
@frappe.whitelist()
def payroll_entry_has_bank_entries(name):
response = {}
bank_entries = get_payroll_entry_bank_entries(name)
response['submitted'] = 1 if bank_entries else 0
@ -568,6 +496,10 @@ def create_salary_slips_for_employees(employees, args, publish_progress=True):
frappe.publish_progress(count*100/len(set(employees) - set(salary_slips_exists_for)),
title = _("Creating Salary Slips..."))
payroll_entry = frappe.get_doc("Payroll Entry", args.payroll_entry)
payroll_entry.db_set("salary_slips_created", 1)
payroll_entry.notify_update()
def get_existing_salary_slips(employees, args):
return frappe.db.sql_list("""
select distinct employee from `tabSalary Slip`
@ -575,4 +507,41 @@ def get_existing_salary_slips(employees, args):
and start_date >= %s and end_date <= %s
and employee in (%s)
""" % ('%s', '%s', '%s', ', '.join(['%s']*len(employees))),
[args.company, args.start_date, args.end_date] + employees)
[args.company, args.start_date, args.end_date] + employees)
def submit_salary_slips_for_employees(payroll_entry, salary_slips, publish_progress=True):
submitted_ss = []
not_submitted_ss = []
frappe.flags.via_payroll_entry = True
count = 0
for ss in salary_slips:
ss_obj = frappe.get_doc("Salary Slip",ss[0])
if ss_obj.net_pay<0:
not_submitted_ss.append(ss[0])
else:
try:
ss_obj.submit()
submitted_ss.append(ss_obj)
except frappe.ValidationError:
not_submitted_ss.append(ss[0])
count += 1
if publish_progress:
frappe.publish_progress(count*100/len(salary_slips), title = _("Submitting Salary Slips..."))
if submitted_ss:
payroll_entry.make_accrual_jv_entry()
frappe.msgprint(_("Salary Slip submitted for period from {0} to {1}")
.format(ss_obj.start_date, ss_obj.end_date))
payroll_entry.email_salary_slip(submitted_ss)
payroll_entry.db_set("salary_slips_submitted", 1)
payroll_entry.notify_update()
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(_("Could not submit some Salary Slips"))

View File

@ -0,0 +1,20 @@
from frappe import _
def get_data():
return {
'fieldname': 'payroll_entry',
'non_standard_fieldnames': {
'Journal Entry': 'reference_name',
'Payment Entry': 'reference_name',
},
'transactions': [
{
'items': ['Salary Slip', 'Journal Entry']
}
],
'form_links': [
{
'items': ['Error Log']
}
]
}

View File

@ -83,10 +83,11 @@ class SalarySlip(TransactionBase):
for additional_component in additional_components:
additional_component = frappe._dict(additional_component)
amount = additional_component.amount
overwrite = additional_component.overwrite
key = "earnings"
if additional_component.type == "Deduction":
key = "deductions"
self.update_component_row(frappe._dict(additional_component.struct_row), amount, key)
self.update_component_row(frappe._dict(additional_component.struct_row), amount, key, overwrite=overwrite)
self.get_last_payroll_period_benefit()
@ -125,7 +126,7 @@ class SalarySlip(TransactionBase):
if benefit_claim_amount:
self.update_component_row(struct_row, benefit_claim_amount, "earnings")
def update_component_row(self, struct_row, amount, key, benefit_tax=None, additional_tax=None):
def update_component_row(self, struct_row, amount, key, benefit_tax=None, additional_tax=None, overwrite=1):
component_row = None
for d in self.get(key):
if d.salary_component == struct_row.salary_component:
@ -146,8 +147,13 @@ class SalarySlip(TransactionBase):
'tax_on_additional_salary': additional_tax
})
else:
component_row.default_amount = amount
component_row.amount = amount
if overwrite:
component_row.default_amount = amount
component_row.amount = amount
else:
component_row.default_amount += amount
component_row.amount = component_row.default_amount
component_row.tax_on_flexible_benefit = benefit_tax
component_row.tax_on_additional_salary = additional_tax
@ -447,6 +453,9 @@ class SalarySlip(TransactionBase):
self.net_pay = flt(self.gross_pay) - (flt(self.total_deduction) + flt(self.total_loan_repayment))
self.rounded_total = rounded(self.net_pay,
self.precision("net_pay") if disable_rounded_total else 0)
if self.net_pay < 0:
frappe.throw(_("Net Pay cannnot be negative"))
def set_loan_repayment(self):
self.set('loans', [])