From a5e14324da4d631d84c5c34787ffe4f1ddd2dcb3 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 31 Jul 2024 14:40:55 +0530 Subject: [PATCH 01/25] fix: 'undefined' in PL and BS report summary on Consolidated report (cherry picked from commit dd5a5e4919ea2b5aac8809eff4ef4e2a4b2bd49a) --- .../consolidated_financial_statement.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py index 794721b6c92..a780e466ba9 100644 --- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py +++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py @@ -109,7 +109,7 @@ def get_balance_sheet_data(fiscal_year, companies, columns, filters): if total_credit: data.append(total_credit) - report_summary = get_bs_summary( + report_summary, primitive_summary = get_bs_summary( companies, asset, liability, @@ -180,7 +180,7 @@ def get_profit_loss_data(fiscal_year, companies, columns, filters): chart = get_pl_chart_data(filters, columns, income, expense, net_profit_loss) - report_summary = get_pl_summary( + report_summary, primitive_summary = get_pl_summary( companies, "", income, expense, net_profit_loss, company_currency, filters, True ) From c8d85364b9b7a2588541f2973ba79ea954effbda Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 31 Jul 2024 16:42:33 +0530 Subject: [PATCH 02/25] fix: set query filters for sales / purchase tax template on PE (cherry picked from commit 9fe47ac101fffeb701e9414b035c813feb0b5db2) # Conflicts: # erpnext/accounts/doctype/payment_entry/payment_entry.js --- .../doctype/payment_entry/payment_entry.js | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index ad1d200ec53..2d07daa5b96 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -145,10 +145,32 @@ frappe.ui.form.on('Payment Entry', { filters: filters }; }); - }, +<<<<<<< HEAD refresh: function(frm) { erpnext.hide_company(); +======= + frm.set_query("sales_taxes_and_charges_template", function () { + return { + filters: { + company: frm.doc.company, + disabled: false, + }, + }; + }); + + frm.set_query("purchase_taxes_and_charges_template", function () { + return { + filters: { + company: frm.doc.company, + disabled: false, + }, + }; + }); + }, + refresh: function (frm) { + erpnext.hide_company(frm); +>>>>>>> 9fe47ac101 (fix: set query filters for sales / purchase tax template on PE) frm.events.hide_unhide_fields(frm); frm.events.set_dynamic_labels(frm); frm.events.show_general_ledger(frm); From e82c4413260991c46e671459283b2a55d20a94b9 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 31 Jul 2024 17:14:26 +0530 Subject: [PATCH 03/25] chore: resolve conflict --- erpnext/accounts/doctype/payment_entry/payment_entry.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 2d07daa5b96..8ec055e28a4 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -146,10 +146,6 @@ frappe.ui.form.on('Payment Entry', { }; }); -<<<<<<< HEAD - refresh: function(frm) { - erpnext.hide_company(); -======= frm.set_query("sales_taxes_and_charges_template", function () { return { filters: { @@ -170,7 +166,6 @@ frappe.ui.form.on('Payment Entry', { }, refresh: function (frm) { erpnext.hide_company(frm); ->>>>>>> 9fe47ac101 (fix: set query filters for sales / purchase tax template on PE) frm.events.hide_unhide_fields(frm); frm.events.set_dynamic_labels(frm); frm.events.show_general_ledger(frm); From 3fb6f97f6662e70ce6bce34c14327d63b8693ccc Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 1 Aug 2024 13:21:37 +0530 Subject: [PATCH 04/25] fix: slowness in reposting dependent vouchers. (backport #42282) (#42569) fix: slowness in reposting dependent vouchers. (#42282) (cherry picked from commit b17696a8ae7f0ea1128887d29336de4ef67a220f) Co-authored-by: rohitwaghchaure --- erpnext/stock/stock_ledger.py | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 42918f56a23..2864c00864b 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -203,6 +203,8 @@ def repost_future_sle( "posting_time": args[i].get("posting_time"), "creation": args[i].get("creation"), "distinct_item_warehouses": distinct_item_warehouses, + "items_to_be_repost": args, + "current_index": i, }, allow_negative_stock=allow_negative_stock, via_landed_cost_voucher=via_landed_cost_voucher, @@ -486,11 +488,20 @@ class update_entries_after: self.distinct_item_warehouses[key] = val self.new_items_found = True else: + # Check if the dependent voucher is reposted + # If not, then do not add it to the list + if not self.is_dependent_voucher_reposted(dependant_sle): + return + existing_sle_posting_date = self.distinct_item_warehouses[key].get("sle", {}).get("posting_date") dependent_voucher_detail_nos = self.get_dependent_voucher_detail_nos(key) - if getdate(dependant_sle.posting_date) < getdate(existing_sle_posting_date): + if dependent_voucher_detail_nos and dependant_sle.voucher_detail_no in set( + dependent_voucher_detail_nos + ): + return + val.sle_changed = True dependent_voucher_detail_nos.append(dependant_sle.voucher_detail_no) val.dependent_voucher_detail_nos = dependent_voucher_detail_nos @@ -504,6 +515,27 @@ class update_entries_after: val.dependent_voucher_detail_nos = dependent_voucher_detail_nos self.distinct_item_warehouses[key] = val + def is_dependent_voucher_reposted(self, dependant_sle) -> bool: + # Return False if the dependent voucher is not reposted + + if self.args.items_to_be_repost and self.args.current_index: + index = self.args.current_index + while index < len(self.args.items_to_be_repost): + if ( + self.args.items_to_be_repost[index].get("item_code") == dependant_sle.item_code + and self.args.items_to_be_repost[index].get("warehouse") == dependant_sle.warehouse + ): + if getdate(self.args.items_to_be_repost[index].get("posting_date")) > getdate( + dependant_sle.posting_date + ): + self.args.items_to_be_repost[index]["posting_date"] = dependant_sle.posting_date + + return False + + index += 1 + + return True + def get_dependent_voucher_detail_nos(self, key): if "dependent_voucher_detail_nos" not in self.distinct_item_warehouses[key]: self.distinct_item_warehouses[key].dependent_voucher_detail_nos = [] From b20b15af74f626dbf0a515659149a787f501824e Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 2 Aug 2024 15:43:04 +0530 Subject: [PATCH 05/25] refactor: ignore filter in general ledger for cr / dr notes (cherry picked from commit 59d5beee20f4a3837b1bcd74e6826d4b7290c0fb) --- erpnext/accounts/report/general_ledger/general_ledger.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js index 3d0a2b78e5e..3431b76dfb1 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.js +++ b/erpnext/accounts/report/general_ledger/general_ledger.js @@ -199,6 +199,11 @@ frappe.query_reports["General Ledger"] = { label: __("Ignore Exchange Rate Revaluation Journals"), fieldtype: "Check", }, + { + fieldname: "ignore_cr_dr_notes", + label: __("Ignore System Generated Credit / Debit Notes"), + fieldtype: "Check", + }, ], }; From 0a83c8b00c3ad7a1b495b6699f71d47fa55bbcee Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 2 Aug 2024 15:47:27 +0530 Subject: [PATCH 06/25] refactor: ignore system generated cr / dr notes on general ledger (cherry picked from commit bb8c9b5a5845d2db3cd96f0264132c0e9b4a1e6e) --- .../report/general_ledger/general_ledger.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 0c4e1eca3f9..05bbc4a79c9 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -234,6 +234,20 @@ def get_conditions(filters): if err_journals: filters.update({"voucher_no_not_in": [x[0] for x in err_journals]}) + if filters.get("ignore_cr_dr_notes"): + system_generated_cr_dr_journals = frappe.db.get_all( + "Journal Entry", + filters={ + "company": filters.get("company"), + "docstatus": 1, + "voucher_type": ("in", ["Credit Note", "Debit Note"]), + "is_system_generated": 1, + }, + as_list=True, + ) + if system_generated_cr_dr_journals: + filters.update({"voucher_no_not_in": [x[0] for x in system_generated_cr_dr_journals]}) + if filters.get("voucher_no_not_in"): conditions.append("voucher_no not in %(voucher_no_not_in)s") From ac763f8c1954d9b5da082ac71376ace69ab1fb47 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 5 Aug 2024 09:56:16 +0530 Subject: [PATCH 07/25] test: ignore filter for system generated cr / dr note journals (cherry picked from commit 3ffac735985a1b0d55b3265d5299ff6d0cf6e93a) --- .../general_ledger/test_general_ledger.py | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/erpnext/accounts/report/general_ledger/test_general_ledger.py b/erpnext/accounts/report/general_ledger/test_general_ledger.py index 33b356feb33..682eef8f419 100644 --- a/erpnext/accounts/report/general_ledger/test_general_ledger.py +++ b/erpnext/accounts/report/general_ledger/test_general_ledger.py @@ -5,7 +5,9 @@ import frappe from frappe.tests.utils import FrappeTestCase from frappe.utils import flt, today +from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.accounts.report.general_ledger.general_ledger import execute +from erpnext.controllers.sales_and_purchase_return import make_return_doc class TestGeneralLedger(FrappeTestCase): @@ -248,3 +250,73 @@ class TestGeneralLedger(FrappeTestCase): ) ) self.assertIn(revaluation_jv.name, set([x.voucher_no for x in data])) + + def test_ignore_cr_dr_notes_filter(self): + si = create_sales_invoice() + + cr_note = make_return_doc(si.doctype, si.name) + cr_note.submit() + + pr = frappe.get_doc("Payment Reconciliation") + pr.company = si.company + pr.party_type = "Customer" + pr.party = si.customer + pr.receivable_payable_account = si.debit_to + + pr.get_unreconciled_entries() + self.assertEqual(len(pr.invoices), 1) + self.assertEqual(len(pr.payments), 1) + + invoices = [invoice.as_dict() for invoice in pr.invoices] + payments = [payment.as_dict() for payment in pr.payments] + pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + pr.reconcile() + + self.assertEqual(len(pr.invoices), 0) + self.assertEqual(len(pr.payments), 0) + + system_generated_journal = frappe.db.get_all( + "Journal Entry", + filters={ + "docstatus": 1, + "reference_type": si.doctype, + "reference_name": si.name, + "voucher_type": "Credit Note", + "is_system_generated": True, + }, + fields=["name"], + ) + self.assertEqual(len(system_generated_journal), 1) + expected = set([si.name, cr_note.name, system_generated_journal[0].name]) + # Without ignore_cr_dr_notes + columns, data = execute( + frappe._dict( + { + "company": si.company, + "from_date": si.posting_date, + "to_date": si.posting_date, + "account": [si.debit_to], + "group_by": "Group by Voucher (Consolidated)", + "ignore_cr_dr_notes": False, + } + ) + ) + actual = set([x.voucher_no for x in data if x.voucher_no]) + self.assertEqual(expected, actual) + + # Without ignore_cr_dr_notes + expected = set([si.name, cr_note.name]) + columns, data = execute( + frappe._dict( + { + "company": si.company, + "from_date": si.posting_date, + "to_date": si.posting_date, + "account": [si.debit_to], + "group_by": "Group by Voucher (Consolidated)", + "ignore_cr_dr_notes": True, + } + ) + ) + actual = set([x.voucher_no for x in data if x.voucher_no]) + self.assertEqual(expected, actual) From 1da744dc4d630ae81ea59ae4d4c1b3e7c49130a0 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 5 Aug 2024 09:58:50 +0530 Subject: [PATCH 08/25] refactor: make use of date filters on ignore filterss (cherry picked from commit 03f3ab522f54612f557a7d2f715367daf8bc370e) --- erpnext/accounts/report/general_ledger/general_ledger.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 05bbc4a79c9..6598e474bcc 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -228,6 +228,7 @@ def get_conditions(filters): "company": filters.get("company"), "docstatus": 1, "voucher_type": ("in", ["Exchange Rate Revaluation", "Exchange Gain Or Loss"]), + "posting_date": ["between", [filters.get("from_date"), filters.get("to_date")]], }, as_list=True, ) @@ -242,6 +243,7 @@ def get_conditions(filters): "docstatus": 1, "voucher_type": ("in", ["Credit Note", "Debit Note"]), "is_system_generated": 1, + "posting_date": ["between", [filters.get("from_date"), filters.get("to_date")]], }, as_list=True, ) From 1b6539c3c5e0805b84283557ee246ac7b7eef923 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 5 Aug 2024 10:23:52 +0530 Subject: [PATCH 09/25] test: clear old data (cherry picked from commit 991069bfbcfba4ef46bae028dcdc908e785f693f) --- .../accounts/report/general_ledger/test_general_ledger.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/accounts/report/general_ledger/test_general_ledger.py b/erpnext/accounts/report/general_ledger/test_general_ledger.py index 682eef8f419..094da52e68a 100644 --- a/erpnext/accounts/report/general_ledger/test_general_ledger.py +++ b/erpnext/accounts/report/general_ledger/test_general_ledger.py @@ -2,6 +2,7 @@ # MIT License. See license.txt import frappe +from frappe import qb from frappe.tests.utils import FrappeTestCase from frappe.utils import flt, today @@ -252,6 +253,10 @@ class TestGeneralLedger(FrappeTestCase): self.assertIn(revaluation_jv.name, set([x.voucher_no for x in data])) def test_ignore_cr_dr_notes_filter(self): + # Clear old data + sinv_doctype = qb.DocType("Sales Invoice") + qb.from_(sinv_doctype).delete().where(sinv_doctype.company.eq("_Test Company")).run() + si = create_sales_invoice() cr_note = make_return_doc(si.doctype, si.name) From 392ba36dcf55846c210ab14b6766927e6e090dee Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 5 Aug 2024 10:46:35 +0530 Subject: [PATCH 10/25] refactor(test): filter and reconcile concerned vouchers (cherry picked from commit 9ade269b7a66a72a7858aad47704da6038a26094) --- .../report/general_ledger/test_general_ledger.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/erpnext/accounts/report/general_ledger/test_general_ledger.py b/erpnext/accounts/report/general_ledger/test_general_ledger.py index 094da52e68a..1a935bd05d7 100644 --- a/erpnext/accounts/report/general_ledger/test_general_ledger.py +++ b/erpnext/accounts/report/general_ledger/test_general_ledger.py @@ -253,10 +253,6 @@ class TestGeneralLedger(FrappeTestCase): self.assertIn(revaluation_jv.name, set([x.voucher_no for x in data])) def test_ignore_cr_dr_notes_filter(self): - # Clear old data - sinv_doctype = qb.DocType("Sales Invoice") - qb.from_(sinv_doctype).delete().where(sinv_doctype.company.eq("_Test Company")).run() - si = create_sales_invoice() cr_note = make_return_doc(si.doctype, si.name) @@ -269,17 +265,12 @@ class TestGeneralLedger(FrappeTestCase): pr.receivable_payable_account = si.debit_to pr.get_unreconciled_entries() - self.assertEqual(len(pr.invoices), 1) - self.assertEqual(len(pr.payments), 1) - invoices = [invoice.as_dict() for invoice in pr.invoices] - payments = [payment.as_dict() for payment in pr.payments] + invoices = [invoice.as_dict() for invoice in pr.invoices if invoice.invoice_number == si.name] + payments = [payment.as_dict() for payment in pr.payments if payment.reference_name == cr_note.name] pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) pr.reconcile() - self.assertEqual(len(pr.invoices), 0) - self.assertEqual(len(pr.payments), 0) - system_generated_journal = frappe.db.get_all( "Journal Entry", filters={ From f0bdc41a9455baa8a8a3432c4f06748694dd93b2 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 5 Aug 2024 12:58:21 +0530 Subject: [PATCH 11/25] refactor(test): clear old records --- .../report/general_ledger/test_general_ledger.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/erpnext/accounts/report/general_ledger/test_general_ledger.py b/erpnext/accounts/report/general_ledger/test_general_ledger.py index 1a935bd05d7..ee2a6772892 100644 --- a/erpnext/accounts/report/general_ledger/test_general_ledger.py +++ b/erpnext/accounts/report/general_ledger/test_general_ledger.py @@ -12,6 +12,22 @@ from erpnext.controllers.sales_and_purchase_return import make_return_doc class TestGeneralLedger(FrappeTestCase): + def setUp(self): + self.company = "_Test Company" + self.clear_old_records() + + def clear_old_records(self): + doctype_list = [ + "GL Entry", + "Payment Ledger Entry", + "Sales Invoice", + "Purchase Invoice", + "Payment Entry", + "Journal Entry", + ] + for doctype in doctype_list: + qb.from_(qb.DocType(doctype)).delete().where(qb.DocType(doctype).company == self.company).run() + def test_foreign_account_balance_after_exchange_rate_revaluation(self): """ Checks the correctness of balance after exchange rate revaluation From 703f58ceac1dc5fe15ef3c4f9023400420a27f39 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 31 Jul 2024 14:01:27 +0530 Subject: [PATCH 12/25] refactor: date filters should be explicit (cherry picked from commit 40c166a0a05bf8ff39ba55de0aa36b6a37afb890) --- .../sales_pipeline_analytics/sales_pipeline_analytics.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.py b/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.py index 9cc69d24a2b..0c540f75df0 100644 --- a/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.py +++ b/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.py @@ -147,6 +147,10 @@ class SalesPipelineAnalytics: conditions.append( ["expected_closing", "between", [self.filters.get("from_date"), self.filters.get("to_date")]] ) + elif self.filters.get("from_date") and not self.filters.get("to_date"): + conditions.append(["expected_closing", ">=", self.filters.get("from_date")]) + elif not self.filters.get("from_date") and self.filters.get("to_date"): + conditions.append(["expected_closing", "<=", self.filters.get("to_date")]) return conditions From f7f191fe50d5ed003fb07846743cbcc98633834b Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 5 Aug 2024 10:10:25 +0530 Subject: [PATCH 13/25] refactor: make 'from_date' and 'to_date' mandatory (cherry picked from commit 3617b41b9559e264ccdefd31faad99a8bc70f44b) --- .../sales_pipeline_analytics.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.py b/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.py index 0c540f75df0..4baa09195d5 100644 --- a/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.py +++ b/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.py @@ -21,7 +21,15 @@ class SalesPipelineAnalytics: def __init__(self, filters=None): self.filters = frappe._dict(filters or {}) + def validate_filters(self): + if not self.filters.from_date: + frappe.throw(_("From Date is mandatory")) + + if not self.filters.to_date: + frappe.throw(_("To Date is mandatory")) + def run(self): + self.validate_filters() self.get_columns() self.get_data() self.get_chart_data() @@ -147,10 +155,6 @@ class SalesPipelineAnalytics: conditions.append( ["expected_closing", "between", [self.filters.get("from_date"), self.filters.get("to_date")]] ) - elif self.filters.get("from_date") and not self.filters.get("to_date"): - conditions.append(["expected_closing", ">=", self.filters.get("from_date")]) - elif not self.filters.get("from_date") and self.filters.get("to_date"): - conditions.append(["expected_closing", "<=", self.filters.get("to_date")]) return conditions From baa36c6d5e33fa53580bbbf29440cb311a4c9d51 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 5 Aug 2024 11:31:13 +0530 Subject: [PATCH 14/25] refactor: report columns should be based on from and to dates (cherry picked from commit 751a25c4b74d8c9bd739a982c163caf32f124921) --- .../sales_pipeline_analytics/sales_pipeline_analytics.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.py b/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.py index 4baa09195d5..8460457c5ee 100644 --- a/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.py +++ b/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.py @@ -8,7 +8,7 @@ from itertools import groupby import frappe from dateutil.relativedelta import relativedelta from frappe import _ -from frappe.utils import cint, flt +from frappe.utils import cint, flt, getdate from erpnext.setup.utils import get_exchange_rate @@ -235,10 +235,9 @@ class SalesPipelineAnalytics: def get_month_list(self): month_list = [] - current_date = date.today() - month_number = date.today().month + current_date = getdate(self.filters.get("from_date")) - for _month in range(month_number, 13): + while current_date < getdate(self.filters.get("to_date")): month_list.append(current_date.strftime("%B")) current_date = current_date + relativedelta(months=1) From 989ef52f59d85edaaa9edf5d51a74b9e0659ee26 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 5 Aug 2024 14:08:08 +0530 Subject: [PATCH 15/25] refactor: consider empty-string as Not Assigned (cherry picked from commit 213b2ba94254655a07ed3df35b1158c35bf02469) --- .../report/sales_pipeline_analytics/sales_pipeline_analytics.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.py b/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.py index 8460457c5ee..e5d34231a87 100644 --- a/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.py +++ b/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.py @@ -193,7 +193,7 @@ class SalesPipelineAnalytics: count_or_amount = info.get(based_on) if self.filters.get("pipeline_by") == "Owner": - if value == "Not Assigned" or value == "[]" or value is None: + if value == "Not Assigned" or value == "[]" or value is None or not value: assigned_to = ["Not Assigned"] else: assigned_to = json.loads(value) From ffd3aea07e69c0479fa4b254036612d1d7e28bf6 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 5 Aug 2024 14:47:09 +0530 Subject: [PATCH 16/25] refactor(test): use test fixture and supply from and to dates (cherry picked from commit 4253caf910bdc47a3f7ee0b13cbf711f4a8560b1) --- .../test_sales_pipeline_analytics.py | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/erpnext/crm/report/sales_pipeline_analytics/test_sales_pipeline_analytics.py b/erpnext/crm/report/sales_pipeline_analytics/test_sales_pipeline_analytics.py index 02d82b637e8..bf3f946d6ab 100644 --- a/erpnext/crm/report/sales_pipeline_analytics/test_sales_pipeline_analytics.py +++ b/erpnext/crm/report/sales_pipeline_analytics/test_sales_pipeline_analytics.py @@ -1,19 +1,21 @@ import unittest import frappe +from frappe.tests.utils import FrappeTestCase from erpnext.crm.report.sales_pipeline_analytics.sales_pipeline_analytics import execute -class TestSalesPipelineAnalytics(unittest.TestCase): - @classmethod - def setUpClass(self): +class TestSalesPipelineAnalytics(FrappeTestCase): + def setUp(self): frappe.db.delete("Opportunity") create_company() create_customer() create_opportunity() def test_sales_pipeline_analytics(self): + self.from_date = "2021-01-01" + self.to_date = "2021-12-31" self.check_for_monthly_and_number() self.check_for_monthly_and_amount() self.check_for_quarterly_and_number() @@ -28,6 +30,8 @@ class TestSalesPipelineAnalytics(unittest.TestCase): "status": "Open", "opportunity_type": "Sales", "company": "Best Test", + "from_date": self.from_date, + "to_date": self.to_date, } report = execute(filters) @@ -43,6 +47,8 @@ class TestSalesPipelineAnalytics(unittest.TestCase): "status": "Open", "opportunity_type": "Sales", "company": "Best Test", + "from_date": self.from_date, + "to_date": self.to_date, } report = execute(filters) @@ -59,6 +65,8 @@ class TestSalesPipelineAnalytics(unittest.TestCase): "status": "Open", "opportunity_type": "Sales", "company": "Best Test", + "from_date": self.from_date, + "to_date": self.to_date, } report = execute(filters) @@ -74,6 +82,8 @@ class TestSalesPipelineAnalytics(unittest.TestCase): "status": "Open", "opportunity_type": "Sales", "company": "Best Test", + "from_date": self.from_date, + "to_date": self.to_date, } report = execute(filters) @@ -90,6 +100,8 @@ class TestSalesPipelineAnalytics(unittest.TestCase): "status": "Open", "opportunity_type": "Sales", "company": "Best Test", + "from_date": self.from_date, + "to_date": self.to_date, } report = execute(filters) @@ -105,6 +117,8 @@ class TestSalesPipelineAnalytics(unittest.TestCase): "status": "Open", "opportunity_type": "Sales", "company": "Best Test", + "from_date": self.from_date, + "to_date": self.to_date, } report = execute(filters) @@ -121,6 +135,8 @@ class TestSalesPipelineAnalytics(unittest.TestCase): "status": "Open", "opportunity_type": "Sales", "company": "Best Test", + "from_date": self.from_date, + "to_date": self.to_date, } report = execute(filters) @@ -136,6 +152,8 @@ class TestSalesPipelineAnalytics(unittest.TestCase): "status": "Open", "opportunity_type": "Sales", "company": "Best Test", + "from_date": self.from_date, + "to_date": self.to_date, } report = execute(filters) @@ -153,8 +171,8 @@ class TestSalesPipelineAnalytics(unittest.TestCase): "opportunity_type": "Sales", "company": "Best Test", "opportunity_source": "Cold Calling", - "from_date": "2021-08-01", - "to_date": "2021-08-31", + "from_date": self.from_date, + "to_date": self.to_date, } report = execute(filters) From 6ceb3476a61a9dac0b3e02d61e136305fa9ae50a Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Mon, 5 Aug 2024 17:37:29 +0530 Subject: [PATCH 17/25] ci: fix semgrep version in linters --- .github/workflows/linters.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml index af6d8f26a73..751b46f4b35 100644 --- a/.github/workflows/linters.yml +++ b/.github/workflows/linters.yml @@ -23,7 +23,7 @@ jobs: run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules - name: Download semgrep - run: pip install semgrep==0.97.0 + run: pip install semgrep - name: Run Semgrep rules run: semgrep ci --config ./frappe-semgrep-rules/rules --config r/python.lang.correctness From 8df1079618608bd90956de54e3d0d343d58b3734 Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Mon, 5 Aug 2024 16:57:10 +0530 Subject: [PATCH 18/25] fix: fixed depreciation calculation as per income tax act --- erpnext/assets/doctype/asset/asset.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 405eda8d09f..88e60d71d8d 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -362,11 +362,16 @@ class Asset(AccountsController): final_number_of_depreciations = cint(finance_book.total_number_of_depreciations) - cint( self.number_of_depreciations_booked ) - has_pro_rata = self.check_is_pro_rata(finance_book) + for_income_tax = 0 + if frappe.db.has_column("Finance Book", "for_income_tax"): + for_income_tax = frappe.db.get_value("Finance Book", finance_book.finance_book, "for_income_tax") + has_pro_rata = False + if not for_income_tax: + has_pro_rata = self.check_is_pro_rata(finance_book) depr_already_booked = any( [d.journal_entry for d in self.get("schedules") if d.finance_book == finance_book.finance_book] ) - if has_pro_rata and not depr_already_booked: + if has_pro_rata and not depr_already_booked and not for_income_tax: final_number_of_depreciations += 1 has_wdv_or_dd_non_yearly_pro_rata = False @@ -517,10 +522,13 @@ class Asset(AccountsController): ) # Adjust depreciation amount in the last period based on the expected value after useful life - if ( - n == cint(final_number_of_depreciations) - 1 - and flt(value_after_depreciation) != flt(finance_book.expected_value_after_useful_life) - ) or flt(value_after_depreciation) < flt(finance_book.expected_value_after_useful_life): + if not for_income_tax and ( + ( + n == cint(final_number_of_depreciations) - 1 + and flt(value_after_depreciation) != flt(finance_book.expected_value_after_useful_life) + ) + or flt(value_after_depreciation) < flt(finance_book.expected_value_after_useful_life) + ): depreciation_amount += flt(value_after_depreciation) - flt( finance_book.expected_value_after_useful_life ) From 5c5349ed16680a22a8fd16a1f6f9ed16957069b4 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 6 Aug 2024 08:19:46 +0530 Subject: [PATCH 19/25] =?UTF-8?q?fix:=20do=20not=20update=20item=20price?= =?UTF-8?q?=20and=20last=20purchase=20rate=20for=20inter=20transf=E2=80=A6?= =?UTF-8?q?=20(backport=20#42616)=20(#42632)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix: do not update item price and last purchase rate for inter transf… (#42616) fix: do not update item price and last purchase rate for inter transfer transaction (cherry picked from commit c8af544ef37d1fec4b93a6d1fc44cfbc441e677b) Co-authored-by: rohitwaghchaure --- erpnext/buying/utils.py | 3 +++ erpnext/public/js/controllers/transaction.js | 6 +++++- erpnext/stock/get_item_details.py | 9 ++++++++- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/erpnext/buying/utils.py b/erpnext/buying/utils.py index 1d708b36980..8223917792c 100644 --- a/erpnext/buying/utils.py +++ b/erpnext/buying/utils.py @@ -14,6 +14,9 @@ from erpnext.stock.doctype.item.item import get_last_purchase_details, validate_ def update_last_purchase_rate(doc, is_submit) -> None: """updates last_purchase_rate in item table for each item""" + if doc.get("is_internal_supplier"): + return + this_purchase_date = getdate(doc.get("posting_date") or doc.get("transaction_date")) for d in doc.get("items"): diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index d4b978b6e7e..bf7e5b9e48f 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -472,6 +472,8 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe quotation_to: me.frm.doc.quotation_to, supplier: me.frm.doc.supplier, currency: me.frm.doc.currency, + is_internal_supplier: me.frm.doc.is_internal_supplier, + is_internal_customer: me.frm.doc.is_internal_customer, update_stock: update_stock, conversion_rate: me.frm.doc.conversion_rate, price_list: me.frm.doc.selling_price_list || me.frm.doc.buying_price_list, @@ -1489,7 +1491,9 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe "update_stock": ['Sales Invoice', 'Purchase Invoice'].includes(me.frm.doc.doctype) ? cint(me.frm.doc.update_stock) : 0, "conversion_factor": me.frm.doc.conversion_factor, "pos_profile": me.frm.doc.doctype == 'Sales Invoice' ? me.frm.doc.pos_profile : '', - "coupon_code": me.frm.doc.coupon_code + "coupon_code": me.frm.doc.coupon_code, + "is_internal_supplier": me.frm.doc.is_internal_supplier, + "is_internal_customer": me.frm.doc.is_internal_customer, }; } diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 45b251f6630..0a8b7d407e4 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -855,6 +855,9 @@ def get_price_list_rate(args, item_doc, out=None): if price_list_rate is None or frappe.db.get_single_value( "Stock Settings", "update_existing_price_list_rate" ): + if args.get("is_internal_supplier") or args.get("is_internal_customer"): + return out + if args.price_list and args.rate: insert_item_price(args) @@ -866,7 +869,11 @@ def get_price_list_rate(args, item_doc, out=None): if frappe.db.get_single_value("Buying Settings", "disable_last_purchase_rate"): return out - if not out.price_list_rate and args.transaction_type == "buying": + if ( + not args.get("is_internal_supplier") + and not out.price_list_rate + and args.transaction_type == "buying" + ): from erpnext.stock.doctype.item.item import get_last_purchase_details out.update(get_last_purchase_details(item_doc.name, args.name, args.conversion_rate)) From f4ba879203376ab13d9eea9c1aa2dd1c74bd9d3d Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 6 Aug 2024 10:36:26 +0530 Subject: [PATCH 20/25] refactor: posting date is not considered for ignore filters in GL (cherry picked from commit c930f8ba9dabb19ca26a321ce8ed30d8a2036f85) --- erpnext/accounts/report/general_ledger/general_ledger.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 6598e474bcc..05bbc4a79c9 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -228,7 +228,6 @@ def get_conditions(filters): "company": filters.get("company"), "docstatus": 1, "voucher_type": ("in", ["Exchange Rate Revaluation", "Exchange Gain Or Loss"]), - "posting_date": ["between", [filters.get("from_date"), filters.get("to_date")]], }, as_list=True, ) @@ -243,7 +242,6 @@ def get_conditions(filters): "docstatus": 1, "voucher_type": ("in", ["Credit Note", "Debit Note"]), "is_system_generated": 1, - "posting_date": ["between", [filters.get("from_date"), filters.get("to_date")]], }, as_list=True, ) From 7086a964629899c73c73487ada1158c684cbd609 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Tue, 30 Jul 2024 17:49:56 +0530 Subject: [PATCH 21/25] fix: min height for rows in sales funnel (cherry picked from commit fd71d8af5262ee943a5b780b6f37d57ac4e14526) --- erpnext/selling/page/sales_funnel/sales_funnel.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/page/sales_funnel/sales_funnel.js b/erpnext/selling/page/sales_funnel/sales_funnel.js index d8546ba51ce..954705b5c32 100644 --- a/erpnext/selling/page/sales_funnel/sales_funnel.js +++ b/erpnext/selling/page/sales_funnel/sales_funnel.js @@ -193,6 +193,9 @@ erpnext.SalesFunnel = class SalesFunnel { this.options.width = ($(this.elements.funnel_wrapper).width() * 2.0) / 3.0; this.options.height = (Math.sqrt(3) * this.options.width) / 2.0; + const min_height = (this.options.height * 0.1) / this.options.data.length; + const height = this.options.height * 0.9; + // calculate total weightage // as height decreases, area decreases by the square of the reduction // hence, compensating by squaring the index value @@ -202,7 +205,7 @@ erpnext.SalesFunnel = class SalesFunnel { // calculate height for each data $.each(this.options.data, function (i, d) { - d.height = (me.options.height * d.value * Math.pow(i + 1, 2)) / me.options.total_weightage; + d.height = (height * d.value * Math.pow(i + 1, 2)) / me.options.total_weightage + min_height; }); this.elements.canvas = $("") From b7731c8fd74a66067666d98406b142b732f7596f Mon Sep 17 00:00:00 2001 From: ljain112 Date: Fri, 2 Aug 2024 13:12:04 +0530 Subject: [PATCH 22/25] fix: company filter for filtring tax withheld vouchers (cherry picked from commit cfe2ae604b9f353fa24782127641ce502fb304bb) --- .../tax_withholding_category/tax_withholding_category.py | 3 +++ 1 file changed, 3 insertions(+) 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 c26c7568924..1283f2fdaaf 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -343,12 +343,14 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"): AND ja.party in %s AND j.apply_tds = 1 AND j.tax_withholding_category = %s + AND j.company = %s """, ( tax_details.from_date, tax_details.to_date, tuple(parties), tax_details.get("tax_withholding_category"), + company, ), as_dict=1, ) @@ -465,6 +467,7 @@ def get_tds_amount(ldc, parties, inv, tax_details, vouchers): "unallocated_amount": (">", 0), "posting_date": ["between", (tax_details.from_date, tax_details.to_date)], "tax_withholding_category": tax_details.get("tax_withholding_category"), + "company": inv.company, } field = "sum(tax_withholding_net_total)" From 83928bbf78f500728c934e3e764ad069a8d55448 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 6 Aug 2024 15:24:37 +0530 Subject: [PATCH 23/25] feat: expiry date column in Available Batch Report (backport #42628) (#42641) * feat: expiry date column in Available Batch Report (#42628) (cherry picked from commit faff84c6e5d65472bc55c8f0f426eb64db58936f) # Conflicts: # erpnext/stock/report/available_batch_report/available_batch_report.py * chore: fix conflicts --------- Co-authored-by: rohitwaghchaure --- .../available_batch_report.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/report/available_batch_report/available_batch_report.py b/erpnext/stock/report/available_batch_report/available_batch_report.py index cb651dd7972..99af98c23bb 100644 --- a/erpnext/stock/report/available_batch_report/available_batch_report.py +++ b/erpnext/stock/report/available_batch_report/available_batch_report.py @@ -54,6 +54,12 @@ def get_columns(filters): "width": 150, "options": "Batch", }, + { + "label": _("Expiry Date"), + "fieldname": "expiry_date", + "fieldtype": "Date", + "width": 120, + }, {"label": _("Balance Qty"), "fieldname": "balance_qty", "fieldtype": "Float", "width": 150}, ] ) @@ -96,6 +102,7 @@ def get_batchwise_data_from_stock_ledger(filters): table.item_code, table.batch_no, table.warehouse, + batch.expiry_date, Sum(table.actual_qty).as_("balance_qty"), ) .where(table.is_cancelled == 0) @@ -118,10 +125,14 @@ def get_query_based_on_filters(query, batch, table, filters): if filters.batch_no: query = query.where(batch.name == filters.batch_no) - if not filters.include_expired_batches: - query = query.where((batch.expiry_date >= today()) | (batch.expiry_date.isnull())) - if filters.to_date == today(): - query = query.where(batch.batch_qty > 0) + if filters.to_date == today(): + if not filters.include_expired_batches: + query = query.where((batch.expiry_date >= today()) | (batch.expiry_date.isnull())) + + query = query.where(batch.batch_qty > 0) + + else: + query = query.where(table.posting_date <= filters.to_date) if filters.warehouse: lft, rgt = frappe.db.get_value("Warehouse", filters.warehouse, ["lft", "rgt"]) From f0f8a2f01baff0b9e585cdcd32ac809c83585feb Mon Sep 17 00:00:00 2001 From: Nihantra Patel Date: Tue, 6 Aug 2024 15:54:05 +0530 Subject: [PATCH 24/25] fix: item_code filter in item-wise sales register --- .../item_wise_sales_register/item_wise_sales_register.js | 6 ++++++ .../item_wise_sales_register/item_wise_sales_register.py | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.js b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.js index d019b718ffb..6fb015f7b74 100644 --- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.js +++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.js @@ -54,6 +54,12 @@ frappe.query_reports["Item-wise Sales Register"] = { fieldtype: "Link", options: "Brand", }, + { + fieldname: "item_code", + label: __("Item"), + fieldtype: "Link", + options: "Item", + }, { fieldname: "item_group", label: __("Item Group"), 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 7bb73143c28..604c0a6569d 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 @@ -342,7 +342,7 @@ def get_columns(additional_table_columns, filters): def apply_conditions(query, si, sii, filters, additional_conditions=None): - for opts in ("company", "customer", "item_code"): + for opts in ("company", "customer"): if filters.get(opts): query = query.where(si[opts] == filters[opts]) @@ -371,6 +371,9 @@ def apply_conditions(query, si, sii, filters, additional_conditions=None): if filters.get("brand"): query = query.where(sii.brand == filters.get("brand")) + if filters.get("item_code"): + query = query.where(sii.item_code == filters.get("item_code")) + if filters.get("item_group"): query = query.where(sii.item_group == filters.get("item_group")) From bc9b46126bd4254d64c0342fa3ce890ed8f310f1 Mon Sep 17 00:00:00 2001 From: Nihantra Patel Date: Tue, 6 Aug 2024 15:56:00 +0530 Subject: [PATCH 25/25] fix: filter condition in item-wise purchase register --- .../item_wise_purchase_register.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py index f55fef068c1..83f664c984a 100644 --- a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py +++ b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py @@ -289,7 +289,7 @@ def get_columns(additional_table_columns, filters): def apply_conditions(query, pi, pii, filters): - for opts in ("company", "supplier", "item_code", "mode_of_payment"): + for opts in ("company", "supplier", "mode_of_payment"): if filters.get(opts): query = query.where(pi[opts] == filters[opts]) @@ -299,6 +299,9 @@ def apply_conditions(query, pi, pii, filters): if filters.get("to_date"): query = query.where(pi.posting_date <= filters.get("to_date")) + if filters.get("item_code"): + query = query.where(pii.item_code == filters.get("item_code")) + if filters.get("item_group"): query = query.where(pii.item_group == filters.get("item_group"))