From 3831c7920d20eeafb78c783c2d1029fc54ea0bfc Mon Sep 17 00:00:00 2001 From: AJAY NATARAJAN <106462162+ajayn1991@users.noreply.github.com> Date: Wed, 7 Jun 2023 21:48:27 +0530 Subject: [PATCH 01/12] fix: Asset Depreciation Ledger Report - Add Total Row Checkbox Enabled --- .../asset_depreciation_ledger.json | 35 ++++++++++--------- .../asset_depreciations_and_balances.json | 35 ++++++++++--------- 2 files changed, 38 insertions(+), 32 deletions(-) diff --git a/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.json b/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.json index bee2829c87ab..fed7760ef5a8 100644 --- a/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.json +++ b/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.json @@ -1,20 +1,23 @@ { - "add_total_row": 0, - "apply_user_permissions": 1, - "creation": "2016-04-08 14:49:58.133098", - "disabled": 0, - "docstatus": 0, - "doctype": "Report", - "idx": 2, - "is_standard": "Yes", - "modified": "2017-02-24 20:08:26.084484", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Asset Depreciation Ledger", - "owner": "Administrator", - "ref_doctype": "Asset", - "report_name": "Asset Depreciation Ledger", - "report_type": "Script Report", + "add_total_row": 1, + "columns": [], + "creation": "2016-04-08 14:49:58.133098", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 2, + "is_standard": "Yes", + "modified": "2023-06-06 09:00:07.435151", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Asset Depreciation Ledger", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Asset", + "report_name": "Asset Depreciation Ledger", + "report_type": "Script Report", "roles": [ { "role": "Accounts User" diff --git a/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.json b/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.json index eab95fc73b3f..b380a464469d 100644 --- a/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.json +++ b/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.json @@ -1,20 +1,23 @@ { - "add_total_row": 0, - "apply_user_permissions": 1, - "creation": "2016-04-08 14:56:37.235981", - "disabled": 0, - "docstatus": 0, - "doctype": "Report", - "idx": 2, - "is_standard": "Yes", - "modified": "2017-02-24 20:08:18.660476", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Asset Depreciations and Balances", - "owner": "Administrator", - "ref_doctype": "Asset", - "report_name": "Asset Depreciations and Balances", - "report_type": "Script Report", + "add_total_row": 1, + "columns": [], + "creation": "2016-04-08 14:56:37.235981", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 2, + "is_standard": "Yes", + "modified": "2023-06-06 11:33:29.611277", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Asset Depreciations and Balances", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Asset", + "report_name": "Asset Depreciations and Balances", + "report_type": "Script Report", "roles": [ { "role": "Accounts User" From 9d5b50006023da0a8a3b867476cb38ba2407a220 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 7 Jun 2023 22:36:49 +0530 Subject: [PATCH 02/12] fix: Project in item-wise sales register (#35596) fix: Project in item-wise sales register (#35596) (cherry picked from commit f732cac6780e0f9c9aead4df381a0bbe5774354c) Co-authored-by: Deepesh Garg --- .../item_wise_sales_register/item_wise_sales_register.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py index dd9c07361289..0ebe13f4f326 100644 --- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py +++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py @@ -399,8 +399,9 @@ def get_items(filters, additional_query_columns, additional_conditions=None): `tabSales Invoice`.posting_date, `tabSales Invoice`.debit_to, `tabSales Invoice`.unrealized_profit_loss_account, `tabSales Invoice`.is_internal_customer, - `tabSales Invoice`.project, `tabSales Invoice`.customer, `tabSales Invoice`.remarks, + `tabSales Invoice`.customer, `tabSales Invoice`.remarks, `tabSales Invoice`.territory, `tabSales Invoice`.company, `tabSales Invoice`.base_net_total, + `tabSales Invoice Item`.project, `tabSales Invoice Item`.item_code, `tabSales Invoice Item`.description, `tabSales Invoice Item`.`item_name`, `tabSales Invoice Item`.`item_group`, `tabSales Invoice Item`.sales_order, `tabSales Invoice Item`.delivery_note, From 97f4af8d978ab43929f26a4c11be3883cd300cbf Mon Sep 17 00:00:00 2001 From: Anand Baburajan Date: Thu, 8 Jun 2023 23:16:35 +0530 Subject: [PATCH 03/12] fix: calculate wdv depr schedule properly for existing assets [v13] (#35615) * fix: calculate wdv depr schedule properly for existing assets * fix: calculate wdv depr schedule properly for existing assets properly --- erpnext/assets/doctype/asset/asset.py | 27 +++++++++++++++----- erpnext/assets/doctype/asset/depreciation.py | 7 +++++ erpnext/assets/doctype/asset/test_asset.py | 4 +-- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 7c25850976be..335b18322873 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -27,6 +27,7 @@ from erpnext.assets.doctype.asset.depreciation import ( get_depreciation_accounts, get_disposal_account_and_cost_center, + is_first_day_of_the_month, is_last_day_of_the_month, ) from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account @@ -364,8 +365,14 @@ def make_depreciation_schedule(self, date_of_disposal): break # For first row - if n == 0 and has_pro_rata and not self.opening_accumulated_depreciation: - from_date = add_days(self.available_for_use_date, -1) + if ( + n == 0 + and (has_pro_rata or has_wdv_or_dd_non_yearly_pro_rata) + and not self.opening_accumulated_depreciation + ): + from_date = add_days( + self.available_for_use_date, -1 + ) # needed to calc depr amount for available_for_use_date too depreciation_amount, days, months = self.get_pro_rata_amt( finance_book, depreciation_amount, @@ -374,10 +381,18 @@ def make_depreciation_schedule(self, date_of_disposal): has_wdv_or_dd_non_yearly_pro_rata, ) elif n == 0 and has_wdv_or_dd_non_yearly_pro_rata and self.opening_accumulated_depreciation: - from_date = add_months( - getdate(self.available_for_use_date), - (self.number_of_depreciations_booked * finance_book.frequency_of_depreciation), - ) + if not is_first_day_of_the_month(getdate(self.available_for_use_date)): + from_date = get_last_day( + add_months( + getdate(self.available_for_use_date), + ((self.number_of_depreciations_booked - 1) * finance_book.frequency_of_depreciation), + ) + ) + else: + from_date = add_months( + getdate(add_days(self.available_for_use_date, -1)), + (self.number_of_depreciations_booked * finance_book.frequency_of_depreciation), + ) depreciation_amount, days, months = self.get_pro_rata_amt( finance_book, depreciation_amount, diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index 0cfe328270a4..4f79ae847caf 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -8,6 +8,7 @@ add_months, cint, flt, + get_first_day, get_last_day, get_link_to_form, getdate, @@ -543,3 +544,9 @@ def is_last_day_of_the_month(date): last_day_of_the_month = get_last_day(date) return getdate(last_day_of_the_month) == getdate(date) + + +def is_first_day_of_the_month(date): + first_day_of_the_month = get_first_day(date) + + return getdate(first_day_of_the_month) == getdate(date) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 4988846463d7..6fa74c1d3917 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -686,14 +686,14 @@ def test_schedule_for_double_declining_method_for_existing_asset(self): number_of_depreciations_booked=1, opening_accumulated_depreciation=50000, expected_value_after_useful_life=10000, - depreciation_start_date="2030-12-31", + depreciation_start_date="2031-12-31", total_number_of_depreciations=3, frequency_of_depreciation=12, ) self.assertEqual(asset.status, "Draft") - expected_schedules = [["2030-12-31", 33333.50, 83333.50], ["2031-12-31", 6666.50, 90000.0]] + expected_schedules = [["2031-12-31", 33333.50, 83333.50], ["2032-12-31", 6666.50, 90000.0]] schedules = [ [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] From 1b69b3722912aa2403a779a613e714695b6ff615 Mon Sep 17 00:00:00 2001 From: Trusted Computer <75872475+trustedcomputer@users.noreply.github.com> Date: Mon, 12 Jun 2023 02:09:41 -0700 Subject: [PATCH 04/12] fix: CSS not applied to product title (backport #35582) (#35635) In an erpnext website, the /all-products route shows website items that have been published to the web site. In the list view (erpnext/e_commerce/product_ui/list.js), the css class is null for the product title. Instead, inline style statements have been added in that can not be modified by overriding CSS. This fix uses a similar approach to that which is taken in the grid view (erpnext/e_commerce/product_ui/grid.js). It removes the null CSS parameter in the product title link as well as the inline style statement. Then, as in the grid view, the product title is wrapped in a div tag with the product_title CSS class. This makes it possible to style the product title as desired with a CSS override. --- erpnext/e_commerce/product_ui/list.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/e_commerce/product_ui/list.js b/erpnext/e_commerce/product_ui/list.js index 894a7cb3d871..c8fd7672c8e7 100644 --- a/erpnext/e_commerce/product_ui/list.js +++ b/erpnext/e_commerce/product_ui/list.js @@ -78,9 +78,10 @@ erpnext.ProductList = class { let title_html = `
`; title_html += ` `; @@ -201,4 +202,4 @@ erpnext.ProductList = class { } } -}; \ No newline at end of file +}; From 8e3636ff53636b1b79ee0207321e7c2bc5bb59ed Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 12 Jun 2023 18:44:56 +0530 Subject: [PATCH 05/12] fix: don't set default payment amount in case of invoice return (backport #35645) (#35648) * fix: don't set default payment amount in case of invoice return (#35645) (cherry picked from commit 79483cc90eb71fa82d645b2cf54d9731502cf6c7) # Conflicts: # erpnext/public/js/controllers/taxes_and_totals.js * chore: fixing conflict --------- Co-authored-by: Anand Baburajan --- erpnext/public/js/controllers/taxes_and_totals.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index 7ea12a86b8a4..0bd24fc8f861 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -764,11 +764,13 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ precision("base_grand_total") ); } - this.frm.doc.payments.find(pay => { - if (pay.default) { - pay.amount = total_amount_to_pay; - } - }); + if(!this.frm.doc.is_return){ + this.frm.doc.payments.find(payment => { + if (payment.default) { + payment.amount = total_amount_to_pay; + } + }); + } this.frm.refresh_fields(); }, From 4a35ff0e5763d4a124de0d84be6a1dce288e1a37 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 13 Jun 2023 12:03:55 +0530 Subject: [PATCH 06/12] fix: savepoint policy assignment submission, log errors & inform the user about failures (#35507) --- .../leave_policy_assignment.py | 48 ++++++++++++++++--- .../leave_policy_assignment_list.js | 2 +- 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py index 2330541813e3..83c0b4dceba4 100644 --- a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py +++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py @@ -8,7 +8,15 @@ import frappe from frappe import _, bold from frappe.model.document import Document -from frappe.utils import date_diff, flt, formatdate, get_last_day, get_link_to_form, getdate +from frappe.utils import ( + comma_and, + date_diff, + flt, + formatdate, + get_last_day, + get_link_to_form, + getdate, +) from six import string_types @@ -207,7 +215,6 @@ def add_current_month_if_applicable(months_passed, date_of_joining, based_on_doj @frappe.whitelist() def create_assignment_for_multiple_employees(employees, data): - if isinstance(employees, string_types): employees = json.loads(employees) @@ -215,6 +222,8 @@ def create_assignment_for_multiple_employees(employees, data): data = frappe._dict(json.loads(data)) docs_name = [] + failed = [] + for employee in employees: assignment = frappe.new_doc("Leave Policy Assignment") assignment.employee = employee @@ -225,18 +234,45 @@ def create_assignment_for_multiple_employees(employees, data): assignment.leave_period = data.leave_period or None assignment.carry_forward = data.carry_forward assignment.save() + + savepoint = "before_assignment_submission" + try: + frappe.db.savepoint(savepoint) assignment.submit() - except frappe.exceptions.ValidationError: - continue - - frappe.db.commit() + except Exception as e: + frappe.db.rollback(save_point=savepoint) + frappe.log_error(title=f"Leave Policy Assignment submission failed for {assignment.name}") + failed.append(assignment.name) docs_name.append(assignment.name) + if failed: + show_assignment_submission_status(failed) + return docs_name +def show_assignment_submission_status(failed): + frappe.clear_messages() + assignment_list = [get_link_to_form("Leave Policy Assignment", entry) for entry in failed] + + msg = _("Failed to submit some leave policy assignments:") + msg += " " + comma_and(assignment_list, False) + "
" + msg += ( + _("Check {0} for more details") + .format("{0}") + .format(_("Error Log")) + ) + + frappe.msgprint( + msg, + indicator="red", + title=_("Submission Failed"), + is_minimizable=True, + ) + + def get_leave_type_details(): leave_type_details = frappe._dict() leave_types = frappe.get_all( diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_list.js b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_list.js index 6b75817cba99..fd038ded691f 100644 --- a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_list.js +++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_list.js @@ -52,7 +52,7 @@ frappe.listview_settings['Leave Policy Assignment'] = { get_query() { let filters = {"is_active": 1}; if (cur_dialog.fields_dict.company.value) - filters["company"] = cur_dialog.fields_dict.company.value; + filters["company"] = cur_dialog?.fields_dict?.company?.value; return { filters: filters From 986a90efe05661ec80db7b6e3cde82a698b58438 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 4 May 2023 04:34:47 +0530 Subject: [PATCH 07/12] perf: refactor `get_all_nodes` in Org Chart --- .../organizational_chart.py | 38 ++++++++++--------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/erpnext/hr/page/organizational_chart/organizational_chart.py b/erpnext/hr/page/organizational_chart/organizational_chart.py index 3674912dc061..717b56138e5b 100644 --- a/erpnext/hr/page/organizational_chart/organizational_chart.py +++ b/erpnext/hr/page/organizational_chart/organizational_chart.py @@ -1,4 +1,5 @@ import frappe +from frappe.query_builder.functions import Count @frappe.whitelist() @@ -15,31 +16,34 @@ def get_children(parent=None, company=None, exclude_node=None): if exclude_node: filters.append(["name", "!=", exclude_node]) - employees = frappe.get_list( + employees = frappe.get_all( "Employee", - fields=["employee_name as name", "name as id", "reports_to", "image", "designation as title"], + fields=[ + "employee_name as name", + "name as id", + "lft", + "rgt", + "reports_to", + "image", + "designation as title", + ], filters=filters, order_by="name", ) for employee in employees: - is_expandable = frappe.db.count("Employee", filters={"reports_to": employee.get("id")}) - employee.connections = get_connections(employee.id) - employee.expandable = 1 if is_expandable else 0 + employee.connections = get_connections(employee.id, employee.lft, employee.rgt) + employee.expandable = bool(employee.connections) return employees -def get_connections(employee): - num_connections = 0 +def get_connections(employee: str, lft: int, rgt: int) -> int: + Employee = frappe.qb.DocType("Employee") + query = ( + frappe.qb.from_(Employee) + .select(Count(Employee.name)) + .where((Employee.lft > lft) & (Employee.rgt < rgt)) + ).run() - nodes_to_expand = frappe.get_list("Employee", filters=[["reports_to", "=", employee]]) - num_connections += len(nodes_to_expand) - - while nodes_to_expand: - parent = nodes_to_expand.pop(0) - descendants = frappe.get_list("Employee", filters=[["reports_to", "=", parent.name]]) - num_connections += len(descendants) - nodes_to_expand.extend(descendants) - - return num_connections + return query[0][0] From a24d488817258a44718efb46a27517384e3d9991 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 13 Jun 2023 12:29:53 +0530 Subject: [PATCH 08/12] test: get children for org chart --- .../test_organizational_chart.py | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 erpnext/hr/page/organizational_chart/test_organizational_chart.py diff --git a/erpnext/hr/page/organizational_chart/test_organizational_chart.py b/erpnext/hr/page/organizational_chart/test_organizational_chart.py new file mode 100644 index 000000000000..1c3c24845c59 --- /dev/null +++ b/erpnext/hr/page/organizational_chart/test_organizational_chart.py @@ -0,0 +1,52 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +import unittest + +import frappe +from frappe.tests.utils import FrappeTestCase + +from erpnext.hr.doctype.employee.test_employee import make_employee +from erpnext.hr.page.organizational_chart.organizational_chart import get_children + + +class TestOrganizationalChart(FrappeTestCase): + def setUp(self): + self.company = create_company("Test Org Chart").name + frappe.db.delete("Employee", {"company": self.company}) + + def test_get_children(self): + company = create_company("Test Org Chart").name + emp1 = make_employee("testemp1@mail.com", company=self.company) + emp2 = make_employee("testemp2@mail.com", company=self.company, reports_to=emp1) + emp3 = make_employee("testemp3@mail.com", company=self.company, reports_to=emp1) + make_employee("testemp4@mail.com", company=self.company, reports_to=emp2) + + # root node + children = get_children(company=self.company) + self.assertEqual(len(children), 1) + self.assertEqual(children[0].id, emp1) + self.assertEqual(children[0].connections, 3) + + # root's children + children = get_children(parent=emp1, company=self.company) + self.assertEqual(len(children), 2) + self.assertEqual(children[0].id, emp2) + self.assertEqual(children[0].connections, 1) + self.assertEqual(children[1].id, emp3) + self.assertEqual(children[1].connections, 0) + + +def create_company(name): + if frappe.db.exists("Company", name): + return frappe.get_doc("Company", name) + + company = frappe.new_doc("Company") + company.update( + { + "company_name": name, + "default_currency": "USD", + "country": "United States", + } + ) + return company.insert() From 4f79214ae693371a04d5d099aee460b111074aa9 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 13 Jun 2023 19:24:21 +0530 Subject: [PATCH 09/12] Stock aging report fix when called in dashboard chart (backport #35671) (#35676) fix: get_range_age conditions fixed (#35671) see https://github.com/frappe/erpnext/issues/35669 (cherry picked from commit 9f669d4c2f18613b0a8e7b58074ce0aa3438037c) Co-authored-by: Hossein Yousefian <86075967+ihosseinu@users.noreply.github.com> --- erpnext/stock/report/stock_ageing/stock_ageing.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/report/stock_ageing/stock_ageing.py b/erpnext/stock/report/stock_ageing/stock_ageing.py index 7c430e491abd..cdddaac64b45 100644 --- a/erpnext/stock/report/stock_ageing/stock_ageing.py +++ b/erpnext/stock/report/stock_ageing/stock_ageing.py @@ -96,14 +96,14 @@ def get_range_age(filters: Filters, fifo_queue: List, to_date: str, item_dict: D range1 = range2 = range3 = above_range3 = 0.0 for item in fifo_queue: - age = date_diff(to_date, item[1]) + age = flt(date_diff(to_date, item[1])) qty = flt(item[0]) if not item_dict["has_serial_no"] else 1.0 - if age <= filters.range1: + if age <= flt(filters.range1): range1 = flt(range1 + qty, precision) - elif age <= filters.range2: + elif age <= flt(filters.range2): range2 = flt(range2 + qty, precision) - elif age <= filters.range3: + elif age <= flt(filters.range3): range3 = flt(range3 + qty, precision) else: above_range3 = flt(above_range3 + qty, precision) From 4c2c037a860abf33267e9a3841b6e996420dfadd Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 13 Jun 2023 21:38:16 +0530 Subject: [PATCH 10/12] fix: make showing taxes as table in print configurable (#35672) * fix: make showing taxes as table in print configurable (#35672) (cherry picked from commit 491a50a02766d833eec0b2cad8650ef495206a8e) # Conflicts: # erpnext/accounts/doctype/accounts_settings/accounts_settings.json * chore: fixing conflicts --------- Co-authored-by: Anand Baburajan --- .../doctype/accounts_settings/accounts_settings.json | 9 ++++++++- erpnext/controllers/accounts_controller.py | 3 +++ erpnext/controllers/print_settings.py | 8 +++++++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index 4dd503097bc8..d3b780b56f65 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -40,6 +40,7 @@ "submit_journal_entries", "print_settings", "show_inclusive_tax_in_print", + "show_taxes_as_table_in_print", "column_break_12", "show_payment_schedule_in_print", "currency_exchange_section", @@ -293,6 +294,12 @@ "fieldname": "book_tax_discount_loss", "fieldtype": "Check", "label": "Book Tax Loss on Early Payment Discount" + }, + { + "default": "0", + "fieldname": "show_taxes_as_table_in_print", + "fieldtype": "Check", + "label": "Show Taxes as Table in Print" } ], "icon": "icon-cog", @@ -300,7 +307,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2023-04-14 17:22:03.680886", + "modified": "2023-06-13 18:47:46.430291", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 225c1b01c20c..58ce1be9eba1 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -891,6 +891,9 @@ def is_inclusive_tax(self): return is_inclusive + def should_show_taxes_as_table_in_print(self): + return cint(frappe.db.get_single_value("Accounts Settings", "show_taxes_as_table_in_print")) + def validate_advance_entries(self): order_field = "sales_order" if self.doctype == "Sales Invoice" else "purchase_order" order_list = list(set(d.get(order_field) for d in self.get("items") if d.get(order_field))) diff --git a/erpnext/controllers/print_settings.py b/erpnext/controllers/print_settings.py index d2c80961a369..c951154a9e04 100644 --- a/erpnext/controllers/print_settings.py +++ b/erpnext/controllers/print_settings.py @@ -30,10 +30,16 @@ def set_print_templates_for_taxes(doc, settings): doc.print_templates.update( { "total": "templates/print_formats/includes/total.html", - "taxes": "templates/print_formats/includes/taxes.html", } ) + if not doc.should_show_taxes_as_table_in_print(): + doc.print_templates.update( + { + "taxes": "templates/print_formats/includes/taxes.html", + } + ) + def format_columns(display_columns, compact_fields): compact_fields = compact_fields + ["image", "item_code", "item_name"] From 4d4f218175360e7215f604e526397d615ea5164e Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 13 Jun 2023 23:01:51 +0530 Subject: [PATCH 11/12] fix(accounts): validate payment entry references with latest data. (backport #31166) (#35674) * fix(accounts): validate payment entry references with latest data. (#31166) * test: payment entry over allocation. * fix: validate allocated_amount against latest outstanding amount. * fix: payment entry get outstanding documents for advance payments * fix: only fetch latest outstanding_amount. * fix: throw if reference is allocated * test: throw error if a reference has been partially allocated after inital creation. * chore: test name * fix: remove unused part of test * chore: linter * chore: more user friendly error messages * fix: only validate outstanding amount if partly paid and don't filter by cost center * chore: minor refactor for doc.cost_center Co-authored-by: Deepesh Garg --------- Co-authored-by: Anand Baburajan Co-authored-by: Deepesh Garg (cherry picked from commit 20de27d480b6c55fce0335cac15866d83b44acc1) # Conflicts: # erpnext/accounts/doctype/payment_entry/test_payment_entry.py * chore: resolve conflicts * chore: resolve more conflicts * chore: don't validate allocated amount in case of donation --------- Co-authored-by: Devin Slauenwhite Co-authored-by: Deepesh Garg Co-authored-by: Anand Baburajan --- .../doctype/payment_entry/payment_entry.py | 68 +++++++++++++++---- .../payment_entry/test_payment_entry.py | 24 +++++++ 2 files changed, 80 insertions(+), 12 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 82f993910453..90ae1d792c7f 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -150,19 +150,57 @@ def validate_payment_type_with_outstanding(self): ) def validate_allocated_amount(self): - for d in self.get("references"): + if self.payment_type == "Internal Transfer" or self.party_type in ("Donor"): + return + + latest_references = get_outstanding_reference_documents( + { + "posting_date": self.posting_date, + "company": self.company, + "party_type": self.party_type, + "payment_type": self.payment_type, + "party": self.party, + "party_account": self.paid_from if self.payment_type == "Receive" else self.paid_to, + } + ) + + # Group latest_references by (voucher_type, voucher_no) + latest_lookup = {} + for d in latest_references: + d = frappe._dict(d) + latest_lookup.update({(d.voucher_type, d.voucher_no): d}) + + for d in self.get("references").copy(): + latest = latest_lookup.get((d.reference_doctype, d.reference_name)) + + # The reference has already been fully paid + if not latest: + frappe.throw( + _("{0} {1} has already been fully paid.").format(d.reference_doctype, d.reference_name) + ) + # The reference has already been partly paid + elif ( + latest.outstanding_amount < latest.invoice_amount + and d.outstanding_amount != latest.outstanding_amount + ): + frappe.throw( + _( + "{0} {1} has already been partly paid. Please use the 'Get Outstanding Invoice' button to get the latest outstanding amount." + ).format(d.reference_doctype, d.reference_name) + ) + + d.outstanding_amount = latest.outstanding_amount + + fail_message = _("Row #{0}: Allocated Amount cannot be greater than outstanding amount.") + if (flt(d.allocated_amount)) > 0: if flt(d.allocated_amount) > flt(d.outstanding_amount): - frappe.throw( - _("Row #{0}: Allocated Amount cannot be greater than outstanding amount.").format(d.idx) - ) + frappe.throw(fail_message.format(d.idx)) # Check for negative outstanding invoices as well if flt(d.allocated_amount) < 0: if flt(d.allocated_amount) < flt(d.outstanding_amount): - frappe.throw( - _("Row #{0}: Allocated Amount cannot be greater than outstanding amount.").format(d.idx) - ) + frappe.throw(fail_message.format(d.idx)) def delink_advance_entry_references(self): for reference in self.references: @@ -391,7 +429,7 @@ def validate_paid_invoices(self): for k, v in no_oustanding_refs.items(): frappe.msgprint( _( - "{} - {} now have {} as they had no outstanding amount left before submitting the Payment Entry." + "{} - {} now has {} as it had no outstanding amount left before submitting the Payment Entry." ).format( _(k), frappe.bold(", ".join(d.reference_name for d in v)), @@ -1457,7 +1495,7 @@ def get_orders_to_be_billed( if voucher_type: doc = frappe.get_doc({"doctype": voucher_type}) condition = "" - if doc and hasattr(doc, "cost_center"): + if doc and hasattr(doc, "cost_center") and doc.cost_center: condition = " and cost_center='%s'" % cost_center orders = [] @@ -1503,9 +1541,15 @@ def get_orders_to_be_billed( order_list = [] for d in orders: - if not ( - flt(d.outstanding_amount) >= flt(filters.get("outstanding_amt_greater_than")) - and flt(d.outstanding_amount) <= flt(filters.get("outstanding_amt_less_than")) + if ( + filters + and filters.get("outstanding_amt_greater_than") + and filters.get("outstanding_amt_less_than") + and not ( + flt(filters.get("outstanding_amt_greater_than")) + <= flt(d.outstanding_amount) + <= flt(filters.get("outstanding_amt_less_than")) + ) ): continue diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index 740f62a360a7..0ea2678c4d37 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -999,6 +999,30 @@ def test_payment_entry_against_onhold_purchase_invoice(self): self.assertTrue("is on hold" in str(err.exception).lower()) + def test_duplicate_payment_entry_allocate_amount(self): + si = create_sales_invoice() + + pe_draft = get_payment_entry("Sales Invoice", si.name) + pe_draft.insert() + + pe = get_payment_entry("Sales Invoice", si.name) + pe.submit() + + self.assertRaises(frappe.ValidationError, pe_draft.submit) + + def test_duplicate_payment_entry_partial_allocate_amount(self): + si = create_sales_invoice() + + pe_draft = get_payment_entry("Sales Invoice", si.name) + pe_draft.insert() + + pe = get_payment_entry("Sales Invoice", si.name) + pe.received_amount = si.total / 2 + pe.references[0].allocated_amount = si.total / 2 + pe.submit() + + self.assertRaises(frappe.ValidationError, pe_draft.submit) + def create_payment_entry(**args): payment_entry = frappe.new_doc("Payment Entry") From c2bf8e35024292865f2db3b0bedb9d1c4ef1c854 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 14 Jun 2023 09:01:54 +0530 Subject: [PATCH 12/12] fix: Lower deduction certificate not getting applied (#35667) * fix: Lower deduction certificate not getting applied (#35667) (cherry picked from commit 937c0feefe302d0a63fca2bee84da1580f7b6e26) # Conflicts: # erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py * chore: Resolve conflicts --------- Co-authored-by: Deepesh Garg --- .../tax_withholding_category.py | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index 778bd0881e37..6c11e51b2676 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -5,7 +5,7 @@ import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils import cint, getdate +from frappe.utils import cint, flt, getdate class TaxWithholdingCategory(Document): @@ -520,8 +520,13 @@ def get_tds_amount_from_ldc(ldc, parties, pan_no, tax_details, posting_date, net tds_amount = 0 limit_consumed = frappe.db.get_value( "Purchase Invoice", - {"supplier": ("in", parties), "apply_tds": 1, "docstatus": 1}, - "sum(net_total)", + { + "supplier": ("in", parties), + "apply_tds": 1, + "docstatus": 1, + "posting_date": ("between", (ldc.valid_from, ldc.valid_upto)), + }, + "sum(tax_withholding_net_total)", ) if is_valid_certificate( @@ -535,10 +540,10 @@ def get_tds_amount_from_ldc(ldc, parties, pan_no, tax_details, posting_date, net def get_ltds_amount(current_amount, deducted_amount, certificate_limit, rate, tax_details): - if current_amount < (certificate_limit - deducted_amount): + if certificate_limit - flt(deducted_amount) - flt(current_amount) >= 0: return current_amount * rate / 100 else: - ltds_amount = certificate_limit - deducted_amount + ltds_amount = certificate_limit - flt(deducted_amount) tds_amount = current_amount - ltds_amount return ltds_amount * rate / 100 + tds_amount * tax_details.rate / 100 @@ -549,9 +554,9 @@ def is_valid_certificate( ): valid = False - if ( - getdate(valid_from) <= getdate(posting_date) <= getdate(valid_upto) - ) and certificate_limit > deducted_amount: + available_amount = flt(certificate_limit) - flt(deducted_amount) - flt(current_amount) + + if (getdate(valid_from) <= getdate(posting_date) <= getdate(valid_upto)) and available_amount > 0: valid = True return valid