Merge pull request #31231 from deepeshgarg007/gstr_3b_composite_supplies
fix(India): Supplies from composite dealer not showing up
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index 42917f8..7e3597e 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
@@ -45,8 +45,6 @@
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) {
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index 9dde85f..aefa9a5 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -52,7 +52,6 @@
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) {
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js
index c9e6798..da45610 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.js
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.js
@@ -43,8 +43,6 @@
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) {
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index ad1ba2a..8594ebb 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -372,3 +372,4 @@
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
diff --git a/erpnext/patches/v13_0/set_payroll_entry_status.py b/erpnext/patches/v13_0/set_payroll_entry_status.py
new file mode 100644
index 0000000..97adff9
--- /dev/null
+++ b/erpnext/patches/v13_0/set_payroll_entry_status.py
@@ -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()
diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.js b/erpnext/payroll/doctype/payroll_entry/payroll_entry.js
index 62e183e..b06f350 100644
--- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.js
+++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.js
@@ -40,30 +40,69 @@
},
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 @@
doc: frm.doc,
method: "create_salary_slips",
callback: function () {
- frm.refresh();
+ frm.reload_doc();
frm.toolbar.refresh();
}
});
@@ -97,7 +136,7 @@
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 @@
method: 'submit_salary_slips',
args: {},
callback: function () {
+ frm.reload_doc();
frm.events.refresh(frm);
},
doc: frm.doc,
diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.json b/erpnext/payroll/doctype/payroll_entry/payroll_entry.json
index 0444134..17882eb 100644
--- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.json
+++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.json
@@ -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": [
{
@@ -54,11 +57,6 @@
"label": "Select Employees"
},
{
- "fieldname": "column_break0",
- "fieldtype": "Column Break",
- "width": "50%"
- },
- {
"default": "Today",
"fieldname": "posting_date",
"fieldtype": "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": []
}
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
index 473fb0d..620fcad 100644
--- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
+++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
@@ -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 @@
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 @@
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 @@
)
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 @@
"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 @@
@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 @@
)
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 @@
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 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
diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry_list.js b/erpnext/payroll/doctype/payroll_entry/payroll_entry_list.js
new file mode 100644
index 0000000..56390b7
--- /dev/null
+++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry_list.js
@@ -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];
+ }
+};
diff --git a/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py
index fda0fcf..0363a0c 100644
--- a/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py
+++ b/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py
@@ -5,6 +5,7 @@
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.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 @@
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 @@
"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"
+
+ 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"
)
- 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"
- )
+ 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 @@
(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 @@
"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 @@
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 @@
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 @@
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 @@
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 @@
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),
+ )
diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
index 60ba2d9..5e3814b 100644
--- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
@@ -1050,10 +1050,10 @@
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:
diff --git a/erpnext/payroll/doctype/salary_structure/test_salary_structure.py b/erpnext/payroll/doctype/salary_structure/test_salary_structure.py
index 5c78e8f..8cc2ea3 100644
--- a/erpnext/payroll/doctype/salary_structure/test_salary_structure.py
+++ b/erpnext/payroll/doctype/salary_structure/test_salary_structure.py
@@ -169,9 +169,6 @@
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)
diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js
index 58eb891..a5b7699 100644
--- a/erpnext/public/js/controllers/buying.js
+++ b/erpnext/public/js/controllers/buying.js
@@ -74,6 +74,7 @@
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() {
diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py
index 0bdbe56..6cbc12c 100644
--- a/erpnext/regional/report/gstr_1/gstr_1.py
+++ b/erpnext/regional/report/gstr_1/gstr_1.py
@@ -1155,8 +1155,11 @@
.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)
)
diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js
index 0954de4..6cb53c3 100644
--- a/erpnext/selling/sales_common.js
+++ b/erpnext/selling/sales_common.js
@@ -43,6 +43,7 @@
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() {
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js
index 706ca36..ea3cf19 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.js
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.js
@@ -77,8 +77,6 @@
}
});
- 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);
},
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
index 51ec598..754404b 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
@@ -46,8 +46,6 @@
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) {