From e1d31aad4d8f3d5276022732d5d35f0c77639df4 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 18 Jul 2023 17:36:38 +0530 Subject: [PATCH 01/30] fix: broken overallocation validation in payment entry In a multi term payment schedule, overallocation logic broke. Fixing it using individual term outstanding amount in references. this should work for the simple, one term payment schedule as well (cherry picked from commit f8d4b19cb987b267dbd18d6ef1ab52426d7f51d1) --- .../doctype/payment_entry/payment_entry.py | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 5011b9296cd..f4f31d957d1 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -182,10 +182,12 @@ class PaymentEntry(AccountsController): latest_lookup = {} for d in latest_references: d = frappe._dict(d) - latest_lookup.update({(d.voucher_type, d.voucher_no): d}) + latest_lookup.setdefault((d.voucher_type, d.voucher_no), frappe._dict())[d.payment_term] = d for d in self.get("references"): - latest = latest_lookup.get((d.reference_doctype, d.reference_name)) + latest = (latest_lookup.get((d.reference_doctype, d.reference_name)) or frappe._dict()).get( + d.payment_term + ) # The reference has already been fully paid if not latest: @@ -208,6 +210,18 @@ class PaymentEntry(AccountsController): if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(latest.outstanding_amount): frappe.throw(fail_message.format(d.idx)) + if d.payment_term and ( + (flt(d.allocated_amount)) > 0 + and flt(d.allocated_amount) > flt(latest.payment_term_outstanding) + ): + frappe.throw( + _( + "Row #{0}: Allocated amount:{1} is greater than outstanding amount:{2} for Payment Term {3}" + ).format( + d.idx, d.allocated_amount, latest.payment_term_outstanding, d.payment_term + ) + ) + # Check for negative outstanding invoices as well if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(latest.outstanding_amount): frappe.throw(fail_message.format(d.idx)) @@ -1466,6 +1480,7 @@ def split_invoices_based_on_payment_terms(outstanding_invoices): "posting_date": d.posting_date, "invoice_amount": flt(d.invoice_amount), "outstanding_amount": flt(d.outstanding_amount), + "payment_term_outstanding": flt(payment_term.outstanding), "payment_amount": payment_term.payment_amount, "payment_term": payment_term.payment_term, } @@ -2240,6 +2255,7 @@ def get_reference_as_per_payment_terms( "due_date": doc.get("due_date"), "total_amount": grand_total, "outstanding_amount": outstanding_amount, + "payment_term_outstanding": payment_term_outstanding, "payment_term": payment_term.payment_term, "allocated_amount": payment_term_outstanding, } From fcafff7ebe3d080a080ae2f0ae20b5722d68fa98 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 19 Jul 2023 09:26:39 +0530 Subject: [PATCH 02/30] refactor: payment term outstanding in party account currency (cherry picked from commit ee83f94bb0ad22e487d63e41cb878ffc27f974fc) --- .../doctype/payment_entry/payment_entry.py | 35 +++++++++++++++++-- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index f4f31d957d1..f29be181e7b 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1392,7 +1392,9 @@ def get_outstanding_reference_documents(args): accounting_dimensions=accounting_dimensions_filter, ) - outstanding_invoices = split_invoices_based_on_payment_terms(outstanding_invoices) + outstanding_invoices = split_invoices_based_on_payment_terms( + outstanding_invoices, args.get("company") + ) for d in outstanding_invoices: d["exchange_rate"] = 1 @@ -1451,8 +1453,27 @@ def get_outstanding_reference_documents(args): return data -def split_invoices_based_on_payment_terms(outstanding_invoices): +def split_invoices_based_on_payment_terms(outstanding_invoices, company): invoice_ref_based_on_payment_terms = {} + + company_currency = ( + frappe.db.get_value("Company", company, "default_currency") if company else None + ) + exc_rates = frappe._dict() + for doctype in ["Sales Invoice", "Purchase Invoice"]: + invoices = [x.voucher_no for x in outstanding_invoices if x.voucher_type == doctype] + for x in frappe.db.get_all( + doctype, + filters={"name": ["in", invoices]}, + fields=["name", "currency", "conversion_rate", "party_account_currency"], + ): + exc_rates[x.name] = frappe._dict( + conversion_rate=x.conversion_rate, + currency=x.currency, + party_account_currency=x.party_account_currency, + company_currency=company_currency, + ) + for idx, d in enumerate(outstanding_invoices): if d.voucher_type in ["Sales Invoice", "Purchase Invoice"]: payment_term_template = frappe.db.get_value( @@ -1469,6 +1490,14 @@ def split_invoices_based_on_payment_terms(outstanding_invoices): for payment_term in payment_schedule: if payment_term.outstanding > 0.1: + doc_details = exc_rates.get(payment_term.parent, None) + is_multi_currency_acc = (doc_details.currency != doc_details.company_currency) and ( + doc_details.party_account_currency != doc_details.company_currency + ) + payment_term_outstanding = flt(payment_term.outstanding) + if not is_multi_currency_acc: + payment_term_outstanding = doc_details.conversion_rate * flt(payment_term.outstanding) + invoice_ref_based_on_payment_terms.setdefault(idx, []) invoice_ref_based_on_payment_terms[idx].append( frappe._dict( @@ -1480,7 +1509,7 @@ def split_invoices_based_on_payment_terms(outstanding_invoices): "posting_date": d.posting_date, "invoice_amount": flt(d.invoice_amount), "outstanding_amount": flt(d.outstanding_amount), - "payment_term_outstanding": flt(payment_term.outstanding), + "payment_term_outstanding": payment_term_outstanding, "payment_amount": payment_term.payment_amount, "payment_term": payment_term.payment_term, } From 8ca3d6b7f3554617f8fb5131a7bd570418c1dac5 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 19 Jul 2023 13:17:12 +0530 Subject: [PATCH 03/30] fix: Default fiscal year in accounting, buying and sellingcharts (cherry picked from commit 3759a41b8358302ff8d33857a0461cc2da3d06d8) --- .../dashboard_chart/budget_variance/budget_variance.json | 5 +++-- .../dashboard_chart/profit_and_loss/profit_and_loss.json | 5 +++-- .../purchase_order_trends/purchase_order_trends.json | 7 ++++--- .../dashboard_chart/top_suppliers/top_suppliers.json | 5 +++-- erpnext/public/js/utils.js | 4 ++++ .../sales_order_trends/sales_order_trends.json | 7 ++++--- .../dashboard_chart/top_customers/top_customers.json | 5 +++-- 7 files changed, 24 insertions(+), 14 deletions(-) diff --git a/erpnext/accounts/dashboard_chart/budget_variance/budget_variance.json b/erpnext/accounts/dashboard_chart/budget_variance/budget_variance.json index 8631d3dc2a3..4883106227b 100644 --- a/erpnext/accounts/dashboard_chart/budget_variance/budget_variance.json +++ b/erpnext/accounts/dashboard_chart/budget_variance/budget_variance.json @@ -4,18 +4,19 @@ "creation": "2020-07-17 11:25:34.593061", "docstatus": 0, "doctype": "Dashboard Chart", - "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_fiscal_year\":\"frappe.sys_defaults.fiscal_year\",\"to_fiscal_year\":\"frappe.sys_defaults.fiscal_year\"}", + "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_fiscal_year\":\"erpnext.utils.get_fiscal_year()\",\"to_fiscal_year\":\"erpnext.utils.get_fiscal_year()\"}", "filters_json": "{\"period\":\"Monthly\",\"budget_against\":\"Cost Center\",\"show_cumulative\":0}", "idx": 0, "is_public": 1, "is_standard": 1, - "modified": "2020-07-22 12:24:49.144210", + "modified": "2023-07-19 13:13:13.307073", "modified_by": "Administrator", "module": "Accounts", "name": "Budget Variance", "number_of_groups": 0, "owner": "Administrator", "report_name": "Budget Variance Report", + "roles": [], "timeseries": 0, "type": "Bar", "use_report_chart": 1, diff --git a/erpnext/accounts/dashboard_chart/profit_and_loss/profit_and_loss.json b/erpnext/accounts/dashboard_chart/profit_and_loss/profit_and_loss.json index 3fa995bbe15..25caa44769b 100644 --- a/erpnext/accounts/dashboard_chart/profit_and_loss/profit_and_loss.json +++ b/erpnext/accounts/dashboard_chart/profit_and_loss/profit_and_loss.json @@ -4,18 +4,19 @@ "creation": "2020-07-17 11:25:34.448572", "docstatus": 0, "doctype": "Dashboard Chart", - "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_fiscal_year\":\"frappe.sys_defaults.fiscal_year\",\"to_fiscal_year\":\"frappe.sys_defaults.fiscal_year\"}", + "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_fiscal_year\":\"erpnext.utils.get_fiscal_year()\",\"to_fiscal_year\":\"erpnext.utils.get_fiscal_year()\"}", "filters_json": "{\"filter_based_on\":\"Fiscal Year\",\"period_start_date\":\"2020-04-01\",\"period_end_date\":\"2021-03-31\",\"periodicity\":\"Yearly\",\"include_default_book_entries\":1}", "idx": 0, "is_public": 1, "is_standard": 1, - "modified": "2020-07-22 12:33:48.888943", + "modified": "2023-07-19 13:08:56.470390", "modified_by": "Administrator", "module": "Accounts", "name": "Profit and Loss", "number_of_groups": 0, "owner": "Administrator", "report_name": "Profit and Loss Statement", + "roles": [], "timeseries": 0, "type": "Bar", "use_report_chart": 1, diff --git a/erpnext/buying/dashboard_chart/purchase_order_trends/purchase_order_trends.json b/erpnext/buying/dashboard_chart/purchase_order_trends/purchase_order_trends.json index 6452ed2139b..751796bbbb5 100644 --- a/erpnext/buying/dashboard_chart/purchase_order_trends/purchase_order_trends.json +++ b/erpnext/buying/dashboard_chart/purchase_order_trends/purchase_order_trends.json @@ -5,18 +5,19 @@ "custom_options": "{\"type\": \"line\", \"axisOptions\": {\"shortenYAxisNumbers\": 1}, \"tooltipOptions\": {}, \"lineOptions\": {\"regionFill\": 1}}", "docstatus": 0, "doctype": "Dashboard Chart", - "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"fiscal_year\":\"frappe.sys_defaults.fiscal_year\"}", + "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"fiscal_year\":\"erpnext.utils.get_fiscal_year()\"}", "filters_json": "{\"period\":\"Monthly\",\"period_based_on\":\"posting_date\",\"based_on\":\"Item\"}", - "idx": 0, + "idx": 1, "is_public": 1, "is_standard": 1, - "modified": "2020-07-21 16:13:25.092287", + "modified": "2023-07-19 13:06:42.937941", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order Trends", "number_of_groups": 0, "owner": "Administrator", "report_name": "Purchase Order Trends", + "roles": [], "timeseries": 0, "type": "Line", "use_report_chart": 1, diff --git a/erpnext/buying/dashboard_chart/top_suppliers/top_suppliers.json b/erpnext/buying/dashboard_chart/top_suppliers/top_suppliers.json index 6f7da8ea870..f6b97175398 100644 --- a/erpnext/buying/dashboard_chart/top_suppliers/top_suppliers.json +++ b/erpnext/buying/dashboard_chart/top_suppliers/top_suppliers.json @@ -4,18 +4,19 @@ "creation": "2020-07-20 21:01:02.329519", "docstatus": 0, "doctype": "Dashboard Chart", - "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"fiscal_year\":\"frappe.sys_defaults.fiscal_year\"}", + "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"fiscal_year\":\"erpnext.utils.get_fiscal_year()\"}", "filters_json": "{\"period\":\"Monthly\",\"period_based_on\":\"posting_date\",\"based_on\":\"Supplier\"}", "idx": 0, "is_public": 1, "is_standard": 1, - "modified": "2020-07-22 12:43:40.829652", + "modified": "2023-07-19 13:07:41.753556", "modified_by": "Administrator", "module": "Buying", "name": "Top Suppliers", "number_of_groups": 0, "owner": "Administrator", "report_name": "Purchase Receipt Trends", + "roles": [], "timeseries": 0, "type": "Bar", "use_report_chart": 1, diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index ec331289e9a..a6b4ea12bbe 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -352,6 +352,10 @@ $.extend(erpnext.utils, { }, get_fiscal_year: function(date) { + if(!date) { + date = frappe.datetime.get_today(); + } + let fiscal_year = ''; frappe.call({ method: "erpnext.accounts.utils.get_fiscal_year", diff --git a/erpnext/selling/dashboard_chart/sales_order_trends/sales_order_trends.json b/erpnext/selling/dashboard_chart/sales_order_trends/sales_order_trends.json index 914d915d944..2f668a865d7 100644 --- a/erpnext/selling/dashboard_chart/sales_order_trends/sales_order_trends.json +++ b/erpnext/selling/dashboard_chart/sales_order_trends/sales_order_trends.json @@ -5,18 +5,19 @@ "custom_options": "{\"type\": \"line\", \"axisOptions\": {\"shortenYAxisNumbers\": 1}, \"tooltipOptions\": {}, \"lineOptions\": {\"regionFill\": 1}}", "docstatus": 0, "doctype": "Dashboard Chart", - "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"fiscal_year\":\"frappe.sys_defaults.fiscal_year\"}", + "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"fiscal_year\":\"erpnext.utils.get_fiscal_year()\"}", "filters_json": "{\"period\":\"Monthly\",\"based_on\":\"Item\"}", - "idx": 0, + "idx": 1, "is_public": 1, "is_standard": 1, - "modified": "2020-07-22 16:24:45.726270", + "modified": "2023-07-19 13:09:45.341791", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order Trends", "number_of_groups": 0, "owner": "Administrator", "report_name": "Sales Order Trends", + "roles": [], "timeseries": 0, "type": "Line", "use_report_chart": 1, diff --git a/erpnext/selling/dashboard_chart/top_customers/top_customers.json b/erpnext/selling/dashboard_chart/top_customers/top_customers.json index 59a2ba37ddf..2972980967c 100644 --- a/erpnext/selling/dashboard_chart/top_customers/top_customers.json +++ b/erpnext/selling/dashboard_chart/top_customers/top_customers.json @@ -5,18 +5,19 @@ "custom_options": "", "docstatus": 0, "doctype": "Dashboard Chart", - "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"fiscal_year\":\"frappe.sys_defaults.fiscal_year\"}", + "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"fiscal_year\":\"erpnext.utils.get_fiscal_year()\"}", "filters_json": "{\"period\":\"Yearly\",\"based_on\":\"Customer\"}", "idx": 0, "is_public": 1, "is_standard": 1, - "modified": "2020-07-22 17:03:10.320147", + "modified": "2023-07-19 13:14:20.151502", "modified_by": "Administrator", "module": "Selling", "name": "Top Customers", "number_of_groups": 0, "owner": "Administrator", "report_name": "Delivery Note Trends", + "roles": [], "timeseries": 0, "type": "Bar", "use_report_chart": 1, From ca4f86d5af917a67042df6d78ec264d2a78e5fef Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 20 Jul 2023 17:51:54 +0530 Subject: [PATCH 04/30] fix: Trial Balance report considering cancelled entries (cherry picked from commit fd58bbff6bbed96367544f0ab0be9d992fafef56) --- .../period_closing_voucher.py | 45 +++++++++---------- .../report/trial_balance/trial_balance.py | 3 ++ 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py index 922722f04d3..49472484ef4 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py @@ -126,23 +126,22 @@ class PeriodClosingVoucher(AccountsController): def make_gl_entries(self, get_opening_entries=False): gl_entries = self.get_gl_entries() closing_entries = self.get_grouped_gl_entries(get_opening_entries=get_opening_entries) - if gl_entries: - if len(gl_entries) > 5000: - frappe.enqueue( - process_gl_entries, - gl_entries=gl_entries, - closing_entries=closing_entries, - voucher_name=self.name, - company=self.company, - closing_date=self.posting_date, - queue="long", - ) - frappe.msgprint( - _("The GL Entries will be processed in the background, it can take a few minutes."), - alert=True, - ) - else: - process_gl_entries(gl_entries, closing_entries, self.name, self.company, self.posting_date) + if len(gl_entries) > 5000: + frappe.enqueue( + process_gl_entries, + gl_entries=gl_entries, + closing_entries=closing_entries, + voucher_name=self.name, + company=self.company, + closing_date=self.posting_date, + queue="long", + ) + frappe.msgprint( + _("The GL Entries will be processed in the background, it can take a few minutes."), + alert=True, + ) + else: + process_gl_entries(gl_entries, closing_entries, self.name, self.company, self.posting_date) def get_grouped_gl_entries(self, get_opening_entries=False): closing_entries = [] @@ -330,17 +329,15 @@ def process_gl_entries(gl_entries, closing_entries, voucher_name, company, closi from erpnext.accounts.general_ledger import make_gl_entries try: - make_gl_entries(gl_entries, merge_entries=False) + if gl_entries: + make_gl_entries(gl_entries, merge_entries=False) + make_closing_entries(gl_entries + closing_entries, voucher_name, company, closing_date) - frappe.db.set_value( - "Period Closing Voucher", gl_entries[0].get("voucher_no"), "gle_processing_status", "Completed" - ) + frappe.db.set_value("Period Closing Voucher", voucher_name, "gle_processing_status", "Completed") except Exception as e: frappe.db.rollback() frappe.log_error(e) - frappe.db.set_value( - "Period Closing Voucher", gl_entries[0].get("voucher_no"), "gle_processing_status", "Failed" - ) + frappe.db.set_value("Period Closing Voucher", voucher_name, "gle_processing_status", "Failed") def make_reverse_gl_entries(voucher_type, voucher_no): diff --git a/erpnext/accounts/report/trial_balance/trial_balance.py b/erpnext/accounts/report/trial_balance/trial_balance.py index 39917f90c93..599c8a312a5 100644 --- a/erpnext/accounts/report/trial_balance/trial_balance.py +++ b/erpnext/accounts/report/trial_balance/trial_balance.py @@ -231,6 +231,9 @@ def get_opening_balance( (closing_balance.posting_date < filters.from_date) | (closing_balance.is_opening == "Yes") ) + if doctype == "GL Entry": + opening_balance = opening_balance.where(closing_balance.is_cancelled == 0) + if ( not filters.show_unclosed_fy_pl_balances and report_type == "Profit and Loss" From 57cf3c28f896bf619563917d63b6f718ef5c8cbe Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 20 Jul 2023 20:11:05 +0530 Subject: [PATCH 05/30] fix: made item or warehouse filter mandatory (backport #36208) (#36215) fix: made item or warehouse filter mandatory (cherry picked from commit 16498627cec1e416fcc9a911b11ca522268eb622) Co-authored-by: Rohit Waghchaure --- .../batch_wise_balance_history.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py index 0d57938e31f..0f319554763 100644 --- a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py +++ b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py @@ -9,11 +9,18 @@ from pypika import functions as fn from erpnext.stock.doctype.warehouse.warehouse import apply_warehouse_filter +SLE_COUNT_LIMIT = 10_000 + def execute(filters=None): if not filters: filters = {} + sle_count = frappe.db.count("Stock Ledger Entry", {"is_cancelled": 0}) + + if sle_count > SLE_COUNT_LIMIT and not filters.get("item_code") and not filters.get("warehouse"): + frappe.throw(_("Please select either the Item or Warehouse filter to generate the report.")) + if filters.from_date > filters.to_date: frappe.throw(_("From Date must be before To Date")) From 844ec58f6bc69c70606032aa1c02366f19656f79 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 20 Jul 2023 20:15:36 +0530 Subject: [PATCH 06/30] fix: Ambiguous column error while submitting stock entry (backport #36209) (#36222) fix: Ambiguous column error while submitting stock entry Stock Entry Type=Manufacture request.js:457 Traceback (most recent call last): File "apps/frappe/frappe/app.py", line 94, in application response = frappe.api.handle() File "apps/frappe/frappe/api.py", line 54, in handle return frappe.handler.handle() File "apps/frappe/frappe/handler.py", line 47, in handle data = execute_cmd(cmd) File "apps/frappe/frappe/handler.py", line 85, in execute_cmd return frappe.call(method, **frappe.form_dict) File "apps/frappe/frappe/__init__.py", line 1610, in call return fn(*args, **newargs) File "apps/frappe/frappe/desk/form/save.py", line 28, in savedocs doc.save() File "apps/frappe/frappe/model/document.py", line 305, in save return self._save(*args, **kwargs) File "apps/frappe/frappe/model/document.py", line 327, in _save return self.insert() File "apps/frappe/frappe/model/document.py", line 259, in insert self.run_before_save_methods() File "apps/frappe/frappe/model/document.py", line 1045, in run_before_save_methods self.run_method("validate") File "apps/frappe/frappe/model/document.py", line 914, in run_method out = Document.hook(fn)(self, *args, **kwargs) File "apps/frappe/frappe/model/document.py", line 1264, in composer return composed(self, method, *args, **kwargs) File "apps/frappe/frappe/model/document.py", line 1246, in runner add_to_return_value(self, fn(self, *args, **kwargs)) File "apps/frappe/frappe/model/document.py", line 911, in fn return method_object(*args, **kwargs) File "apps/erpnext/erpnext/stock/doctype/stock_entry/stock_entry.py", line 122, in validate self.validate_qty() File "apps/erpnext/erpnext/stock/doctype/stock_entry/stock_entry.py", line 433, in validate_qty transferred_materials = frappe.db.sql( File "apps/frappe/frappe/database/database.py", line 220, in sql self._cursor.execute(query, values) File "env/lib/python3.10/site-packages/pymysql/cursors.py", line 158, in execute result = self._query(query) File "env/lib/python3.10/site-packages/pymysql/cursors.py", line 325, in _query conn.query(q) File "env/lib/python3.10/site-packages/pymysql/connections.py", line 549, in query self._affected_rows = self._read_query_result(unbuffered=unbuffered) File "env/lib/python3.10/site-packages/pymysql/connections.py", line 779, in _read_query_result result.read() File "env/lib/python3.10/site-packages/pymysql/connections.py", line 1157, in read first_packet = self.connection._read_packet() File "env/lib/python3.10/site-packages/pymysql/connections.py", line 729, in _read_packet packet.raise_for_error() File "env/lib/python3.10/site-packages/pymysql/protocol.py", line 221, in raise_for_error err.raise_mysql_exception(self._data) File "env/lib/python3.10/site-packages/pymysql/err.py", line 143, in raise_mysql_exception raise errorclass(errno, errval) pymysql.err.OperationalError: (1052, "Column 'qty' in field list is ambiguous") (cherry picked from commit c21fd45883d3b2a6d2ba32c5f91d5151c8df0650) Co-authored-by: MohsinAli --- erpnext/stock/doctype/stock_entry/stock_entry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index ae9adc826c6..b93ffc437cb 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -433,7 +433,7 @@ class StockEntry(StockController): transferred_materials = frappe.db.sql( """ select - sum(qty) as qty + sum(sed.qty) as qty from `tabStock Entry` se,`tabStock Entry Detail` sed where se.name = sed.parent and se.docstatus=1 and From c2976ef6430385391aa939e18c86984e43c49b1e Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 2 Jun 2023 16:57:00 +0530 Subject: [PATCH 07/30] fix: Ignore permissions while submitting account closing balance record (#35536) (cherry picked from commit f11d9b019ddf8238c9e53369262a89fb4041e0ab) --- .../doctype/account_closing_balance/account_closing_balance.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/doctype/account_closing_balance/account_closing_balance.py b/erpnext/accounts/doctype/account_closing_balance/account_closing_balance.py index ea67051fb49..e75af7047f1 100644 --- a/erpnext/accounts/doctype/account_closing_balance/account_closing_balance.py +++ b/erpnext/accounts/doctype/account_closing_balance/account_closing_balance.py @@ -36,6 +36,7 @@ def make_closing_entries(closing_entries, voucher_name, company, closing_date): "closing_date": closing_date, } ) + cle.flags.ignore_permissions = True cle.submit() From 6a6a3ae3c3b8c8c52788cdbab0ca265e0621da46 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 21 Jul 2023 16:03:17 +0530 Subject: [PATCH 08/30] fix: FY in naming series variable for orders (cherry picked from commit 7a7d32db817ab6619168ad3582abe23c197d6419) --- erpnext/accounts/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 84c71b376ef..aa861a48eff 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -1108,7 +1108,8 @@ def get_autoname_with_number(number_value, doc_title, company): def parse_naming_series_variable(doc, variable): if variable == "FY": - return get_fiscal_year(date=doc.get("posting_date"), company=doc.get("company"))[0] + date = doc.get("posting_date") or doc.get("transaction_date") or getdate() + return get_fiscal_year(date=date, company=doc.get("company"))[0] @frappe.whitelist() From 2f4d8e1e94345c013c85040f8d657b63f60f3f7c Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 20 Jul 2023 09:08:55 +0530 Subject: [PATCH 09/30] test: overallocation validation in payment entry (cherry picked from commit e7e3853f819d6e50692e779da9ca90a0038c5564) --- .../payment_entry/test_payment_entry.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index ae2625b6539..dfae979ccd9 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -1061,6 +1061,26 @@ class TestPaymentEntry(FrappeTestCase): } self.assertDictEqual(ref_details, expected_response) + def test_overallocation_validation_on_payment_terms(self): + si = create_sales_invoice(do_not_save=1, qty=1, rate=200) + create_payment_terms_template() + si.payment_terms_template = "Test Receivable Template" + si.save().submit() + + si.reload() + si.payment_schedule[0].payment_amount + + pe = get_payment_entry(si.doctype, si.name).save() + # Allocated amount should be according to the payment schedule + for idx, schedule in enumerate(si.payment_schedule): + with self.subTest(idx=idx): + self.assertEqual(schedule.payment_amount, pe.references[idx].allocated_amount) + pe.paid_amount = 400 + pe.references[0].allocated_amount = 200 + pe.references[1].allocated_amount = 200 + + self.assertRaises(frappe.ValidationError, pe.save) + def create_payment_entry(**args): payment_entry = frappe.new_doc("Payment Entry") From f3295a9f594515f055181601835d579f5e27932d Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 20 Jul 2023 21:19:29 +0530 Subject: [PATCH 10/30] chore: test more scenarios (cherry picked from commit 6b4a81ee482d4a2c6ce32ab69a3b42cfd5496b60) --- .../payment_entry/test_payment_entry.py | 85 +++++++++++++++++-- 1 file changed, 77 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index dfae979ccd9..19a4af62ced 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -1061,25 +1061,94 @@ class TestPaymentEntry(FrappeTestCase): } self.assertDictEqual(ref_details, expected_response) + @change_settings( + "Accounts Settings", + {"unlink_payment_on_cancellation_of_invoice": 1, "delete_linked_ledger_entries": 1}, + ) def test_overallocation_validation_on_payment_terms(self): - si = create_sales_invoice(do_not_save=1, qty=1, rate=200) + """ + Validate Allocation on Payment Entry based on Payment Schedule. Upon overallocation, validation error must be thrown. + + """ create_payment_terms_template() - si.payment_terms_template = "Test Receivable Template" - si.save().submit() - si.reload() - si.payment_schedule[0].payment_amount + # Validate allocation on base/company currency + si1 = create_sales_invoice(do_not_save=1, qty=1, rate=200) + si1.payment_terms_template = "Test Receivable Template" + si1.save().submit() - pe = get_payment_entry(si.doctype, si.name).save() + si1.reload() + pe = get_payment_entry(si1.doctype, si1.name).save() # Allocated amount should be according to the payment schedule - for idx, schedule in enumerate(si.payment_schedule): + for idx, schedule in enumerate(si1.payment_schedule): with self.subTest(idx=idx): self.assertEqual(schedule.payment_amount, pe.references[idx].allocated_amount) + pe.save() + + # Overallocation validation should trigger pe.paid_amount = 400 pe.references[0].allocated_amount = 200 pe.references[1].allocated_amount = 200 - self.assertRaises(frappe.ValidationError, pe.save) + pe.delete() + si1.cancel() + si1.delete() + + # Validate allocation on foreign currency + si2 = create_sales_invoice( + customer="_Test Customer USD", + debit_to="_Test Receivable USD - _TC", + currency="USD", + conversion_rate=80, + do_not_save=1, + ) + si2.payment_terms_template = "Test Receivable Template" + si2.save().submit() + + si2.reload() + pe = get_payment_entry(si2.doctype, si2.name).save() + # Allocated amount should be according to the payment schedule + for idx, schedule in enumerate(si2.payment_schedule): + with self.subTest(idx=idx): + self.assertEqual(schedule.payment_amount, pe.references[idx].allocated_amount) + pe.save() + + # Overallocation validation should trigger + pe.paid_amount = 200 + pe.references[0].allocated_amount = 100 + pe.references[1].allocated_amount = 100 + self.assertRaises(frappe.ValidationError, pe.save) + pe.delete() + si2.cancel() + si2.delete() + + # Validate allocation in base/company currency on a foreign currency document + # when invoice is made is foreign currency, but posted to base/company currency account + si3 = create_sales_invoice( + customer="_Test Customer USD", + currency="USD", + conversion_rate=80, + do_not_save=1, + ) + si3.payment_terms_template = "Test Receivable Template" + si3.save().submit() + + si3.reload() + pe = get_payment_entry(si3.doctype, si3.name).save() + # Allocated amount should be according to the payment schedule + for idx, schedule in enumerate(si3.payment_schedule): + with self.subTest(idx=idx): + self.assertEqual(schedule.payment_amount, pe.references[idx].allocated_amount) + pe.save() + + # Overallocation validation should trigger + pe.paid_amount = 400 + pe.references[0].allocated_amount = 200 + pe.references[1].allocated_amount = 200 + self.assertRaises(frappe.ValidationError, pe.save) + # pe.delete() + # si3.cancel() + # si3.delete() def create_payment_entry(**args): From 602f0769c4a39c56e0fc0d54203be12237e5e329 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sat, 22 Jul 2023 08:15:03 +0530 Subject: [PATCH 11/30] chore: use flt for currency (cherry picked from commit 5b37919574ac6b3454c99faffe1b42ceaca0f74e) --- .../payment_entry/test_payment_entry.py | 37 +++++++++++++------ 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index 19a4af62ced..38dadbe5eb7 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -1070,6 +1070,7 @@ class TestPaymentEntry(FrappeTestCase): Validate Allocation on Payment Entry based on Payment Schedule. Upon overallocation, validation error must be thrown. """ + customer = create_customer() create_payment_terms_template() # Validate allocation on base/company currency @@ -1082,7 +1083,7 @@ class TestPaymentEntry(FrappeTestCase): # Allocated amount should be according to the payment schedule for idx, schedule in enumerate(si1.payment_schedule): with self.subTest(idx=idx): - self.assertEqual(schedule.payment_amount, pe.references[idx].allocated_amount) + self.assertEqual(flt(schedule.payment_amount), flt(pe.references[idx].allocated_amount)) pe.save() # Overallocation validation should trigger @@ -1110,7 +1111,7 @@ class TestPaymentEntry(FrappeTestCase): # Allocated amount should be according to the payment schedule for idx, schedule in enumerate(si2.payment_schedule): with self.subTest(idx=idx): - self.assertEqual(schedule.payment_amount, pe.references[idx].allocated_amount) + self.assertEqual(flt(schedule.payment_amount), flt(pe.references[idx].allocated_amount)) pe.save() # Overallocation validation should trigger @@ -1123,9 +1124,9 @@ class TestPaymentEntry(FrappeTestCase): si2.delete() # Validate allocation in base/company currency on a foreign currency document - # when invoice is made is foreign currency, but posted to base/company currency account + # when invoice is made is foreign currency, but posted to base/company currency debtors account si3 = create_sales_invoice( - customer="_Test Customer USD", + customer=customer, currency="USD", conversion_rate=80, do_not_save=1, @@ -1138,17 +1139,17 @@ class TestPaymentEntry(FrappeTestCase): # Allocated amount should be according to the payment schedule for idx, schedule in enumerate(si3.payment_schedule): with self.subTest(idx=idx): - self.assertEqual(schedule.payment_amount, pe.references[idx].allocated_amount) + self.assertEqual(flt(schedule.base_payment_amount), flt(pe.references[idx].allocated_amount)) pe.save() # Overallocation validation should trigger - pe.paid_amount = 400 - pe.references[0].allocated_amount = 200 - pe.references[1].allocated_amount = 200 + pe.paid_amount = 16000 + pe.references[0].allocated_amount = 8000 + pe.references[1].allocated_amount = 8000 self.assertRaises(frappe.ValidationError, pe.save) - # pe.delete() - # si3.cancel() - # si3.delete() + pe.delete() + si3.cancel() + si3.delete() def create_payment_entry(**args): @@ -1239,3 +1240,17 @@ def create_payment_terms_template_with_discount( def create_payment_term(name): if not frappe.db.exists("Payment Term", name): frappe.get_doc({"doctype": "Payment Term", "payment_term_name": name}).insert() + + +def create_customer(name="_Test Customer 2 USD", currency="USD"): + customer = None + if frappe.db.exists("Customer", name): + customer = name + else: + customer = frappe.new_doc("Customer") + customer.customer_name = name + customer.default_currency = currency + customer.type = "Individual" + customer.save() + customer = customer.name + return customer From bc7ab2f787d89077e000baf59e6f6b15869ba2b7 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sat, 22 Jul 2023 09:16:03 +0530 Subject: [PATCH 12/30] chore: validation on multi-currency tran on company curtency account (cherry picked from commit 8f9ef4ef5b411d4debe460b607ccb78c6490462a) --- .../accounts/doctype/payment_entry/test_payment_entry.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index 38dadbe5eb7..bb21e206042 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -1136,10 +1136,11 @@ class TestPaymentEntry(FrappeTestCase): si3.reload() pe = get_payment_entry(si3.doctype, si3.name).save() - # Allocated amount should be according to the payment schedule - for idx, schedule in enumerate(si3.payment_schedule): + # Allocated amount should be equal to payment term outstanding + self.assertEqual(len(pe.references), 2) + for idx, ref in enumerate(pe.references): with self.subTest(idx=idx): - self.assertEqual(flt(schedule.base_payment_amount), flt(pe.references[idx].allocated_amount)) + self.assertEqual(ref.payment_term_outstanding, ref.allocated_amount) pe.save() # Overallocation validation should trigger From 25301881c940ddb3bdbe04c776d92604a5c3692d Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sat, 22 Jul 2023 10:01:59 +0530 Subject: [PATCH 13/30] chore(test): enable multi-currency party for testing (cherry picked from commit 93246043ec092268d9b011e024bedf8b008993a1) --- .../accounts/doctype/payment_entry/test_payment_entry.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index bb21e206042..785b8a180b1 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -1063,7 +1063,11 @@ class TestPaymentEntry(FrappeTestCase): @change_settings( "Accounts Settings", - {"unlink_payment_on_cancellation_of_invoice": 1, "delete_linked_ledger_entries": 1}, + { + "unlink_payment_on_cancellation_of_invoice": 1, + "delete_linked_ledger_entries": 1, + "allow_multi_currency_invoices_against_single_party_account": 1, + }, ) def test_overallocation_validation_on_payment_terms(self): """ From 14600fa19007e3a00bc2d987f04f2a7b29d99536 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sat, 22 Jul 2023 11:18:11 +0530 Subject: [PATCH 14/30] fix: allocation logic on 'Get Outstanding Invoices' btn in PE 1. fixed broken `payment_term` filter in Payment References section 2. Throw error if user fails to select 'Payment Term' for an invoice with 'Payment Term based allocation' enabled. (cherry picked from commit 662ccd467c94e66c866d8646d29104846efb936a) --- .../doctype/payment_entry/payment_entry.js | 7 ++----- .../doctype/payment_entry/payment_entry.py | 18 ++++++++++++++---- erpnext/controllers/queries.py | 15 +++++++++++++++ 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index ace34e052cd..f91d29a6b68 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -122,13 +122,10 @@ frappe.ui.form.on('Payment Entry', { frm.set_query('payment_term', 'references', function(frm, cdt, cdn) { const child = locals[cdt][cdn]; if (in_list(['Purchase Invoice', 'Sales Invoice'], child.reference_doctype) && child.reference_name) { - let payment_term_list = frappe.get_list('Payment Schedule', {'parent': child.reference_name}); - - payment_term_list = payment_term_list.map(pt => pt.payment_term); - return { + query: "erpnext.controllers.queries.get_payment_terms_for_references", filters: { - 'name': ['in', payment_term_list] + 'reference': child.reference_name } } } diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index f29be181e7b..e8d88ba1381 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -184,10 +184,17 @@ class PaymentEntry(AccountsController): d = frappe._dict(d) latest_lookup.setdefault((d.voucher_type, d.voucher_no), frappe._dict())[d.payment_term] = d - for d in self.get("references"): - latest = (latest_lookup.get((d.reference_doctype, d.reference_name)) or frappe._dict()).get( - d.payment_term - ) + for idx, d in enumerate(self.get("references"), start=1): + latest = latest_lookup.get((d.reference_doctype, d.reference_name)) or frappe._dict() + + if (d.payment_term is None or d.payment_term == "") and d.payment_term not in latest.keys(): + frappe.throw( + _( + "{0} has Payment Term based allocation enabled. Select a Payment Term for Row #{1} in Payment References section" + ).format(frappe.bold(d.reference_name), frappe.bold(idx)) + ) + + latest = latest.get(d.payment_term) # The reference has already been fully paid if not latest: @@ -1510,6 +1517,9 @@ def split_invoices_based_on_payment_terms(outstanding_invoices, company): "invoice_amount": flt(d.invoice_amount), "outstanding_amount": flt(d.outstanding_amount), "payment_term_outstanding": payment_term_outstanding, + "allocated_amount": payment_term_outstanding + if payment_term_outstanding + else d.outstanding_amount, "payment_amount": payment_term.payment_amount, "payment_term": payment_term.payment_term, } diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 80bc3eef745..36225e3dd5e 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -823,3 +823,18 @@ def get_fields(doctype, fields=None): fields.insert(1, meta.title_field.strip()) return unique(fields) + + +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs +def get_payment_terms_for_references(doctype, txt, searchfield, start, page_len, filters) -> list: + terms = [] + if filters: + terms = frappe.db.get_all( + "Payment Schedule", + filters={"parent": filters.get("reference")}, + fields=["payment_term"], + limit=page_len, + as_list=1, + ) + return terms From 690b52622d62ddefb2d06817d4ec6cefda30ce94 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sun, 23 Jul 2023 11:55:16 +0530 Subject: [PATCH 15/30] refactor: handle references without any template and `payment_term` (cherry picked from commit ec7558b9e043ec90c5492e2d8581a750b76e88af) --- .../doctype/payment_entry/payment_entry.py | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index e8d88ba1381..5000bce35d5 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -164,6 +164,16 @@ class PaymentEntry(AccountsController): if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(d.outstanding_amount): frappe.throw(fail_message.format(d.idx)) + def term_based_allocation_enabled_for_reference( + self, reference_doctype: str, reference_name: str + ) -> bool: + if reference_doctype and reference_name: + if template := frappe.db.get_value(reference_doctype, reference_name, "payment_terms_template"): + return frappe.db.get_value( + "Payment Terms Template", template, "allocate_payment_based_on_payment_terms" + ) + return False + def validate_allocated_amount_with_latest_data(self): latest_references = get_outstanding_reference_documents( { @@ -187,14 +197,20 @@ class PaymentEntry(AccountsController): for idx, d in enumerate(self.get("references"), start=1): latest = latest_lookup.get((d.reference_doctype, d.reference_name)) or frappe._dict() - if (d.payment_term is None or d.payment_term == "") and d.payment_term not in latest.keys(): + # If term based allocation is enabled, throw + if ( + d.payment_term is None or d.payment_term == "" + ) and self.term_based_allocation_enabled_for_reference( + d.reference_doctype, d.reference_name + ): frappe.throw( _( "{0} has Payment Term based allocation enabled. Select a Payment Term for Row #{1} in Payment References section" ).format(frappe.bold(d.reference_name), frappe.bold(idx)) ) - latest = latest.get(d.payment_term) + # if no payment template is used by invoice and has a custom term(no `payment_term`), then invoice outstanding will be in 'None' key + latest = latest.get(d.payment_term) or latest.get(None) # The reference has already been fully paid if not latest: From bf948243a6eb322652e738cc3b71d78026bd3bac Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sun, 23 Jul 2023 19:50:14 +0530 Subject: [PATCH 16/30] refactor: refresh table once after loop ends (cherry picked from commit d048365da31d090736f5cbf768218bcca420b623) --- erpnext/accounts/doctype/sales_invoice/sales_invoice.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 66cf87761d9..df3db37bc65 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -887,6 +887,8 @@ frappe.ui.form.on('Sales Invoice', { frm.events.append_time_log(frm, timesheet, 1.0); } }); + frm.refresh_field("timesheets"); + frm.trigger("calculate_timesheet_totals"); }, async get_exchange_rate(frm, from_currency, to_currency) { @@ -926,9 +928,6 @@ frappe.ui.form.on('Sales Invoice', { row.billing_amount = flt(time_log.billing_amount) * flt(exchange_rate); row.timesheet_detail = time_log.name; row.project_name = time_log.project_name; - - frm.refresh_field("timesheets"); - frm.trigger("calculate_timesheet_totals"); }, calculate_timesheet_totals: function(frm) { From 8f24292155c0112b0f5d2a394e6fbf1e8d138db3 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 24 Jul 2023 14:13:57 +0530 Subject: [PATCH 17/30] fix: apply terms validaton only in Sales/Purchase documents --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 5000bce35d5..411ec6d02c6 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -167,7 +167,11 @@ class PaymentEntry(AccountsController): def term_based_allocation_enabled_for_reference( self, reference_doctype: str, reference_name: str ) -> bool: - if reference_doctype and reference_name: + if ( + reference_doctype + and reference_doctype in ["Sales Invoice", "Sales Order", "Purchase Order", "Purchase Invoice"] + and reference_name + ): if template := frappe.db.get_value(reference_doctype, reference_name, "payment_terms_template"): return frappe.db.get_value( "Payment Terms Template", template, "allocate_payment_based_on_payment_terms" From 026f9f5a69400c2bd2b076dd05a7551b35cfdffb Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 24 Jul 2023 14:00:20 +0530 Subject: [PATCH 18/30] fix: no default email account causing reposting issue (cherry picked from commit efb51526a92158af34e43e5d4f62f2bca6e6ba6b) --- .../doctype/repost_item_valuation/repost_item_valuation.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index 5e61f0f196a..9f3074a24ca 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -256,7 +256,11 @@ def repost(doc): message += "
" + "Traceback:
" + traceback frappe.db.set_value(doc.doctype, doc.name, "error_log", message) - if not isinstance(e, RecoverableErrors): + outgoing_email_account = frappe.get_cached_value( + "Email Account", {"default_outgoing": 1, "enable_outgoing": 1}, "name" + ) + + if outgoing_email_account and not isinstance(e, RecoverableErrors): notify_error_to_stock_managers(doc, message) doc.set_status("Failed") finally: From 2b47f5815e17d0f2733de68b1a8b493d9eeb898d Mon Sep 17 00:00:00 2001 From: Anand Baburajan Date: Mon, 24 Jul 2023 15:51:16 +0530 Subject: [PATCH 19/30] fix: allow both custodian and location while creating asset (#36263) --- .../doctype/asset_movement/asset_movement.py | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/erpnext/assets/doctype/asset_movement/asset_movement.py b/erpnext/assets/doctype/asset_movement/asset_movement.py index 22055dcb736..b85f7194f98 100644 --- a/erpnext/assets/doctype/asset_movement/asset_movement.py +++ b/erpnext/assets/doctype/asset_movement/asset_movement.py @@ -62,20 +62,21 @@ class AssetMovement(Document): frappe.throw(_("Source and Target Location cannot be same")) if self.purpose == "Receipt": - if not (d.source_location or d.from_employee) and not (d.target_location or d.to_employee): + if not (d.source_location) and not (d.target_location or d.to_employee): frappe.throw( _("Target Location or To Employee is required while receiving Asset {0}").format(d.asset) ) - elif d.from_employee and not d.target_location: - frappe.throw( - _("Target Location is required while receiving Asset {0} from an employee").format(d.asset) - ) - elif d.to_employee and d.target_location: - frappe.throw( - _( - "Asset {0} cannot be received at a location and given to an employee in a single movement" - ).format(d.asset) - ) + elif d.source_location: + if d.from_employee and not d.target_location: + frappe.throw( + _("Target Location is required while receiving Asset {0} from an employee").format(d.asset) + ) + elif d.to_employee and d.target_location: + frappe.throw( + _( + "Asset {0} cannot be received at a location and given to an employee in a single movement" + ).format(d.asset) + ) def validate_employee(self): for d in self.assets: From 5938af9c3f4752034991188ae2f4eec89f8a8ba5 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 24 Jul 2023 16:47:06 +0530 Subject: [PATCH 20/30] fix: customer filter in process soa (cherry picked from commit 34d7fb388d2743732dab7e56869f6006cdd1c8ea) --- .../process_statement_of_accounts.py | 2 +- ...ss_statement_of_accounts_accounts_receivable.html | 12 ++++-------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index ab0baf6273b..6cd601f663d 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -138,7 +138,7 @@ def get_gl_filters(doc, entry, tax_id, presentation_currency): def get_ar_filters(doc, entry): return { "report_date": doc.posting_date if doc.posting_date else None, - "customer_name": entry.customer, + "customer": entry.customer, "payment_terms_template": doc.payment_terms_template if doc.payment_terms_template else None, "sales_partner": doc.sales_partner if doc.sales_partner else None, "sales_person": doc.sales_person if doc.sales_person else None, diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts_accounts_receivable.html b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts_accounts_receivable.html index 07e1896292d..259526f8c43 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts_accounts_receivable.html +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts_accounts_receivable.html @@ -10,16 +10,12 @@

{{ _(report.report_name) }}

- {% if (filters.customer_name) %} - {{ filters.customer_name }} - {% else %} - {{ filters.customer ~ filters.supplier }} - {% endif %} + {{ filters.customer }}

- {% if (filters.tax_id) %} - {{ _("Tax Id: ") }}{{ filters.tax_id }} - {% endif %} + {% if (filters.tax_id) %} + {{ _("Tax Id: ") }}{{ filters.tax_id }} + {% endif %}
{{ _(filters.ageing_based_on) }} From e867fe77a40a5d2abbc66fcda42d2615bb49bf5f Mon Sep 17 00:00:00 2001 From: Anand Baburajan Date: Mon, 24 Jul 2023 18:50:16 +0530 Subject: [PATCH 21/30] fix: set new purchase_receipt_amount on asset split (#36272) --- erpnext/assets/doctype/asset/asset.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 02260c6da3a..c6247ea0da3 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -1413,6 +1413,8 @@ def create_new_asset_after_split(asset, split_qty): ) new_asset.gross_purchase_amount = new_gross_purchase_amount + if asset.purchase_receipt_amount: + new_asset.purchase_receipt_amount = new_gross_purchase_amount new_asset.opening_accumulated_depreciation = opening_accumulated_depreciation new_asset.asset_quantity = split_qty new_asset.split_from = asset.name From 73e9b38cda37b52f917f9d781a11d3035f0e055e Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Mon, 24 Jul 2023 18:37:36 +0530 Subject: [PATCH 22/30] fix(regional): set `frappe.flags.company` temporarily, where required (cherry picked from commit 4205f564a06e8b074f1b0a2f4dda38bcef3967f2) --- erpnext/accounts/party.py | 6 +++--- erpnext/controllers/accounts_controller.py | 5 ++++- erpnext/controllers/taxes_and_totals.py | 22 ++++++++-------------- erpnext/utilities/regional.py | 13 +++++++++++++ 4 files changed, 28 insertions(+), 18 deletions(-) create mode 100644 erpnext/utilities/regional.py diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 7583a60d63e..56d33b758b7 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -33,6 +33,7 @@ import erpnext from erpnext import get_company_currency from erpnext.accounts.utils import get_fiscal_year from erpnext.exceptions import InvalidAccountCurrency, PartyDisabled, PartyFrozen +from erpnext.utilities.regional import temporary_flag PURCHASE_TRANSACTION_TYPES = {"Purchase Order", "Purchase Receipt", "Purchase Invoice"} SALES_TRANSACTION_TYPES = { @@ -261,9 +262,8 @@ def set_address_details( ) if doctype in TRANSACTION_TYPES: - # required to set correct region - frappe.flags.company = company - get_regional_address_details(party_details, doctype, company) + with temporary_flag("company", company): + get_regional_address_details(party_details, doctype, company) return party_address, shipping_address diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index a1eba4ae0c3..b70ea36dcde 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -55,6 +55,7 @@ from erpnext.stock.get_item_details import ( get_item_tax_map, get_item_warehouse, ) +from erpnext.utilities.regional import temporary_flag from erpnext.utilities.transaction_base import TransactionBase @@ -758,7 +759,9 @@ class AccountsController(TransactionBase): } ) - update_gl_dict_with_regional_fields(self, gl_dict) + with temporary_flag("company", self.company): + update_gl_dict_with_regional_fields(self, gl_dict) + accounting_dimensions = get_accounting_dimensions() dimension_dict = frappe._dict() diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 4661c5ca7e8..77006b905cb 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -18,6 +18,7 @@ from erpnext.controllers.accounts_controller import ( validate_taxes_and_charges, ) from erpnext.stock.get_item_details import _get_item_tax_template +from erpnext.utilities.regional import temporary_flag class calculate_taxes_and_totals(object): @@ -942,7 +943,6 @@ class calculate_taxes_and_totals(object): def get_itemised_tax_breakup_html(doc): if not doc.taxes: return - frappe.flags.company = doc.company # get headers tax_accounts = [] @@ -952,15 +952,11 @@ def get_itemised_tax_breakup_html(doc): if tax.description not in tax_accounts: tax_accounts.append(tax.description) - headers = get_itemised_tax_breakup_header(doc.doctype + " Item", tax_accounts) - - # get tax breakup data - itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(doc) - - get_rounded_tax_amount(itemised_tax, doc.precision("tax_amount", "taxes")) - - update_itemised_tax_data(doc) - frappe.flags.company = None + with temporary_flag("company", doc.company): + headers = get_itemised_tax_breakup_header(doc.doctype + " Item", tax_accounts) + itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(doc) + get_rounded_tax_amount(itemised_tax, doc.precision("tax_amount", "taxes")) + update_itemised_tax_data(doc) return frappe.render_template( "templates/includes/itemised_tax_breakup.html", @@ -977,10 +973,8 @@ def get_itemised_tax_breakup_html(doc): @frappe.whitelist() def get_round_off_applicable_accounts(company, account_list): # required to set correct region - frappe.flags.company = company - account_list = get_regional_round_off_accounts(company, account_list) - - return account_list + with temporary_flag("company", company): + return get_regional_round_off_accounts(company, account_list) @erpnext.allow_regional diff --git a/erpnext/utilities/regional.py b/erpnext/utilities/regional.py new file mode 100644 index 00000000000..858976f8557 --- /dev/null +++ b/erpnext/utilities/regional.py @@ -0,0 +1,13 @@ +from contextlib import contextmanager + +import frappe + + +@contextmanager +def temporary_flag(flag_name, value): + flags = frappe.local.flags + flags[flag_name] = value + try: + yield + finally: + flags.pop(flag_name, None) From 6f2e6391822564f771a92a01435ec4923c59a05f Mon Sep 17 00:00:00 2001 From: DaizyModi Date: Tue, 25 Jul 2023 11:25:53 +0530 Subject: [PATCH 23/30] fix: itemised tax breakup --- .../sales_invoice/test_sales_invoice.py | 22 ++++++++++------- erpnext/controllers/taxes_and_totals.py | 24 ++++++++++++------- .../includes/itemised_tax_breakup.html | 8 +++---- 3 files changed, 34 insertions(+), 20 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index f9fb6232ac9..856631ee657 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1900,16 +1900,22 @@ class TestSalesInvoice(unittest.TestCase): si = self.create_si_to_test_tax_breakup() - itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(si) + itemised_tax_data = get_itemised_tax_breakup_data(si) - expected_itemised_tax = { - "_Test Item": {"Service Tax": {"tax_rate": 10.0, "tax_amount": 1000.0}}, - "_Test Item 2": {"Service Tax": {"tax_rate": 10.0, "tax_amount": 500.0}}, - } - expected_itemised_taxable_amount = {"_Test Item": 10000.0, "_Test Item 2": 5000.0} + expected_itemised_tax = [ + { + "item": "_Test Item", + "taxable_amount": 10000.0, + "Service Tax": {"tax_rate": 10.0, "tax_amount": 1000.0}, + }, + { + "item": "_Test Item 2", + "taxable_amount": 5000.0, + "Service Tax": {"tax_rate": 10.0, "tax_amount": 500.0}, + }, + ] - self.assertEqual(itemised_tax, expected_itemised_tax) - self.assertEqual(itemised_taxable_amount, expected_itemised_taxable_amount) + self.assertEqual(itemised_tax_data, expected_itemised_tax) frappe.flags.country = None diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 77006b905cb..62d4c538682 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -954,16 +954,15 @@ def get_itemised_tax_breakup_html(doc): with temporary_flag("company", doc.company): headers = get_itemised_tax_breakup_header(doc.doctype + " Item", tax_accounts) - itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(doc) - get_rounded_tax_amount(itemised_tax, doc.precision("tax_amount", "taxes")) + itemised_tax_data = get_itemised_tax_breakup_data(doc) + get_rounded_tax_amount(itemised_tax_data, doc.precision("tax_amount", "taxes")) update_itemised_tax_data(doc) return frappe.render_template( "templates/includes/itemised_tax_breakup.html", dict( headers=headers, - itemised_tax=itemised_tax, - itemised_taxable_amount=itemised_taxable_amount, + itemised_tax_data=itemised_tax_data, tax_accounts=tax_accounts, doc=doc, ), @@ -999,7 +998,15 @@ def get_itemised_tax_breakup_data(doc): itemised_taxable_amount = get_itemised_taxable_amount(doc.items) - return itemised_tax, itemised_taxable_amount + itemised_tax_data = [] + for item_code, taxes in itemised_tax.items(): + itemised_tax_data.append( + frappe._dict( + {"item": item_code, "taxable_amount": itemised_taxable_amount.get(item_code), **taxes} + ) + ) + + return itemised_tax_data def get_itemised_tax(taxes, with_tax_account=False): @@ -1044,9 +1051,10 @@ def get_itemised_taxable_amount(items): def get_rounded_tax_amount(itemised_tax, precision): # Rounding based on tax_amount precision - for taxes in itemised_tax.values(): - for tax_account in taxes: - taxes[tax_account]["tax_amount"] = flt(taxes[tax_account]["tax_amount"], precision) + for taxes in itemised_tax: + for row in taxes.values(): + if isinstance(row, dict) and isinstance(row["tax_amount"], float): + row["tax_amount"] = flt(row["tax_amount"], precision) class init_landed_taxes_and_totals(object): diff --git a/erpnext/templates/includes/itemised_tax_breakup.html b/erpnext/templates/includes/itemised_tax_breakup.html index fbc80de7d0d..7b507bd1fdb 100644 --- a/erpnext/templates/includes/itemised_tax_breakup.html +++ b/erpnext/templates/includes/itemised_tax_breakup.html @@ -12,14 +12,14 @@ - {% for item, taxes in itemised_tax.items() %} + {% for taxes in itemised_tax_data %} - {{ item }} + {{ taxes.item }} {% if doc.get('is_return') %} - {{ frappe.utils.fmt_money((itemised_taxable_amount.get(item, 0))|abs, None, doc.currency) }} + {{ frappe.utils.fmt_money(taxes.taxable_amount|abs, None, doc.currency) }} {% else %} - {{ frappe.utils.fmt_money(itemised_taxable_amount.get(item, 0), None, doc.currency) }} + {{ frappe.utils.fmt_money(taxes.taxable_amount, None, doc.currency) }} {% endif %} {% for tax_account in tax_accounts %} From 0575005c8708a0c1b0dd2601c04b04a96fa6d613 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 25 Jul 2023 12:00:36 +0530 Subject: [PATCH 24/30] fix: timeout error while cancelling the Purchase Receipt --- .../doctype/sales_invoice_item/sales_invoice_item.json | 5 +++-- .../doctype/delivery_note_item/delivery_note_item.json | 7 ++++--- erpnext/stock/doctype/pick_list_item/pick_list_item.json | 7 ++++--- .../stock_reconciliation_item.json | 5 +++-- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json index cfda48e5c75..f25280f007c 100644 --- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json +++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json @@ -603,7 +603,8 @@ "in_list_view": 1, "label": "Batch No", "options": "Batch", - "print_hide": 1 + "print_hide": 1, + "search_index": 1 }, { "fieldname": "col_break5", @@ -890,7 +891,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2022-10-17 12:51:44.825398", + "modified": "2023-07-25 11:58:10.723833", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Item", diff --git a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json index b97e42c2468..edfb269da9a 100644 --- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json +++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json @@ -515,7 +515,8 @@ "oldfieldname": "batch_no", "oldfieldtype": "Link", "options": "Batch", - "print_hide": 1 + "print_hide": 1, + "search_index": 1 }, { "allow_on_submit": 1, @@ -867,7 +868,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-05-01 21:05:14.175640", + "modified": "2023-07-25 11:58:28.101919", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note Item", @@ -877,4 +878,4 @@ "sort_field": "modified", "sort_order": "DESC", "states": [] -} +} \ No newline at end of file diff --git a/erpnext/stock/doctype/pick_list_item/pick_list_item.json b/erpnext/stock/doctype/pick_list_item/pick_list_item.json index b887f795640..883cdd19e7c 100644 --- a/erpnext/stock/doctype/pick_list_item/pick_list_item.json +++ b/erpnext/stock/doctype/pick_list_item/pick_list_item.json @@ -79,7 +79,8 @@ "fieldname": "batch_no", "fieldtype": "Link", "label": "Batch No", - "options": "Batch" + "options": "Batch", + "search_index": 1 }, { "fieldname": "column_break_2", @@ -192,7 +193,7 @@ ], "istable": 1, "links": [], - "modified": "2023-06-16 14:05:51.719959", + "modified": "2023-07-25 11:56:23.361867", "modified_by": "Administrator", "module": "Stock", "name": "Pick List Item", @@ -203,4 +204,4 @@ "sort_order": "DESC", "states": [], "track_changes": 1 -} +} \ No newline at end of file diff --git a/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json index 2f65eaa358d..a04309ad48e 100644 --- a/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json +++ b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json @@ -168,7 +168,8 @@ "fieldname": "batch_no", "fieldtype": "Link", "label": "Batch No", - "options": "Batch" + "options": "Batch", + "search_index": 1 }, { "default": "0", @@ -189,7 +190,7 @@ ], "istable": 1, "links": [], - "modified": "2023-05-09 18:42:19.224916", + "modified": "2023-07-25 11:58:44.992419", "modified_by": "Administrator", "module": "Stock", "name": "Stock Reconciliation Item", From ba95df2a74fb3a637a2865882cc267ac9cdbd29f Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 12 Jul 2023 10:00:18 +0530 Subject: [PATCH 25/30] feat: filter based on accounting dimension in profitability analysis (cherry picked from commit dd8c3d5462c97f34d03f76f6e797391ad401b56d) --- .../profitability_analysis.js | 15 ++++++++++++++- .../profitability_analysis.py | 10 +++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/report/profitability_analysis/profitability_analysis.js b/erpnext/accounts/report/profitability_analysis/profitability_analysis.js index 6caebd34a2f..84253710beb 100644 --- a/erpnext/accounts/report/profitability_analysis/profitability_analysis.js +++ b/erpnext/accounts/report/profitability_analysis/profitability_analysis.js @@ -16,10 +16,23 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() { "fieldname": "based_on", "label": __("Based On"), "fieldtype": "Select", - "options": ["Cost Center", "Project"], + "options": ["Cost Center", "Project", "Accounting Dimension"], "default": "Cost Center", "reqd": 1 }, + { + "fieldname": "accounting_dimension", + "label": __("Accounting Dimension"), + "fieldtype": "Link", + "options": "Accounting Dimension", + "get_query": () =>{ + return { + filters: { + "disabled": 0 + } + } + } + }, { "fieldname": "fiscal_year", "label": __("Fiscal Year"), diff --git a/erpnext/accounts/report/profitability_analysis/profitability_analysis.py b/erpnext/accounts/report/profitability_analysis/profitability_analysis.py index 183e279fe5d..c05aa944575 100644 --- a/erpnext/accounts/report/profitability_analysis/profitability_analysis.py +++ b/erpnext/accounts/report/profitability_analysis/profitability_analysis.py @@ -16,8 +16,8 @@ value_fields = ("income", "expense", "gross_profit_loss") def execute(filters=None): - if not filters.get("based_on"): - filters["based_on"] = "Cost Center" + if filters.get("based_on") == "Accounting Dimension" and not filters.get("accounting_dimension"): + frappe.throw(_("Select Accounting Dimension.")) based_on = filters.based_on.replace(" ", "_").lower() validate_filters(filters) @@ -37,6 +37,8 @@ def get_accounts_data(based_on, company): ) elif based_on == "project": return frappe.get_all("Project", fields=["name"], filters={"company": company}, order_by="name") + elif based_on == "accounting_dimension": + return frappe.get_all("Accounting Dimension", fields=["name"], order_by="name") else: filters = {} doctype = frappe.unscrub(based_on) @@ -60,7 +62,9 @@ def get_data(accounts, filters, based_on): filters.get("company"), filters.get("from_date"), filters.get("to_date"), - based_on, + based_on + if based_on != "accounting_dimension" + else filters.accounting_dimension.replace(" ", "_").lower(), gl_entries_by_account, ignore_closing_entries=not flt(filters.get("with_period_closing_entry")), ) From 1fe6d4ef1f9550854bd357bb961cc2c74d81bf44 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 12 Jul 2023 20:32:08 +0530 Subject: [PATCH 26/30] fix: clear accounting dimension value when based on field changes (cherry picked from commit 21c993a7b3129696ac4efa3d2dd2ac3f4d3a5ec2) --- .../profitability_analysis/profitability_analysis.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/profitability_analysis/profitability_analysis.js b/erpnext/accounts/report/profitability_analysis/profitability_analysis.js index 84253710beb..27b29baa40a 100644 --- a/erpnext/accounts/report/profitability_analysis/profitability_analysis.js +++ b/erpnext/accounts/report/profitability_analysis/profitability_analysis.js @@ -18,7 +18,15 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() { "fieldtype": "Select", "options": ["Cost Center", "Project", "Accounting Dimension"], "default": "Cost Center", - "reqd": 1 + "reqd": 1, + "on_change": function(query_report){ + let based_on = query_report.get_values().based_on; + if(based_on!='Accounting Dimension'){ + frappe.query_report.set_filter_value({ + accounting_dimension: '' + }); + } + } }, { "fieldname": "accounting_dimension", From 542c80433d55c12109c28333c5e3e56910e6f162 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Fri, 21 Jul 2023 13:37:48 +0530 Subject: [PATCH 27/30] fix: fetch acc dimension fieldname (cherry picked from commit 1c5c310f5a16fb87e5c741156ad7dc567cbd8400) --- .../profitability_analysis.py | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/report/profitability_analysis/profitability_analysis.py b/erpnext/accounts/report/profitability_analysis/profitability_analysis.py index c05aa944575..3d6e9b5428c 100644 --- a/erpnext/accounts/report/profitability_analysis/profitability_analysis.py +++ b/erpnext/accounts/report/profitability_analysis/profitability_analysis.py @@ -6,6 +6,7 @@ import frappe from frappe import _ from frappe.utils import cstr, flt +from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_dimensions from erpnext.accounts.report.financial_statements import ( filter_accounts, filter_out_zero_value_rows, @@ -19,7 +20,9 @@ def execute(filters=None): if filters.get("based_on") == "Accounting Dimension" and not filters.get("accounting_dimension"): frappe.throw(_("Select Accounting Dimension.")) - based_on = filters.based_on.replace(" ", "_").lower() + based_on = ( + filters.based_on if filters.based_on != "Accounting Dimension" else filters.accounting_dimension + ) validate_filters(filters) accounts = get_accounts_data(based_on, filters.get("company")) data = get_data(accounts, filters, based_on) @@ -28,17 +31,15 @@ def execute(filters=None): def get_accounts_data(based_on, company): - if based_on == "cost_center": + if based_on == "Cost Center": return frappe.db.sql( """select name, parent_cost_center as parent_account, cost_center_name as account_name, lft, rgt from `tabCost Center` where company=%s order by name""", company, as_dict=True, ) - elif based_on == "project": + elif based_on == "Project": return frappe.get_all("Project", fields=["name"], filters={"company": company}, order_by="name") - elif based_on == "accounting_dimension": - return frappe.get_all("Accounting Dimension", fields=["name"], order_by="name") else: filters = {} doctype = frappe.unscrub(based_on) @@ -58,13 +59,17 @@ def get_data(accounts, filters, based_on): gl_entries_by_account = {} + accounting_dimensions = get_dimensions(with_cost_center_and_project=True)[0] + fieldname = "" + for dimension in accounting_dimensions: + if dimension["document_type"] == based_on: + fieldname = dimension["fieldname"] + set_gl_entries_by_account( filters.get("company"), filters.get("from_date"), filters.get("to_date"), - based_on - if based_on != "accounting_dimension" - else filters.accounting_dimension.replace(" ", "_").lower(), + fieldname, gl_entries_by_account, ignore_closing_entries=not flt(filters.get("with_period_closing_entry")), ) From 4fa93b05c62b7f340a94e9e84d9d2e132ea35b99 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 25 Jul 2023 17:22:56 +0530 Subject: [PATCH 28/30] fix: added missing option Partially Received in the status dropdown field --- erpnext/stock/doctype/material_request/material_request.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/material_request/material_request.json b/erpnext/stock/doctype/material_request/material_request.json index ae39470d1dc..ffec57ca1df 100644 --- a/erpnext/stock/doctype/material_request/material_request.json +++ b/erpnext/stock/doctype/material_request/material_request.json @@ -181,7 +181,7 @@ "no_copy": 1, "oldfieldname": "status", "oldfieldtype": "Select", - "options": "\nDraft\nSubmitted\nStopped\nCancelled\nPending\nPartially Ordered\nOrdered\nIssued\nTransferred\nReceived", + "options": "\nDraft\nSubmitted\nStopped\nCancelled\nPending\nPartially Ordered\nPartially Received\nOrdered\nIssued\nTransferred\nReceived", "print_hide": 1, "print_width": "100px", "read_only": 1, @@ -356,7 +356,7 @@ "idx": 70, "is_submittable": 1, "links": [], - "modified": "2023-05-07 20:17:29.108095", + "modified": "2023-07-25 17:19:31.662662", "modified_by": "Administrator", "module": "Stock", "name": "Material Request", From 6cca18e1689a4ef66a77cc224c3a128b2877a93a Mon Sep 17 00:00:00 2001 From: Anand Baburajan Date: Tue, 25 Jul 2023 20:58:18 +0530 Subject: [PATCH 29/30] fix: group by in fixed asset register (#36310) --- .../fixed_asset_register.py | 138 ++++++++++++------ 1 file changed, 92 insertions(+), 46 deletions(-) diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py index 6911f94bbbb..94c77ea517c 100644 --- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py +++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py @@ -54,12 +54,12 @@ def get_conditions(filters): conditions["cost_center"] = filters.get("cost_center") if status: - # In Store assets are those that are not sold or scrapped + # In Store assets are those that are not sold or scrapped or capitalized or decapitalized operand = "not in" if status not in "In Location": operand = "in" - conditions["status"] = (operand, ["Sold", "Scrapped"]) + conditions["status"] = (operand, ["Sold", "Scrapped", "Capitalized", "Decapitalized"]) return conditions @@ -71,36 +71,6 @@ def get_data(filters): pr_supplier_map = get_purchase_receipt_supplier_map() pi_supplier_map = get_purchase_invoice_supplier_map() - group_by = frappe.scrub(filters.get("group_by")) - - if group_by == "asset_category": - fields = ["asset_category", "gross_purchase_amount", "opening_accumulated_depreciation"] - assets_record = frappe.db.get_all("Asset", filters=conditions, fields=fields, group_by=group_by) - - elif group_by == "location": - fields = ["location", "gross_purchase_amount", "opening_accumulated_depreciation"] - assets_record = frappe.db.get_all("Asset", filters=conditions, fields=fields, group_by=group_by) - - else: - fields = [ - "name as asset_id", - "asset_name", - "status", - "department", - "company", - "cost_center", - "calculate_depreciation", - "purchase_receipt", - "asset_category", - "purchase_date", - "gross_purchase_amount", - "location", - "available_for_use_date", - "purchase_invoice", - "opening_accumulated_depreciation", - ] - assets_record = frappe.db.get_all("Asset", filters=conditions, fields=fields) - assets_linked_to_fb = get_assets_linked_to_fb(filters) company_fb = frappe.get_cached_value("Company", filters.company, "default_finance_book") @@ -114,6 +84,31 @@ def get_data(filters): depreciation_amount_map = get_asset_depreciation_amount_map(filters, finance_book) + group_by = frappe.scrub(filters.get("group_by")) + + if group_by in ("asset_category", "location"): + data = get_group_by_data(group_by, conditions, assets_linked_to_fb, depreciation_amount_map) + return data + + fields = [ + "name as asset_id", + "asset_name", + "status", + "department", + "company", + "cost_center", + "calculate_depreciation", + "purchase_receipt", + "asset_category", + "purchase_date", + "gross_purchase_amount", + "location", + "available_for_use_date", + "purchase_invoice", + "opening_accumulated_depreciation", + ] + assets_record = frappe.db.get_all("Asset", filters=conditions, fields=fields) + for asset in assets_record: if ( assets_linked_to_fb @@ -136,7 +131,7 @@ def get_data(filters): or pi_supplier_map.get(asset.purchase_invoice), "gross_purchase_amount": asset.gross_purchase_amount, "opening_accumulated_depreciation": asset.opening_accumulated_depreciation, - "depreciated_amount": get_depreciation_amount_of_asset(asset, depreciation_amount_map), + "depreciated_amount": depreciation_amount_map.get(asset.asset_id) or 0.0, "available_for_use_date": asset.available_for_use_date, "location": asset.location, "asset_category": asset.asset_category, @@ -230,12 +225,11 @@ def get_assets_linked_to_fb(filters): return assets_linked_to_fb -def get_depreciation_amount_of_asset(asset, depreciation_amount_map): - return depreciation_amount_map.get(asset.asset_id) or 0.0 - - def get_asset_depreciation_amount_map(filters, finance_book): - date = filters.to_date if filters.filter_based_on == "Date Range" else filters.year_end_date + start_date = ( + filters.from_date if filters.filter_based_on == "Date Range" else filters.year_start_date + ) + end_date = filters.to_date if filters.filter_based_on == "Date Range" else filters.year_end_date asset = frappe.qb.DocType("Asset") gle = frappe.qb.DocType("GL Entry") @@ -256,25 +250,77 @@ def get_asset_depreciation_amount_map(filters, finance_book): ) .where(gle.debit != 0) .where(gle.is_cancelled == 0) + .where(company.name == filters.company) .where(asset.docstatus == 1) - .groupby(asset.name) ) + if filters.only_existing_assets: + query = query.where(asset.is_existing_asset == 1) + if filters.asset_category: + query = query.where(asset.asset_category == filters.asset_category) + if filters.cost_center: + query = query.where(asset.cost_center == filters.cost_center) + if filters.status: + if filters.status == "In Location": + query = query.where(asset.status.notin(["Sold", "Scrapped", "Capitalized", "Decapitalized"])) + else: + query = query.where(asset.status.isin(["Sold", "Scrapped", "Capitalized", "Decapitalized"])) if finance_book: query = query.where( (gle.finance_book.isin([cstr(finance_book), ""])) | (gle.finance_book.isnull()) ) else: query = query.where((gle.finance_book.isin([""])) | (gle.finance_book.isnull())) - if filters.filter_based_on in ("Date Range", "Fiscal Year"): - query = query.where(gle.posting_date <= date) + query = query.where(gle.posting_date >= start_date) + query = query.where(gle.posting_date <= end_date) + + query = query.groupby(asset.name) asset_depr_amount_map = query.run() return dict(asset_depr_amount_map) +def get_group_by_data(group_by, conditions, assets_linked_to_fb, depreciation_amount_map): + fields = [ + group_by, + "name", + "gross_purchase_amount", + "opening_accumulated_depreciation", + "calculate_depreciation", + ] + assets = frappe.db.get_all("Asset", filters=conditions, fields=fields) + + data = [] + + for a in assets: + if assets_linked_to_fb and a.calculate_depreciation and a.name not in assets_linked_to_fb: + continue + + a["depreciated_amount"] = depreciation_amount_map.get(a["name"], 0.0) + a["asset_value"] = ( + a["gross_purchase_amount"] - a["opening_accumulated_depreciation"] - a["depreciated_amount"] + ) + + del a["name"] + del a["calculate_depreciation"] + + idx = ([i for i, d in enumerate(data) if a[group_by] == d[group_by]] or [None])[0] + if idx is None: + data.append(a) + else: + for field in ( + "gross_purchase_amount", + "opening_accumulated_depreciation", + "depreciated_amount", + "asset_value", + ): + data[idx][field] = data[idx][field] + a[field] + + return data + + def get_purchase_receipt_supplier_map(): return frappe._dict( frappe.db.sql( @@ -313,35 +359,35 @@ def get_columns(filters): "fieldtype": "Link", "fieldname": frappe.scrub(filters.get("group_by")), "options": filters.get("group_by"), - "width": 120, + "width": 216, }, { "label": _("Gross Purchase Amount"), "fieldname": "gross_purchase_amount", "fieldtype": "Currency", "options": "company:currency", - "width": 100, + "width": 250, }, { "label": _("Opening Accumulated Depreciation"), "fieldname": "opening_accumulated_depreciation", "fieldtype": "Currency", "options": "company:currency", - "width": 90, + "width": 250, }, { "label": _("Depreciated Amount"), "fieldname": "depreciated_amount", "fieldtype": "Currency", "options": "company:currency", - "width": 100, + "width": 250, }, { "label": _("Asset Value"), "fieldname": "asset_value", "fieldtype": "Currency", "options": "company:currency", - "width": 100, + "width": 250, }, ] From cff6e72838b9a118de269f1ed4d6806d1474bbbd Mon Sep 17 00:00:00 2001 From: Anand Baburajan Date: Tue, 25 Jul 2023 21:55:26 +0530 Subject: [PATCH 30/30] fix: apply discount on item after applying price list (#36316) --- erpnext/public/js/controllers/transaction.js | 13 +++++++++++++ erpnext/selling/sales_common.js | 10 ---------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index cfbb54ab2d0..b6b6e2e5ad6 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -969,6 +969,16 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe this.frm.set_df_property("conversion_rate", "read_only", erpnext.stale_rate_allowed() ? 0 : 1); } + apply_discount_on_item(doc, cdt, cdn, field) { + var item = frappe.get_doc(cdt, cdn); + if(!item.price_list_rate) { + item[field] = 0.0; + } else { + this.price_list_rate(doc, cdt, cdn); + } + this.set_gross_profit(item); + } + shipping_rule() { var me = this; if(this.frm.doc.shipping_rule) { @@ -1639,6 +1649,9 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe () => { if(args.items.length) { me._set_values_for_item_list(r.message.children); + $.each(r.message.children || [], function(i, d) { + me.apply_discount_on_item(d, d.doctype, d.name, 'discount_percentage'); + }); } }, () => { me.in_apply_price_list = false; } diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js index e3de49c57d8..80a6b7712fc 100644 --- a/erpnext/selling/sales_common.js +++ b/erpnext/selling/sales_common.js @@ -145,16 +145,6 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran this.apply_discount_on_item(doc, cdt, cdn, 'discount_amount'); } - apply_discount_on_item(doc, cdt, cdn, field) { - var item = frappe.get_doc(cdt, cdn); - if(!item.price_list_rate) { - item[field] = 0.0; - } else { - this.price_list_rate(doc, cdt, cdn); - } - this.set_gross_profit(item); - } - commission_rate() { this.calculate_commission(); }