Merge branch 'develop' into item_wise_report_perf

This commit is contained in:
Deepesh Garg 2022-06-06 09:01:37 +05:30 committed by GitHub
commit 8b56f0559e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 490 additions and 261 deletions

View File

@ -45,8 +45,6 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
if (this.frm.doc.supplier && this.frm.doc.__islocal) {
this.frm.trigger('supplier');
}
erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype);
}
refresh(doc) {

View File

@ -52,7 +52,6 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
me.frm.refresh_fields();
}
erpnext.queries.setup_warehouse_query(this.frm);
erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype);
}
refresh(doc, dt, dn) {

View File

@ -43,8 +43,6 @@ frappe.ui.form.on("Purchase Order", {
erpnext.queries.setup_queries(frm, "Warehouse", function() {
return erpnext.queries.warehouse(frm.doc);
});
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
},
apply_tds: function(frm) {

View File

@ -372,3 +372,4 @@ erpnext.patches.v14_0.delete_employee_transfer_property_doctype
erpnext.patches.v13_0.create_accounting_dimensions_in_orders
erpnext.patches.v13_0.set_per_billed_in_return_delivery_note
execute:frappe.delete_doc("DocType", "Naming Series")
erpnext.patches.v13_0.set_payroll_entry_status

View File

@ -0,0 +1,16 @@
import frappe
from frappe.query_builder import Case
def execute():
PayrollEntry = frappe.qb.DocType("Payroll Entry")
(
frappe.qb.update(PayrollEntry).set(
"status",
Case()
.when(PayrollEntry.docstatus == 0, "Draft")
.when(PayrollEntry.docstatus == 1, "Submitted")
.else_("Cancelled"),
)
).run()

View File

@ -40,30 +40,69 @@ frappe.ui.form.on('Payroll Entry', {
},
refresh: function (frm) {
if (frm.doc.docstatus == 0) {
if (!frm.is_new()) {
if (frm.doc.docstatus === 0 && !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
&& !frappe.model.has_workflow(frm.doctype)
&& !cint(frm.doc.salary_slips_created)
&& (frm.doc.docstatus != 2)
) {
if (frm.doc.docstatus == 0) {
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 && !frappe.model.has_workflow(frm.doctype)) {
frm.page.clear_primary_action();
frm.page.set_primary_action(__('Create Salary Slips'), () => {
frm.save('Submit').then(() => {
frm.page.set_primary_action(__("Create Salary Slips"), () => {
frm.save("Submit").then(() => {
frm.page.clear_primary_action();
frm.refresh();
frm.events.refresh(frm);
});
});
} else if (frm.doc.docstatus == 1 && frm.doc.status == "Failed") {
frm.add_custom_button(__("Create Salary Slip"), function () {
frm.call("create_salary_slips", {}, () => {
frm.reload_doc();
});
}).addClass("btn-primary");
}
}
if (frm.doc.docstatus == 1) {
if (frm.doc.docstatus == 1 && frm.doc.status == "Submitted") {
if (frm.custom_buttons) frm.clear_custom_buttons();
frm.events.add_context_buttons(frm);
}
if (frm.doc.status == "Failed" && frm.doc.error_message) {
const issue = `<a id="jump_to_error" style="text-decoration: underline;">issue</a>`;
let process = (cint(frm.doc.salary_slips_created)) ? "submission" : "creation";
frm.dashboard.set_headline(
__("Salary Slip {0} failed. You can resolve the {1} and retry {0}.", [process, issue])
);
$("#jump_to_error").on("click", (e) => {
e.preventDefault();
frappe.utils.scroll_to(
frm.get_field("error_message").$wrapper,
true,
30
);
});
}
frappe.realtime.on("completed_salary_slip_creation", function() {
frm.reload_doc();
});
frappe.realtime.on("completed_salary_slip_submission", function() {
frm.reload_doc();
});
},
get_employee_details: function (frm) {
@ -88,7 +127,7 @@ frappe.ui.form.on('Payroll Entry', {
doc: frm.doc,
method: "create_salary_slips",
callback: function () {
frm.refresh();
frm.reload_doc();
frm.toolbar.refresh();
}
});
@ -97,7 +136,7 @@ frappe.ui.form.on('Payroll Entry', {
add_context_buttons: function (frm) {
if (frm.doc.salary_slips_submitted || (frm.doc.__onload && frm.doc.__onload.submitted_ss)) {
frm.events.add_bank_entry_button(frm);
} else if (frm.doc.salary_slips_created) {
} else if (frm.doc.salary_slips_created && frm.doc.status != 'Queued') {
frm.add_custom_button(__("Submit Salary Slip"), function () {
submit_salary_slip(frm);
}).addClass("btn-primary");
@ -331,6 +370,7 @@ const submit_salary_slip = function (frm) {
method: 'submit_salary_slips',
args: {},
callback: function () {
frm.reload_doc();
frm.events.refresh(frm);
},
doc: frm.doc,

View File

@ -8,11 +8,11 @@
"engine": "InnoDB",
"field_order": [
"section_break0",
"column_break0",
"posting_date",
"payroll_frequency",
"company",
"column_break1",
"status",
"currency",
"exchange_rate",
"payroll_payable_account",
@ -41,11 +41,14 @@
"cost_center",
"account",
"payment_account",
"amended_from",
"column_break_33",
"bank_account",
"salary_slips_created",
"salary_slips_submitted"
"salary_slips_submitted",
"failure_details_section",
"error_message",
"section_break_41",
"amended_from"
],
"fields": [
{
@ -53,11 +56,6 @@
"fieldtype": "Section Break",
"label": "Select Employees"
},
{
"fieldname": "column_break0",
"fieldtype": "Column Break",
"width": "50%"
},
{
"default": "Today",
"fieldname": "posting_date",
@ -231,6 +229,7 @@
"fieldtype": "Check",
"hidden": 1,
"label": "Salary Slips Created",
"no_copy": 1,
"read_only": 1
},
{
@ -239,6 +238,7 @@
"fieldtype": "Check",
"hidden": 1,
"label": "Salary Slips Submitted",
"no_copy": 1,
"read_only": 1
},
{
@ -284,15 +284,44 @@
"label": "Payroll Payable Account",
"options": "Account",
"reqd": 1
},
{
"collapsible": 1,
"collapsible_depends_on": "error_message",
"depends_on": "eval:doc.status=='Failed';",
"fieldname": "failure_details_section",
"fieldtype": "Section Break",
"label": "Failure Details"
},
{
"depends_on": "eval:doc.status=='Failed';",
"fieldname": "error_message",
"fieldtype": "Small Text",
"label": "Error Message",
"no_copy": 1,
"read_only": 1
},
{
"fieldname": "section_break_41",
"fieldtype": "Section Break"
},
{
"fieldname": "status",
"fieldtype": "Select",
"label": "Status",
"options": "Draft\nSubmitted\nCancelled\nQueued\nFailed",
"print_hide": 1,
"read_only": 1
}
],
"icon": "fa fa-cog",
"is_submittable": 1,
"links": [],
"modified": "2020-12-17 15:13:17.766210",
"modified": "2022-03-16 12:45:21.662765",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Payroll Entry",
"naming_rule": "Expression (old style)",
"owner": "Administrator",
"permissions": [
{
@ -308,5 +337,6 @@
}
],
"sort_field": "modified",
"sort_order": "DESC"
"sort_order": "DESC",
"states": []
}

View File

@ -1,6 +1,7 @@
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import json
import frappe
from dateutil.relativedelta import relativedelta
@ -40,8 +41,10 @@ class PayrollEntry(Document):
def validate(self):
self.number_of_employees = len(self.employees)
self.set_status()
def on_submit(self):
self.set_status(update=True, status="Submitted")
self.create_salary_slips()
def before_submit(self):
@ -51,6 +54,15 @@ class PayrollEntry(Document):
if self.validate_employee_attendance():
frappe.throw(_("Cannot Submit, Employees left to mark attendance"))
def set_status(self, status=None, update=False):
if not status:
status = {0: "Draft", 1: "Submitted", 2: "Cancelled"}[self.docstatus or 0]
if update:
self.db_set("status", status)
else:
self.status = status
def validate_employee_details(self):
emp_with_sal_slip = []
for employee_details in self.employees:
@ -87,6 +99,8 @@ class PayrollEntry(Document):
)
self.db_set("salary_slips_created", 0)
self.db_set("salary_slips_submitted", 0)
self.set_status(update=True, status="Cancelled")
self.db_set("error_message", "")
def get_emp_list(self):
"""
@ -183,8 +197,20 @@ class PayrollEntry(Document):
"currency": self.currency,
}
)
if len(employees) > 30:
frappe.enqueue(create_salary_slips_for_employees, timeout=600, employees=employees, args=args)
if len(employees) > 30 or frappe.flags.enqueue_payroll_entry:
self.db_set("status", "Queued")
frappe.enqueue(
create_salary_slips_for_employees,
timeout=600,
employees=employees,
args=args,
publish_progress=False,
)
frappe.msgprint(
_("Salary Slip creation is queued. It may take a few minutes"),
alert=True,
indicator="blue",
)
else:
create_salary_slips_for_employees(employees, args, publish_progress=False)
# since this method is called via frm.call this doc needs to be updated manually
@ -214,13 +240,23 @@ class PayrollEntry(Document):
@frappe.whitelist()
def submit_salary_slips(self):
self.check_permission("write")
ss_list = self.get_sal_slip_list(ss_status=0)
if len(ss_list) > 30:
salary_slips = self.get_sal_slip_list(ss_status=0)
if len(salary_slips) > 30 or frappe.flags.enqueue_payroll_entry:
self.db_set("status", "Queued")
frappe.enqueue(
submit_salary_slips_for_employees, timeout=600, payroll_entry=self, salary_slips=ss_list
submit_salary_slips_for_employees,
timeout=600,
payroll_entry=self,
salary_slips=salary_slips,
publish_progress=False,
)
frappe.msgprint(
_("Salary Slip submission is queued. It may take a few minutes"),
alert=True,
indicator="blue",
)
else:
submit_salary_slips_for_employees(self, ss_list, publish_progress=False)
submit_salary_slips_for_employees(self, salary_slips, publish_progress=False)
def email_salary_slip(self, submitted_ss):
if frappe.db.get_single_value("Payroll Settings", "email_salary_slip_to_employee"):
@ -233,7 +269,11 @@ class PayrollEntry(Document):
)
if not account:
frappe.throw(_("Please set account in Salary Component {0}").format(salary_component))
frappe.throw(
_("Please set account in Salary Component {0}").format(
get_link_to_form("Salary Component", salary_component)
)
)
return account
@ -790,36 +830,80 @@ def payroll_entry_has_bank_entries(name):
return response
def log_payroll_failure(process, payroll_entry, error):
error_log = frappe.log_error(
title=_("Salary Slip {0} failed for Payroll Entry {1}").format(process, payroll_entry.name)
)
message_log = frappe.message_log.pop() if frappe.message_log else str(error)
try:
error_message = json.loads(message_log).get("message")
except Exception:
error_message = message_log
error_message += "\n" + _("Check Error Log {0} for more details.").format(
get_link_to_form("Error Log", error_log.name)
)
payroll_entry.db_set({"error_message": error_message, "status": "Failed"})
def create_salary_slips_for_employees(employees, args, publish_progress=True):
salary_slips_exists_for = get_existing_salary_slips(employees, args)
count = 0
salary_slips_not_created = []
for emp in employees:
if emp not in salary_slips_exists_for:
args.update({"doctype": "Salary Slip", "employee": emp})
ss = frappe.get_doc(args)
ss.insert()
count += 1
if publish_progress:
frappe.publish_progress(
count * 100 / len(set(employees) - set(salary_slips_exists_for)),
title=_("Creating Salary Slips..."),
)
try:
payroll_entry = frappe.get_doc("Payroll Entry", args.payroll_entry)
salary_slips_exist_for = get_existing_salary_slips(employees, args)
count = 0
else:
salary_slips_not_created.append(emp)
for emp in employees:
if emp not in salary_slips_exist_for:
args.update({"doctype": "Salary Slip", "employee": emp})
frappe.get_doc(args).insert()
payroll_entry = frappe.get_doc("Payroll Entry", args.payroll_entry)
payroll_entry.db_set("salary_slips_created", 1)
payroll_entry.notify_update()
count += 1
if publish_progress:
frappe.publish_progress(
count * 100 / len(set(employees) - set(salary_slips_exist_for)),
title=_("Creating Salary Slips..."),
)
if salary_slips_not_created:
payroll_entry.db_set({"status": "Submitted", "salary_slips_created": 1, "error_message": ""})
if salary_slips_exist_for:
frappe.msgprint(
_(
"Salary Slips already exist for employees {}, and will not be processed by this payroll."
).format(frappe.bold(", ".join(emp for emp in salary_slips_exist_for))),
title=_("Message"),
indicator="orange",
)
except Exception as e:
frappe.db.rollback()
log_payroll_failure("creation", payroll_entry, e)
finally:
frappe.db.commit() # nosemgrep
frappe.publish_realtime("completed_salary_slip_creation")
def show_payroll_submission_status(submitted, unsubmitted, payroll_entry):
if not submitted and not unsubmitted:
frappe.msgprint(
_(
"Salary Slips already exists for employees {}, and will not be processed by this payroll."
).format(frappe.bold(", ".join([emp for emp in salary_slips_not_created]))),
title=_("Message"),
indicator="orange",
"No salary slip found to submit for the above selected criteria OR salary slip already submitted"
)
)
elif submitted and not unsubmitted:
frappe.msgprint(
_("Salary Slips submitted for period from {0} to {1}").format(
payroll_entry.start_date, payroll_entry.end_date
)
)
elif unsubmitted:
frappe.msgprint(
_("Could not submit some Salary Slips: {}").format(
", ".join(get_link_to_form("Salary Slip", entry) for entry in unsubmitted)
)
)
@ -837,45 +921,41 @@ def get_existing_salary_slips(employees, args):
def submit_salary_slips_for_employees(payroll_entry, salary_slips, publish_progress=True):
submitted_ss = []
not_submitted_ss = []
frappe.flags.via_payroll_entry = True
try:
submitted = []
unsubmitted = []
frappe.flags.via_payroll_entry = True
count = 0
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])
for entry in salary_slips:
salary_slip = frappe.get_doc("Salary Slip", entry[0])
if salary_slip.net_pay < 0:
unsubmitted.append(entry[0])
else:
try:
salary_slip.submit()
submitted.append(salary_slip)
except frappe.ValidationError:
unsubmitted.append(entry[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)
)
count += 1
if publish_progress:
frappe.publish_progress(count * 100 / len(salary_slips), title=_("Submitting Salary Slips..."))
payroll_entry.email_salary_slip(submitted_ss)
if submitted:
payroll_entry.make_accrual_jv_entry()
payroll_entry.email_salary_slip(submitted)
payroll_entry.db_set({"salary_slips_submitted": 1, "status": "Submitted", "error_message": ""})
payroll_entry.db_set("salary_slips_submitted", 1)
payroll_entry.notify_update()
show_payroll_submission_status(submitted, unsubmitted, payroll_entry)
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"
)
)
except Exception as e:
frappe.db.rollback()
log_payroll_failure("submission", payroll_entry, e)
if not_submitted_ss:
frappe.msgprint(_("Could not submit some Salary Slips"))
finally:
frappe.db.commit() # nosemgrep
frappe.publish_realtime("completed_salary_slip_submission")
frappe.flags.via_payroll_entry = False

View File

@ -0,0 +1,18 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
// render
frappe.listview_settings['Payroll Entry'] = {
has_indicator_for_draft: 1,
get_indicator: function(doc) {
var status_color = {
'Draft': 'red',
'Submitted': 'blue',
'Queued': 'orange',
'Failed': 'red',
'Cancelled': 'red'
};
return [__(doc.status), status_color[doc.status], 'status,=,'+doc.status];
}
};

View File

@ -5,6 +5,7 @@ import unittest
import frappe
from dateutil.relativedelta import relativedelta
from frappe.tests.utils import FrappeTestCase
from frappe.utils import add_months
import erpnext
@ -22,10 +23,9 @@ from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_
from erpnext.payroll.doctype.payroll_entry.payroll_entry import get_end_date, get_start_end_dates
from erpnext.payroll.doctype.salary_slip.test_salary_slip import (
create_account,
get_salary_component_account,
make_deduction_salary_component,
make_earning_salary_component,
make_employee_salary_slip,
set_salary_component_account,
)
from erpnext.payroll.doctype.salary_structure.test_salary_structure import (
create_salary_structure_assignment,
@ -35,13 +35,7 @@ from erpnext.payroll.doctype.salary_structure.test_salary_structure import (
test_dependencies = ["Holiday List"]
class TestPayrollEntry(unittest.TestCase):
@classmethod
def setUpClass(cls):
frappe.db.set_value(
"Company", erpnext.get_default_company(), "default_holiday_list", "_Test Holiday List"
)
class TestPayrollEntry(FrappeTestCase):
def setUp(self):
for dt in [
"Salary Slip",
@ -52,81 +46,72 @@ class TestPayrollEntry(unittest.TestCase):
"Salary Structure Assignment",
"Payroll Employee Detail",
"Additional Salary",
"Loan",
]:
frappe.db.sql("delete from `tab%s`" % dt)
frappe.db.delete(dt)
make_earning_salary_component(setup=True, company_list=["_Test Company"])
make_deduction_salary_component(setup=True, test_tax=False, company_list=["_Test Company"])
frappe.db.set_value("Company", "_Test Company", "default_holiday_list", "_Test Holiday List")
frappe.db.set_value("Payroll Settings", None, "email_salary_slip_to_employee", 0)
def test_payroll_entry(self): # pylint: disable=no-self-use
company = erpnext.get_default_company()
for data in frappe.get_all("Salary Component", fields=["name"]):
if not frappe.db.get_value(
"Salary Component Account", {"parent": data.name, "company": company}, "name"
):
get_salary_component_account(data.name)
employee = frappe.db.get_value("Employee", {"company": company})
company_doc = frappe.get_doc("Company", company)
make_salary_structure(
"_Test Salary Structure",
"Monthly",
employee,
company=company,
currency=company_doc.default_currency,
# set default payable account
default_account = frappe.db.get_value(
"Company", "_Test Company", "default_payroll_payable_account"
)
dates = get_start_end_dates("Monthly", nowdate())
if not frappe.db.get_value(
"Salary Slip", {"start_date": dates.start_date, "end_date": dates.end_date}
):
make_payroll_entry(
start_date=dates.start_date,
end_date=dates.end_date,
payable_account=company_doc.default_payroll_payable_account,
currency=company_doc.default_currency,
if not default_account or default_account != "_Test Payroll Payable - _TC":
create_account(
account_name="_Test Payroll Payable",
company="_Test Company",
parent_account="Current Liabilities - _TC",
account_type="Payable",
)
frappe.db.set_value(
"Company", "_Test Company", "default_payroll_payable_account", "_Test Payroll Payable - _TC"
)
def test_multi_currency_payroll_entry(self): # pylint: disable=no-self-use
company = erpnext.get_default_company()
employee = make_employee("test_muti_currency_employee@payroll.com", company=company)
for data in frappe.get_all("Salary Component", fields=["name"]):
if not frappe.db.get_value(
"Salary Component Account", {"parent": data.name, "company": company}, "name"
):
get_salary_component_account(data.name)
def test_payroll_entry(self):
company = frappe.get_doc("Company", "_Test Company")
employee = frappe.db.get_value("Employee", {"company": "_Test Company"})
setup_salary_structure(employee, company)
company_doc = frappe.get_doc("Company", company)
salary_structure = make_salary_structure(
"_Test Multi Currency Salary Structure", "Monthly", company=company, currency="USD"
dates = get_start_end_dates("Monthly", nowdate())
make_payroll_entry(
start_date=dates.start_date,
end_date=dates.end_date,
payable_account=company.default_payroll_payable_account,
currency=company.default_currency,
company=company.name,
)
create_salary_structure_assignment(
employee, salary_structure.name, company=company, currency="USD"
)
frappe.db.sql(
"""delete from `tabSalary Slip` where employee=%s""",
(frappe.db.get_value("Employee", {"user_id": "test_muti_currency_employee@payroll.com"})),
)
salary_slip = get_salary_slip(
"test_muti_currency_employee@payroll.com", "Monthly", "_Test Multi Currency Salary Structure"
def test_multi_currency_payroll_entry(self):
company = frappe.get_doc("Company", "_Test Company")
employee = make_employee(
"test_muti_currency_employee@payroll.com", company=company.name, department="Accounts - _TC"
)
salary_structure = "_Test Multi Currency Salary Structure"
setup_salary_structure(employee, company, "USD", salary_structure)
dates = get_start_end_dates("Monthly", nowdate())
payroll_entry = make_payroll_entry(
start_date=dates.start_date,
end_date=dates.end_date,
payable_account=company_doc.default_payroll_payable_account,
payable_account=company.default_payroll_payable_account,
currency="USD",
exchange_rate=70,
company=company.name,
cost_center="Main - _TC",
)
payroll_entry.make_payment_entry()
salary_slip.load_from_db()
salary_slip = frappe.db.get_value("Salary Slip", {"payroll_entry": payroll_entry.name}, "name")
salary_slip = frappe.get_doc("Salary Slip", salary_slip)
payroll_entry.reload()
payroll_je = salary_slip.journal_entry
if payroll_je:
payroll_je_doc = frappe.get_doc("Journal Entry", payroll_je)
self.assertEqual(salary_slip.base_gross_pay, payroll_je_doc.total_debit)
self.assertEqual(salary_slip.base_gross_pay, payroll_je_doc.total_credit)
@ -139,27 +124,15 @@ class TestPayrollEntry(unittest.TestCase):
(payroll_entry.name),
as_dict=1,
)
self.assertEqual(salary_slip.base_net_pay, payment_entry[0].total_debit)
self.assertEqual(salary_slip.base_net_pay, payment_entry[0].total_credit)
def test_payroll_entry_with_employee_cost_center(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", {"parent": data.name, "company": "_Test Company"}, "name"
):
get_salary_component_account(data.name)
def test_payroll_entry_with_employee_cost_center(self):
if not frappe.db.exists("Department", "cc - _TC"):
frappe.get_doc(
{"doctype": "Department", "department_name": "cc", "company": "_Test Company"}
).insert()
frappe.db.sql("""delete from `tabEmployee` where employee_name='test_employee1@example.com' """)
frappe.db.sql("""delete from `tabEmployee` where employee_name='test_employee2@example.com' """)
frappe.db.sql("""delete from `tabSalary Structure` where name='_Test Salary Structure 1' """)
frappe.db.sql("""delete from `tabSalary Structure` where name='_Test Salary Structure 2' """)
employee1 = make_employee(
"test_employee1@example.com",
payroll_cost_center="_Test Cost Center - _TC",
@ -170,38 +143,15 @@ class TestPayrollEntry(unittest.TestCase):
"test_employee2@example.com", department="cc - _TC", company="_Test Company"
)
if not frappe.db.exists("Account", "_Test Payroll Payable - _TC"):
create_account(
account_name="_Test Payroll Payable",
company="_Test Company",
parent_account="Current Liabilities - _TC",
account_type="Payable",
)
company = frappe.get_doc("Company", "_Test Company")
setup_salary_structure(employee1, company)
if (
not frappe.db.get_value("Company", "_Test Company", "default_payroll_payable_account")
or frappe.db.get_value("Company", "_Test Company", "default_payroll_payable_account")
!= "_Test Payroll Payable - _TC"
):
frappe.db.set_value(
"Company", "_Test Company", "default_payroll_payable_account", "_Test Payroll Payable - _TC"
)
currency = frappe.db.get_value("Company", "_Test Company", "default_currency")
make_salary_structure(
"_Test Salary Structure 1",
"Monthly",
employee1,
company="_Test Company",
currency=currency,
test_tax=False,
)
ss = make_salary_structure(
"_Test Salary Structure 2",
"Monthly",
employee2,
company="_Test Company",
currency=currency,
currency=company.default_currency,
test_tax=False,
)
@ -220,42 +170,38 @@ class TestPayrollEntry(unittest.TestCase):
ssa_doc.append(
"payroll_cost_centers", {"cost_center": "_Test Cost Center 2 - _TC", "percentage": 40}
)
ssa_doc.save()
dates = get_start_end_dates("Monthly", nowdate())
if not frappe.db.get_value(
"Salary Slip", {"start_date": dates.start_date, "end_date": dates.end_date}
):
pe = make_payroll_entry(
start_date=dates.start_date,
end_date=dates.end_date,
payable_account="_Test Payroll Payable - _TC",
currency=frappe.db.get_value("Company", "_Test Company", "default_currency"),
department="cc - _TC",
company="_Test Company",
payment_account="Cash - _TC",
cost_center="Main - _TC",
)
je = frappe.db.get_value("Salary Slip", {"payroll_entry": pe.name}, "journal_entry")
je_entries = frappe.db.sql(
"""
select account, cost_center, debit, credit
from `tabJournal Entry Account`
where parent=%s
order by account, cost_center
""",
je,
)
expected_je = (
("_Test Payroll Payable - _TC", "Main - _TC", 0.0, 155600.0),
("Salary - _TC", "_Test Cost Center - _TC", 124800.0, 0.0),
("Salary - _TC", "_Test Cost Center 2 - _TC", 31200.0, 0.0),
("Salary Deductions - _TC", "_Test Cost Center - _TC", 0.0, 320.0),
("Salary Deductions - _TC", "_Test Cost Center 2 - _TC", 0.0, 80.0),
)
pe = make_payroll_entry(
start_date=dates.start_date,
end_date=dates.end_date,
payable_account="_Test Payroll Payable - _TC",
currency=frappe.db.get_value("Company", "_Test Company", "default_currency"),
department="cc - _TC",
company="_Test Company",
payment_account="Cash - _TC",
cost_center="Main - _TC",
)
je = frappe.db.get_value("Salary Slip", {"payroll_entry": pe.name}, "journal_entry")
je_entries = frappe.db.sql(
"""
select account, cost_center, debit, credit
from `tabJournal Entry Account`
where parent=%s
order by account, cost_center
""",
je,
)
expected_je = (
("_Test Payroll Payable - _TC", "Main - _TC", 0.0, 155600.0),
("Salary - _TC", "_Test Cost Center - _TC", 124800.0, 0.0),
("Salary - _TC", "_Test Cost Center 2 - _TC", 31200.0, 0.0),
("Salary Deductions - _TC", "_Test Cost Center - _TC", 0.0, 320.0),
("Salary Deductions - _TC", "_Test Cost Center 2 - _TC", 0.0, 80.0),
)
self.assertEqual(je_entries, expected_je)
self.assertEqual(je_entries, expected_je)
def test_get_end_date(self):
self.assertEqual(get_end_date("2017-01-01", "monthly"), {"end_date": "2017-01-31"})
@ -268,31 +214,22 @@ class TestPayrollEntry(unittest.TestCase):
self.assertEqual(get_end_date("2017-02-15", "daily"), {"end_date": "2017-02-15"})
def test_loan(self):
branch = "Test Employee Branch"
applicant = make_employee("test_employee@loan.com", company="_Test Company")
company = "_Test Company"
holiday_list = make_holiday("test holiday for loan")
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()
branch = "Test Employee Branch"
if not frappe.db.exists("Branch", branch):
frappe.get_doc({"doctype": "Branch", "branch": branch}).insert()
holiday_list = make_holiday("test holiday for loan")
employee_doc = frappe.get_doc("Employee", applicant)
employee_doc.branch = branch
employee_doc.holiday_list = holiday_list
employee_doc.save()
applicant = make_employee(
"test_employee@loan.com", company="_Test Company", branch=branch, holiday_list=holiday_list
)
company_doc = frappe.get_doc("Company", company)
salary_structure = "Test Salary Structure for Loan"
make_salary_structure(
salary_structure,
"Test Salary Structure for Loan",
"Monthly",
employee=employee_doc.name,
employee=applicant,
company="_Test Company",
currency=company_doc.default_currency,
)
@ -353,11 +290,110 @@ class TestPayrollEntry(unittest.TestCase):
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)
def test_salary_slip_operation_queueing(self):
company = "_Test Company"
company_doc = frappe.get_doc("Company", company)
employee = make_employee("test_employee@payroll.com", company=company)
setup_salary_structure(employee, company_doc)
# enqueue salary slip creation via payroll entry
# Payroll Entry status should change to Queued
dates = get_start_end_dates("Monthly", nowdate())
payroll_entry = get_payroll_entry(
start_date=dates.start_date,
end_date=dates.end_date,
payable_account=company_doc.default_payroll_payable_account,
currency=company_doc.default_currency,
company=company_doc.name,
cost_center="Main - _TC",
)
frappe.flags.enqueue_payroll_entry = True
payroll_entry.submit()
payroll_entry.reload()
self.assertEqual(payroll_entry.status, "Queued")
frappe.flags.enqueue_payroll_entry = False
def test_salary_slip_operation_failure(self):
company = "_Test Company"
company_doc = frappe.get_doc("Company", company)
employee = make_employee("test_employee@payroll.com", company=company)
salary_structure = make_salary_structure(
"_Test Salary Structure",
"Monthly",
employee,
company=company,
currency=company_doc.default_currency,
)
# reset account in component to test submission failure
component = frappe.get_doc("Salary Component", salary_structure.earnings[0].salary_component)
component.accounts = []
component.save()
# salary slip submission via payroll entry
# Payroll Entry status should change to Failed because of the missing account setup
dates = get_start_end_dates("Monthly", nowdate())
payroll_entry = get_payroll_entry(
start_date=dates.start_date,
end_date=dates.end_date,
payable_account=company_doc.default_payroll_payable_account,
currency=company_doc.default_currency,
company=company_doc.name,
cost_center="Main - _TC",
)
# set employee as Inactive to check creation failure
frappe.db.set_value("Employee", employee, "status", "Inactive")
payroll_entry.submit()
payroll_entry.reload()
self.assertEqual(payroll_entry.status, "Failed")
self.assertIsNotNone(payroll_entry.error_message)
frappe.db.set_value("Employee", employee, "status", "Active")
payroll_entry.submit()
payroll_entry.submit_salary_slips()
payroll_entry.reload()
self.assertEqual(payroll_entry.status, "Failed")
self.assertIsNotNone(payroll_entry.error_message)
# set accounts
for data in frappe.get_all("Salary Component", pluck="name"):
set_salary_component_account(data, company_list=[company])
# Payroll Entry successful, status should change to Submitted
payroll_entry.submit_salary_slips()
payroll_entry.reload()
self.assertEqual(payroll_entry.status, "Submitted")
self.assertEqual(payroll_entry.error_message, "")
def test_payroll_entry_status(self):
company = "_Test Company"
company_doc = frappe.get_doc("Company", company)
employee = make_employee("test_employee@payroll.com", company=company)
setup_salary_structure(employee, company_doc)
dates = get_start_end_dates("Monthly", nowdate())
payroll_entry = get_payroll_entry(
start_date=dates.start_date,
end_date=dates.end_date,
payable_account=company_doc.default_payroll_payable_account,
currency=company_doc.default_currency,
company=company_doc.name,
cost_center="Main - _TC",
)
payroll_entry.submit()
self.assertEqual(payroll_entry.status, "Submitted")
payroll_entry.cancel()
self.assertEqual(payroll_entry.status, "Cancelled")
def make_payroll_entry(**args):
def get_payroll_entry(**args):
args = frappe._dict(args)
payroll_entry = frappe.new_doc("Payroll Entry")
@ -380,8 +416,17 @@ def make_payroll_entry(**args):
payroll_entry.payment_account = args.payment_account
payroll_entry.fill_employee_details()
payroll_entry.save()
payroll_entry.create_salary_slips()
payroll_entry.insert()
# Commit so that the first salary slip creation failure does not rollback the Payroll Entry insert.
frappe.db.commit() # nosemgrep
return payroll_entry
def make_payroll_entry(**args):
payroll_entry = get_payroll_entry(**args)
payroll_entry.submit()
payroll_entry.submit_salary_slips()
if payroll_entry.get_sal_slip_list(ss_status=1):
payroll_entry.make_payment_entry()
@ -423,10 +468,17 @@ def make_holiday(holiday_list_name):
return holiday_list_name
def get_salary_slip(user, period, salary_structure):
salary_slip = make_employee_salary_slip(user, period, salary_structure)
salary_slip.exchange_rate = 70
salary_slip.calculate_net_pay()
salary_slip.db_update()
def setup_salary_structure(employee, company_doc, currency=None, salary_structure=None):
for data in frappe.get_all("Salary Component", pluck="name"):
if not frappe.db.get_value(
"Salary Component Account", {"parent": data, "company": company_doc.name}, "name"
):
set_salary_component_account(data)
return salary_slip
make_salary_structure(
salary_structure or "_Test Salary Structure",
"Monthly",
employee,
company=company_doc.name,
currency=(currency or company_doc.default_currency),
)

View File

@ -1050,10 +1050,10 @@ def make_salary_component(salary_components, test_tax, company_list=None):
doc.update(salary_component)
doc.insert()
get_salary_component_account(doc, company_list)
set_salary_component_account(doc, company_list)
def get_salary_component_account(sal_comp, company_list=None):
def set_salary_component_account(sal_comp, company_list=None):
company = erpnext.get_default_company()
if company_list and company not in company_list:

View File

@ -169,9 +169,6 @@ def make_salary_structure(
payroll_period=None,
include_flexi_benefits=False,
):
if test_tax:
frappe.db.sql("""delete from `tabSalary Structure` where name=%s""", (salary_structure))
if frappe.db.exists("Salary Structure", salary_structure):
frappe.db.delete("Salary Structure", salary_structure)

View File

@ -74,6 +74,7 @@ erpnext.buying.BuyingController = class BuyingController extends erpnext.Transac
me.frm.set_query('supplier_address', erpnext.queries.address_query);
me.frm.set_query('billing_address', erpnext.queries.company_address_query);
erpnext.accounts.dimensions.setup_dimension_filters(me.frm, me.frm.doctype);
if(this.frm.fields_dict.supplier) {
this.frm.set_query("supplier", function() {

View File

@ -148,7 +148,6 @@ class GSTR3BReport(Document):
FROM `tabPurchase Invoice` p , `tabPurchase Invoice Item` i
WHERE p.docstatus = 1 and p.name = i.parent
and p.is_opening = 'No'
and p.gst_category != 'Registered Composition'
and (i.is_nil_exempt = 1 or i.is_non_gst = 1 or p.gst_category = 'Registered Composition') and
month(p.posting_date) = %s and year(p.posting_date) = %s
and p.company = %s and p.company_gstin = %s

View File

@ -1155,8 +1155,11 @@ def get_company_gstins(company):
.inner_join(links)
.on(address.name == links.parent)
.select(address.gstin)
.distinct()
.where(links.link_doctype == "Company")
.where(links.link_name == company)
.where(address.gstin.isnotnull())
.where(address.gstin != "")
.run(as_dict=1)
)

View File

@ -43,6 +43,7 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran
me.frm.set_query('shipping_address_name', erpnext.queries.address_query);
me.frm.set_query('dispatch_address_name', erpnext.queries.dispatch_address_query);
erpnext.accounts.dimensions.setup_dimension_filters(me.frm, me.frm.doctype);
if(this.frm.fields_dict.selling_price_list) {
this.frm.set_query("selling_price_list", function() {

View File

@ -77,8 +77,6 @@ frappe.ui.form.on("Delivery Note", {
}
});
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
frm.set_df_property('packed_items', 'cannot_add_rows', true);
frm.set_df_property('packed_items', 'cannot_delete_rows', true);
},

View File

@ -46,8 +46,6 @@ frappe.ui.form.on("Purchase Receipt", {
erpnext.queries.setup_queries(frm, "Warehouse", function() {
return erpnext.queries.warehouse(frm.doc);
});
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
},
refresh: function(frm) {