From 822bc581f3965f85f0080cfc0b2426520538fccb Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 3 Jun 2024 10:09:20 +0530 Subject: [PATCH 01/25] fix: Do no apply pricing rule on qty change for mapped docs (cherry picked from commit 2b1242170c87f558945df18d3414903a3311570b) --- erpnext/public/js/controllers/transaction.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 937b19d9abb..139ab4bff0f 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1246,8 +1246,8 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } qty(doc, cdt, cdn) { - if (!this.frm.doc.__onload?.load_after_mapping) { - let item = frappe.get_doc(cdt, cdn); + let item = frappe.get_doc(cdt, cdn); + if (!this.is_a_mapped_document(item)) { // item.pricing_rules = '' frappe.run_serially([ () => this.remove_pricing_rule_for_item(item), From 0e562e9933fba768731f04a89a15b51554caed8a Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 6 Jun 2024 13:57:14 +0530 Subject: [PATCH 02/25] chore: repost settings permission overhaul (cherry picked from commit 458d8f5ed8affecf0d25bd732dc5af6f786d894c) --- .../repost_accounting_ledger_settings.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.json b/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.json index 8aa0a840c7e..dd314ca6601 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.json +++ b/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.json @@ -17,7 +17,7 @@ "in_create": 1, "issingle": 1, "links": [], - "modified": "2023-11-07 14:24:13.321522", + "modified": "2024-06-06 13:56:37.908879", "modified_by": "Administrator", "module": "Accounts", "name": "Repost Accounting Ledger Settings", @@ -30,13 +30,17 @@ "print": 1, "read": 1, "role": "Administrator", + "select": 1, "share": 1, "write": 1 }, { + "create": 1, + "delete": 1, "read": 1, "role": "System Manager", - "select": 1 + "select": 1, + "write": 1 } ], "sort_field": "modified", From 94720851c6b819f3466dbae45a566f2d1830e2e3 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 6 Jun 2024 17:22:27 +0530 Subject: [PATCH 03/25] fix: Add posting date to key for consolidated view (cherry picked from commit 2c164b47a13bc458b10df95f0aa3f2425bef1bfb) --- erpnext/accounts/report/general_ledger/general_ledger.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 2946cfa0e82..44fad110baa 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -482,6 +482,7 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map): elif group_by_voucher_consolidated: keylist = [ + gle.get("posting_date"), gle.get("voucher_type"), gle.get("voucher_no"), gle.get("account"), From 4d3e88313013af0e3741a0b108c49f6680fc95a5 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 6 Jun 2024 19:46:02 +0530 Subject: [PATCH 04/25] fix: typo in method - holiday list (backport #41811) (#41812) fix: typo in method - holiday list (#41811) (cherry picked from commit 699cfd85c64916163f6ef156d47297c5725028ac) Co-authored-by: Nihantra C. Patel <141945075+Nihantra-Patel@users.noreply.github.com> --- erpnext/setup/doctype/holiday_list/holiday_list.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/setup/doctype/holiday_list/holiday_list.py b/erpnext/setup/doctype/holiday_list/holiday_list.py index 9d854c7c412..6932f7be825 100644 --- a/erpnext/setup/doctype/holiday_list/holiday_list.py +++ b/erpnext/setup/doctype/holiday_list/holiday_list.py @@ -42,7 +42,7 @@ class HolidayList(Document): def validate(self): self.validate_days() self.total_holidays = len(self.holidays) - self.validate_dupliacte_date() + self.validate_duplicate_date() @frappe.whitelist() def get_weekly_off_dates(self): @@ -148,7 +148,7 @@ class HolidayList(Document): def clear_table(self): self.set("holidays", []) - def validate_dupliacte_date(self): + def validate_duplicate_date(self): unique_dates = [] for row in self.holidays: if row.holiday_date in unique_dates: From 4d37739dd3ede937e93e8328fa6a147462efc241 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 7 Jun 2024 15:45:50 +0530 Subject: [PATCH 05/25] fix(Vehicle): allow doc renaming and add test (backport #41729) (#41785) * fix(Vehicle): allow doc renaming and add test (#41729) fix(Vehicle): allow renaming doc and add test (cherry picked from commit c76f466528e2e6651df5d72e14f9418b8d5bec65) # Conflicts: # erpnext/setup/doctype/vehicle/vehicle.json * fix: resolve conflicts for backporting --------- Co-authored-by: Viny Selopal <52369157+vinyselopal@users.noreply.github.com> Co-authored-by: Viny Selopal --- erpnext/setup/doctype/vehicle/test_vehicle.py | 26 +++++++++++++++++++ erpnext/setup/doctype/vehicle/vehicle.json | 6 +++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/erpnext/setup/doctype/vehicle/test_vehicle.py b/erpnext/setup/doctype/vehicle/test_vehicle.py index 97fe651122c..3cd52386284 100644 --- a/erpnext/setup/doctype/vehicle/test_vehicle.py +++ b/erpnext/setup/doctype/vehicle/test_vehicle.py @@ -26,3 +26,29 @@ class TestVehicle(unittest.TestCase): } ) vehicle.insert() + + def test_renaming_vehicle(self): + license_plate = random_string(10).upper() + + vehicle = frappe.get_doc( + { + "doctype": "Vehicle", + "license_plate": license_plate, + "make": "Skoda", + "model": "Slavia", + "last_odometer": 5000, + "acquisition_date": frappe.utils.nowdate(), + "location": "Mumbai", + "chassis_no": "1234EFGH", + "uom": "Litre", + "vehicle_value": frappe.utils.flt(500000), + } + ) + vehicle.insert() + + new_license_plate = random_string(10).upper() + frappe.rename_doc("Vehicle", license_plate, new_license_plate) + + self.assertEqual( + new_license_plate, frappe.db.get_value("Vehicle", new_license_plate, "license_plate") + ) diff --git a/erpnext/setup/doctype/vehicle/vehicle.json b/erpnext/setup/doctype/vehicle/vehicle.json index b19d45924fb..c76719e4b92 100644 --- a/erpnext/setup/doctype/vehicle/vehicle.json +++ b/erpnext/setup/doctype/vehicle/vehicle.json @@ -2,7 +2,8 @@ "allow_copy": 0, "allow_guest_to_view": 0, "allow_import": 0, - "allow_rename": 0, + "actions": [], + "allow_rename": 1, "autoname": "field:license_plate", "beta": 0, "creation": "2016-09-03 03:33:27.680331", @@ -834,7 +835,8 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2022-06-27 14:48:30.813359", + "links": [], + "modified": "2024-05-31 06:38:15.399283", "modified_by": "Administrator", "module": "Setup", "name": "Vehicle", From f1ad0f6f269c7eeb9668798f0e6815a620c38a95 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 7 Jun 2024 16:50:21 +0530 Subject: [PATCH 06/25] fix: enable no_copy for timesheet in sales invoice --- erpnext/accounts/doctype/sales_invoice/sales_invoice.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 1b36bf50f86..90b6e768092 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -787,6 +787,7 @@ "hide_days": 1, "hide_seconds": 1, "label": "Time Sheets", + "no_copy": 1, "options": "Sales Invoice Timesheet", "print_hide": 1 }, @@ -2187,7 +2188,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2024-05-08 18:02:28.549041", + "modified": "2024-06-07 16:49:32.458402", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", From 8d31243a42a37b4dd0947d4d506815e8d999a009 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 7 Jun 2024 18:25:58 +0530 Subject: [PATCH 07/25] fix: valuation rate for backdated legacy serial/batches (backport #41788) (#41821) fix: valuation rate for backdated legacy serial/batches (#41788) (cherry picked from commit 3956354e0834a438c24de0f2dee7c5c8c4e613cc) Co-authored-by: rohitwaghchaure --- erpnext/stock/stock_ledger.py | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index abbd48853cc..ef088927024 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -36,6 +36,7 @@ from erpnext.stock.utils import ( get_incoming_outgoing_rate_for_cancel, get_incoming_rate, get_or_make_bin, + get_serial_nos_data, get_stock_balance, get_valuation_method, ) @@ -811,9 +812,10 @@ class update_entries_after: self.update_outgoing_rate_on_transaction(sle) def get_serialized_values(self, sle): + from erpnext.stock.serial_batch_bundle import SerialNoValuation + incoming_rate = flt(sle.incoming_rate) actual_qty = flt(sle.actual_qty) - serial_nos = cstr(sle.serial_no).split("\n") if incoming_rate < 0: # wrong incoming rate @@ -826,7 +828,15 @@ class update_entries_after: # In case of delivery/stock issue, get average purchase rate # of serial nos of current entry if not sle.is_cancelled: - outgoing_value = self.get_incoming_value_for_serial_nos(sle, serial_nos) + new_sle = copy.deepcopy(sle) + new_sle.qty = new_sle.actual_qty + new_sle.serial_nos = get_serial_nos_data(new_sle.get("serial_no")) + + sn_obj = SerialNoValuation( + sle=new_sle, warehouse=new_sle.get("warehouse"), item_code=new_sle.get("item_code") + ) + + outgoing_value = sn_obj.get_incoming_rate() stock_value_change = -1 * outgoing_value else: stock_value_change = actual_qty * sle.outgoing_rate @@ -1272,6 +1282,8 @@ class update_entries_after: self.wh_data.valuation_rate = self.wh_data.stock_value / self.wh_data.qty_after_transaction def update_batched_values(self, sle): + from erpnext.stock.serial_batch_bundle import BatchNoValuation + incoming_rate = flt(sle.incoming_rate) actual_qty = flt(sle.actual_qty) @@ -1282,15 +1294,18 @@ class update_entries_after: if actual_qty > 0: stock_value_difference = incoming_rate * actual_qty else: - outgoing_rate = get_batch_incoming_rate( - item_code=sle.item_code, - warehouse=sle.warehouse, - batch_no=sle.batch_no, - posting_date=sle.posting_date, - posting_time=sle.posting_time, - creation=sle.creation, + new_sle = copy.deepcopy(sle) + + new_sle.qty = new_sle.actual_qty + new_sle.batch_nos = frappe._dict({new_sle.batch_no: new_sle}) + batch_obj = BatchNoValuation( + sle=new_sle, + warehouse=new_sle.get("warehouse"), + item_code=new_sle.get("item_code"), ) + outgoing_rate = batch_obj.get_incoming_rate() + if outgoing_rate is None: # This can *only* happen if qty available for the batch is zero. # in such case fall back various other rates. From a7971cf8443dac71347369fb38ab2edf0ed8dc27 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 7 Jun 2024 23:05:50 +0530 Subject: [PATCH 08/25] fix: stock_value_change for legacy serial nos (backport #41825) (#41826) fix: stock_value_change for legacy serial nos (#41825) (cherry picked from commit f6a4d391c01f751f7cea5050690460b8032cbbbd) Co-authored-by: rohitwaghchaure --- erpnext/stock/stock_ledger.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index ef088927024..e804ae18016 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -837,7 +837,7 @@ class update_entries_after: ) outgoing_value = sn_obj.get_incoming_rate() - stock_value_change = -1 * outgoing_value + stock_value_change = actual_qty * outgoing_value else: stock_value_change = actual_qty * sle.outgoing_rate @@ -1312,6 +1312,7 @@ class update_entries_after: # future entries will correct the overall accounting as each # batch individually uses moving average rates. outgoing_rate = self.get_fallback_rate(sle) + stock_value_difference = outgoing_rate * actual_qty self.wh_data.stock_value = round_off_if_near_zero(self.wh_data.stock_value + stock_value_difference) From f36db7ebaf0c1e3c0de716f2733961be4f5b632e Mon Sep 17 00:00:00 2001 From: Poorvi Date: Fri, 10 May 2024 11:47:57 +0530 Subject: [PATCH 09/25] fix: fixing Item-wise sales register (cherry picked from commit c90185f5331e2271afb5ddc22340d3704f5bbcd3) --- .../accounts/report/purchase_register/purchase_register.py | 6 ++++-- erpnext/accounts/report/sales_register/sales_register.py | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/report/purchase_register/purchase_register.py b/erpnext/accounts/report/purchase_register/purchase_register.py index 504c74babcb..7631506f506 100644 --- a/erpnext/accounts/report/purchase_register/purchase_register.py +++ b/erpnext/accounts/report/purchase_register/purchase_register.py @@ -376,8 +376,10 @@ def get_account_columns(invoice_list, include_payments): def get_invoices(filters, additional_query_columns): pi = frappe.qb.DocType("Purchase Invoice") + pii = frappe.qb.DocType("Purchase Invoice Item") query = ( frappe.qb.from_(pi) + .left_join(pii).on(pi.name == pii.parent) .select( ConstantColumn("Purchase Invoice").as_("doctype"), pi.name, @@ -386,7 +388,7 @@ def get_invoices(filters, additional_query_columns): pi.supplier, pi.supplier_name, pi.tax_id, - pi.bill_no, + pi.bill_no, pi.bill_date, pi.remarks, pi.base_net_total, @@ -395,7 +397,7 @@ def get_invoices(filters, additional_query_columns): pi.outstanding_amount, pi.mode_of_payment, ) - .where(pi.docstatus == 1) + .where((pi.docstatus == 1) & pii.item_code.isnotnull()) .orderby(pi.posting_date, pi.name, order=Order.desc) ) diff --git a/erpnext/accounts/report/sales_register/sales_register.py b/erpnext/accounts/report/sales_register/sales_register.py index f27569531b1..832845ffe2e 100644 --- a/erpnext/accounts/report/sales_register/sales_register.py +++ b/erpnext/accounts/report/sales_register/sales_register.py @@ -414,9 +414,11 @@ def get_account_columns(invoice_list, include_payments): def get_invoices(filters, additional_query_columns): si = frappe.qb.DocType("Sales Invoice") + sii = frappe.qb.DocType("Sales Invoice Item") query = ( frappe.qb.from_(si) - .select( + .left_join(sii).on(si.name == sii.parent) + .select( ConstantColumn("Sales Invoice").as_("doctype"), si.name, si.posting_date, @@ -437,7 +439,7 @@ def get_invoices(filters, additional_query_columns): si.represents_company, si.company, ) - .where(si.docstatus == 1) + .where((si.docstatus == 1) & sii.item_code.isnotnull()) .orderby(si.posting_date, si.name, order=Order.desc) ) From 96405e8e4957c672b7a4aa5f7e9ead081a6e143d Mon Sep 17 00:00:00 2001 From: Poorvi-R-Bhat Date: Mon, 13 May 2024 09:35:10 +0530 Subject: [PATCH 10/25] fix: Item-wise Sales and Purchase register with no item codes #41373 (cherry picked from commit 1b45ecfcae5ed9587c5d03e14afaf6d39d3fa84f) # Conflicts: # erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py # erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py --- .../item_wise_purchase_register.py | 36 +++++++++++++++- .../item_wise_sales_register.py | 43 +++++++++++++++++++ .../purchase_register/purchase_register.py | 4 +- .../report/sales_register/sales_register.py | 6 +-- 4 files changed, 81 insertions(+), 8 deletions(-) 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 ebdea0602d1..02fb873c572 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 @@ -311,8 +311,8 @@ def get_conditions(filters): return conditions - def get_items(filters, additional_query_columns): +<<<<<<< HEAD conditions = get_conditions(filters) if additional_query_columns: additional_query_columns = "," + ",".join(additional_query_columns) @@ -339,6 +339,40 @@ def get_items(filters, additional_query_columns): filters, as_dict=1, ) +======= + pi = frappe.qb.DocType('Purchase Invoice') + pii = frappe.qb.DocType('Purchase Invoice Item') + Item = frappe.qb.DocType('Item') + query = (frappe.qb.from_(pi) + .join(pii).on(pi.name == pii.parent) + .left_join(Item).on(pii.item_code == Item.name) + .select( + pii.name.as_('pii_name'), pii.parent, + pi.posting_date, pi.credit_to, pi.company, + pi.supplier, pi.remarks, pi.base_net_total, + pi.unrealized_profit_loss_account, + pii.item_code, pii.description, pii.item_group, + pii.item_name.as_('pi_item_name'), pii.item_group.as_('pi_item_group'), + Item.item_name.as_('i_item_name'), Item.item_group.as_('i_item_group'), + pii.project, pii.purchase_order, + pii.purchase_receipt, pii.po_detail, + pii.expense_account, pii.stock_qty, + pii.stock_uom, pii.base_net_amount, + pi.supplier_name, pi.mode_of_payment + ) + .where(pi.docstatus == 1)) + + if additional_query_columns: + query = query.select(*additional_query_columns) + + if filters.get("supplier"): + query = query.where(pi.supplier == filters['supplier']) + if filters.get("company"): + query = query.where(pi.company == filters['company']) + + return query.run(as_dict=True) + +>>>>>>> 1b45ecfcae (fix: Item-wise Sales and Purchase register with no item codes #41373) def get_aii_accounts(): 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 c7819731930..fd538b7bfa6 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 @@ -388,6 +388,7 @@ def get_group_by_conditions(filters, doctype): return "ORDER BY `tab{}`.{}".format(doctype, frappe.scrub(filters.get("group_by"))) +<<<<<<< HEAD def get_items(filters, additional_query_columns, additional_conditions=None): conditions = get_conditions(filters, additional_conditions) if additional_query_columns: @@ -420,6 +421,48 @@ def get_items(filters, additional_query_columns, additional_conditions=None): filters, as_dict=1, ) # nosec +======= +def get_items(filters, additional_query_columns,additional_conditions=None): + si = frappe.qb.DocType('Sales Invoice') + sii = frappe.qb.DocType('Sales Invoice Item') + Item = frappe.qb.DocType('Item') + + query = ( + frappe.qb.from_(si) + .join(sii).on(si.name == sii.parent) + .left_join(Item).on(sii.item_code == Item.name) + .select( + sii.name, sii.parent, + si.posting_date, si.debit_to, + si.unrealized_profit_loss_account, + si.is_internal_customer, + si.customer, si.remarks, + si.territory, si.company, si.base_net_total, + sii.project, + sii.item_code, sii.description, + sii.item_name, sii.item_group, + sii.item_name.as_('si_item_name'), sii.item_group.as_('si_item_group'), + Item.item_name.as_('i_item_name'), Item.item_group.as_('i_item_group'), + sii.sales_order, sii.delivery_note, + sii.income_account, sii.cost_center, + sii.enable_deferred_revenue, sii.deferred_revenue_account, + sii.stock_qty, sii.stock_uom, + sii.base_net_rate, sii.base_net_amount, + si.customer_name, si.customer_group, sii.so_detail, + si.update_stock, sii.uom, sii.qty + ) + .where(si.docstatus == 1) + ) + if filters.get("customer"): + query = query.where(si.customer == filters['customer']) + + if filters.get("customer_group"): + query = query.where(si.customer_group == filters['customer_group']) + + return query.run(as_dict=True) + + +>>>>>>> 1b45ecfcae (fix: Item-wise Sales and Purchase register with no item codes #41373) def get_delivery_notes_against_sales_order(item_list): diff --git a/erpnext/accounts/report/purchase_register/purchase_register.py b/erpnext/accounts/report/purchase_register/purchase_register.py index 7631506f506..25caa9e03fb 100644 --- a/erpnext/accounts/report/purchase_register/purchase_register.py +++ b/erpnext/accounts/report/purchase_register/purchase_register.py @@ -376,10 +376,8 @@ def get_account_columns(invoice_list, include_payments): def get_invoices(filters, additional_query_columns): pi = frappe.qb.DocType("Purchase Invoice") - pii = frappe.qb.DocType("Purchase Invoice Item") query = ( frappe.qb.from_(pi) - .left_join(pii).on(pi.name == pii.parent) .select( ConstantColumn("Purchase Invoice").as_("doctype"), pi.name, @@ -397,7 +395,7 @@ def get_invoices(filters, additional_query_columns): pi.outstanding_amount, pi.mode_of_payment, ) - .where((pi.docstatus == 1) & pii.item_code.isnotnull()) + .where((pi.docstatus == 1)) .orderby(pi.posting_date, pi.name, order=Order.desc) ) diff --git a/erpnext/accounts/report/sales_register/sales_register.py b/erpnext/accounts/report/sales_register/sales_register.py index 832845ffe2e..dc557433f7d 100644 --- a/erpnext/accounts/report/sales_register/sales_register.py +++ b/erpnext/accounts/report/sales_register/sales_register.py @@ -414,11 +414,9 @@ def get_account_columns(invoice_list, include_payments): def get_invoices(filters, additional_query_columns): si = frappe.qb.DocType("Sales Invoice") - sii = frappe.qb.DocType("Sales Invoice Item") query = ( frappe.qb.from_(si) - .left_join(sii).on(si.name == sii.parent) - .select( + .select( ConstantColumn("Sales Invoice").as_("doctype"), si.name, si.posting_date, @@ -439,7 +437,7 @@ def get_invoices(filters, additional_query_columns): si.represents_company, si.company, ) - .where((si.docstatus == 1) & sii.item_code.isnotnull()) + .where((si.docstatus == 1)) .orderby(si.posting_date, si.name, order=Order.desc) ) From a4b28ba354873a8bb5253a5902970fc780aa9441 Mon Sep 17 00:00:00 2001 From: Poorvi-R-Bhat Date: Wed, 22 May 2024 12:26:02 +0530 Subject: [PATCH 11/25] fix: fixing Item-wise sales register and purchase register #41373 (cherry picked from commit 76073ae228d9f3ebe67eae562e2e48bd54ace252) # Conflicts: # erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py --- .../item_wise_purchase_register.py | 62 ++++++++++++++++--- 1 file changed, 54 insertions(+), 8 deletions(-) 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 02fb873c572..bc2f24b8076 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 @@ -311,7 +311,9 @@ def get_conditions(filters): return conditions + def get_items(filters, additional_query_columns): +<<<<<<< HEAD <<<<<<< HEAD conditions = get_conditions(filters) if additional_query_columns: @@ -361,18 +363,62 @@ def get_items(filters, additional_query_columns): pi.supplier_name, pi.mode_of_payment ) .where(pi.docstatus == 1)) +======= + pi = frappe.qb.DocType("Purchase Invoice") + pii = frappe.qb.DocType("Purchase Invoice Item") + Item = frappe.qb.DocType("Item") + query = ( + frappe.qb.from_(pi) + .join(pii) + .on(pi.name == pii.parent) + # added left join + .left_join(Item) + .on(pii.item_code == Item.name) + .select( + pii.name.as_("pii_name"), + pii.parent, + pi.posting_date, + pi.credit_to, + pi.company, + pi.supplier, + pi.remarks, + pi.base_net_total, + pi.unrealized_profit_loss_account, + pii.item_code, + pii.description, + pii.item_group, + pii.item_name.as_("pi_item_name"), + pii.item_group.as_("pi_item_group"), + Item.item_name.as_("i_item_name"), + Item.item_group.as_("i_item_group"), + pii.project, + pii.purchase_order, + pii.purchase_receipt, + pii.po_detail, + pii.expense_account, + pii.stock_qty, + pii.stock_uom, + pii.base_net_amount, + pi.supplier_name, + pi.mode_of_payment, + ) + .where(pi.docstatus == 1) + ) +>>>>>>> 76073ae228 (fix: fixing Item-wise sales register and purchase register #41373) - if additional_query_columns: - query = query.select(*additional_query_columns) + if additional_query_columns: + query = query.select(*additional_query_columns) - if filters.get("supplier"): - query = query.where(pi.supplier == filters['supplier']) - if filters.get("company"): - query = query.where(pi.company == filters['company']) - - return query.run(as_dict=True) + if filters.get("supplier"): + query = query.where(pi.supplier == filters["supplier"]) + if filters.get("company"): + query = query.where(pi.company == filters["company"]) +<<<<<<< HEAD >>>>>>> 1b45ecfcae (fix: Item-wise Sales and Purchase register with no item codes #41373) +======= + return query.run(as_dict=True) +>>>>>>> 76073ae228 (fix: fixing Item-wise sales register and purchase register #41373) def get_aii_accounts(): From 5e0fa1b75ec1437516aa190550ccf8fbfab67a6e Mon Sep 17 00:00:00 2001 From: Poorvi-R-Bhat Date: Wed, 22 May 2024 12:39:22 +0530 Subject: [PATCH 12/25] fix: fixing Item-wise sales register #41373 (cherry picked from commit eafa88b8e9da824d89e5673504c0de3422b54b6b) # Conflicts: # erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py --- .../item_wise_purchase_register.py | 1 - .../item_wise_sales_register.py | 66 +++++++++++++++++-- .../purchase_register/purchase_register.py | 6 +- .../report/sales_register/sales_register.py | 4 +- 4 files changed, 67 insertions(+), 10 deletions(-) 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 bc2f24b8076..086026efb53 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 @@ -371,7 +371,6 @@ def get_items(filters, additional_query_columns): frappe.qb.from_(pi) .join(pii) .on(pi.name == pii.parent) - # added left join .left_join(Item) .on(pii.item_code == Item.name) .select( 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 fd538b7bfa6..102d9d6a0af 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 @@ -388,6 +388,7 @@ def get_group_by_conditions(filters, doctype): return "ORDER BY `tab{}`.{}".format(doctype, frappe.scrub(filters.get("group_by"))) +<<<<<<< HEAD <<<<<<< HEAD def get_items(filters, additional_query_columns, additional_conditions=None): conditions = get_conditions(filters, additional_conditions) @@ -455,14 +456,71 @@ def get_items(filters, additional_query_columns,additional_conditions=None): ) if filters.get("customer"): query = query.where(si.customer == filters['customer']) +======= +def get_items(filters, additional_query_columns, additional_conditions=None): + si = frappe.qb.DocType("Sales Invoice") + sii = frappe.qb.DocType("Sales Invoice Item") + Item = frappe.qb.DocType("Item") +>>>>>>> eafa88b8e9 (fix: fixing Item-wise sales register #41373) - if filters.get("customer_group"): - query = query.where(si.customer_group == filters['customer_group']) - - return query.run(as_dict=True) + query = ( + frappe.qb.from_(si) + .join(sii) + .on(si.name == sii.parent) + # added left join + .left_join(Item) + .on(sii.item_code == Item.name) + .select( + sii.name, + sii.parent, + si.posting_date, + si.debit_to, + si.unrealized_profit_loss_account, + si.is_internal_customer, + si.customer, + si.remarks, + si.territory, + si.company, + si.base_net_total, + sii.project, + sii.item_code, + sii.description, + sii.item_name, + sii.item_group, + sii.item_name.as_("si_item_name"), + sii.item_group.as_("si_item_group"), + Item.item_name.as_("i_item_name"), + Item.item_group.as_("i_item_group"), + sii.sales_order, + sii.delivery_note, + sii.income_account, + sii.cost_center, + sii.enable_deferred_revenue, + sii.deferred_revenue_account, + sii.stock_qty, + sii.stock_uom, + sii.base_net_rate, + sii.base_net_amount, + si.customer_name, + si.customer_group, + sii.so_detail, + si.update_stock, + sii.uom, + sii.qty, + ) + .where(si.docstatus == 1) + ) + if filters.get("customer"): + query = query.where(si.customer == filters["customer"]) + if filters.get("customer_group"): + query = query.where(si.customer_group == filters["customer_group"]) +<<<<<<< HEAD >>>>>>> 1b45ecfcae (fix: Item-wise Sales and Purchase register with no item codes #41373) +======= + return query.run(as_dict=True) +>>>>>>> eafa88b8e9 (fix: fixing Item-wise sales register #41373) def get_delivery_notes_against_sales_order(item_list): diff --git a/erpnext/accounts/report/purchase_register/purchase_register.py b/erpnext/accounts/report/purchase_register/purchase_register.py index 25caa9e03fb..1e24d96115d 100644 --- a/erpnext/accounts/report/purchase_register/purchase_register.py +++ b/erpnext/accounts/report/purchase_register/purchase_register.py @@ -377,7 +377,7 @@ def get_account_columns(invoice_list, include_payments): def get_invoices(filters, additional_query_columns): pi = frappe.qb.DocType("Purchase Invoice") query = ( - frappe.qb.from_(pi) + frappe.qb.from_(pi) # qb .select( ConstantColumn("Purchase Invoice").as_("doctype"), pi.name, @@ -386,7 +386,7 @@ def get_invoices(filters, additional_query_columns): pi.supplier, pi.supplier_name, pi.tax_id, - pi.bill_no, + pi.bill_no, pi.bill_date, pi.remarks, pi.base_net_total, @@ -395,7 +395,7 @@ def get_invoices(filters, additional_query_columns): pi.outstanding_amount, pi.mode_of_payment, ) - .where((pi.docstatus == 1)) + .where(pi.docstatus == 1) .orderby(pi.posting_date, pi.name, order=Order.desc) ) diff --git a/erpnext/accounts/report/sales_register/sales_register.py b/erpnext/accounts/report/sales_register/sales_register.py index dc557433f7d..f8b94d353f9 100644 --- a/erpnext/accounts/report/sales_register/sales_register.py +++ b/erpnext/accounts/report/sales_register/sales_register.py @@ -415,7 +415,7 @@ def get_account_columns(invoice_list, include_payments): def get_invoices(filters, additional_query_columns): si = frappe.qb.DocType("Sales Invoice") query = ( - frappe.qb.from_(si) + frappe.qb.from_(si) # qb .select( ConstantColumn("Sales Invoice").as_("doctype"), si.name, @@ -437,7 +437,7 @@ def get_invoices(filters, additional_query_columns): si.represents_company, si.company, ) - .where((si.docstatus == 1)) + .where(si.docstatus == 1) .orderby(si.posting_date, si.name, order=Order.desc) ) From 209af58f8318c58c3e8bad840aedd21ba8fa0c3c Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 2 Jun 2024 20:17:24 +0530 Subject: [PATCH 13/25] chore: update condition queries in qb (cherry picked from commit d2af36e1eb2ba395b3bf35f933f40b6906852fa0) # Conflicts: # erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py # erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py --- .../item_wise_purchase_register.py | 42 +++--- .../item_wise_sales_register.js | 6 + .../item_wise_sales_register.py | 126 ++++++++++-------- .../purchase_register/purchase_register.py | 2 +- .../report/sales_register/sales_register.py | 2 +- 5 files changed, 105 insertions(+), 73 deletions(-) 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 086026efb53..dc4999a3b4e 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 @@ -5,14 +5,15 @@ import frappe from frappe import _ from frappe.utils import flt +from pypika import Order import erpnext from erpnext.accounts.report.item_wise_sales_register.item_wise_sales_register import ( add_sub_total_row, add_total_row, + apply_group_by_conditions, get_grand_total, get_group_by_and_display_fields, - get_group_by_conditions, get_tax_accounts, ) from erpnext.accounts.report.utils import get_query_columns, get_values_for_columns @@ -287,29 +288,27 @@ def get_columns(additional_table_columns, filters): return columns -def get_conditions(filters): - conditions = "" +def apply_conditions(query, pi, pii, filters): + for opts in ("company", "supplier", "item_code", "mode_of_payment"): + if filters.get(opts): + query = query.where(pi[opts] == filters[opts]) - for opts in ( - ("company", " and `tabPurchase Invoice`.company=%(company)s"), - ("supplier", " and `tabPurchase Invoice`.supplier = %(supplier)s"), - ("item_code", " and `tabPurchase Invoice Item`.item_code = %(item_code)s"), - ("from_date", " and `tabPurchase Invoice`.posting_date>=%(from_date)s"), - ("to_date", " and `tabPurchase Invoice`.posting_date<=%(to_date)s"), - ("mode_of_payment", " and ifnull(mode_of_payment, '') = %(mode_of_payment)s"), - ("item_group", " and ifnull(`tabPurchase Invoice Item`.item_group, '') = %(item_group)s"), - ): - if filters.get(opts[0]): - conditions += opts[1] + if filters.get("from_date"): + query = query.where(pi.posting_date >= filters.get("from_date")) + + if filters.get("to_date"): + query = query.where(pi.posting_date <= filters.get("to_date")) + + if filters.get("item_group"): + query = query.where(pii.item_group == filters.get("item_group")) if not filters.get("group_by"): - conditions += ( - "ORDER BY `tabPurchase Invoice`.posting_date desc, `tabPurchase Invoice Item`.item_code desc" - ) + query = query.orderby(pi.posting_date, order=Order.desc) + query = query.orderby(pii.item_group, order=Order.desc) else: - conditions += get_group_by_conditions(filters, "Purchase Invoice") + query = apply_group_by_conditions(filters, "Purchase Invoice") - return conditions + return query def get_items(filters, additional_query_columns): @@ -413,9 +412,14 @@ def get_items(filters, additional_query_columns): if filters.get("company"): query = query.where(pi.company == filters["company"]) +<<<<<<< HEAD <<<<<<< HEAD >>>>>>> 1b45ecfcae (fix: Item-wise Sales and Purchase register with no item codes #41373) ======= +======= + query = apply_conditions(query, pi, pii, filters) + +>>>>>>> d2af36e1eb (chore: update condition queries in qb) return query.run(as_dict=True) >>>>>>> 76073ae228 (fix: fixing Item-wise sales register and purchase register #41373) 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 7ed2ebd89ab..1f155de63a0 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 @@ -41,6 +41,12 @@ frappe.query_reports["Item-wise Sales Register"] = { label: __("Warehouse"), fieldtype: "Link", options: "Warehouse", + get_query: function () { + const company = frappe.query_report.get_filter_value("company"); + return { + filters: { company: company }, + }; + }, }, { fieldname: "brand", 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 102d9d6a0af..192cbc0e0cc 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 @@ -7,6 +7,7 @@ from frappe import _ from frappe.model.meta import get_field_precision from frappe.utils import cstr, flt from frappe.utils.xlsxutils import handle_html +from pypika import Order from erpnext.accounts.report.sales_register.sales_register import get_mode_of_payments from erpnext.accounts.report.utils import get_query_columns, get_values_for_columns @@ -333,59 +334,69 @@ def get_columns(additional_table_columns, filters): return columns -def get_conditions(filters, additional_conditions=None): - conditions = "" +def apply_conditions(query, si, sii, filters, additional_conditions=None): + for opts in ("company", "customer", "item_code"): + if filters.get(opts): + query = query.where(si[opts] == filters[opts]) - for opts in ( - ("company", " and `tabSales Invoice`.company=%(company)s"), - ("customer", " and `tabSales Invoice`.customer = %(customer)s"), - ("item_code", " and `tabSales Invoice Item`.item_code = %(item_code)s"), - ("from_date", " and `tabSales Invoice`.posting_date>=%(from_date)s"), - ("to_date", " and `tabSales Invoice`.posting_date<=%(to_date)s"), - ): - if filters.get(opts[0]): - conditions += opts[1] + if filters.get("from_date"): + query = query.where(si.posting_date >= filters.get("from_date")) - if additional_conditions: - conditions += additional_conditions + if filters.get("to_date"): + query = query.where(si.posting_date <= filters.get("to_date")) if filters.get("mode_of_payment"): - conditions += """ and exists(select name from `tabSales Invoice Payment` - where parent=`tabSales Invoice`.name - and ifnull(`tabSales Invoice Payment`.mode_of_payment, '') = %(mode_of_payment)s)""" + sales_invoice = frappe.db.get_all( + "Sales Invoice Payment", {"mode_of_payment": filters.get("mode_of_payment")}, pluck="parent" + ) + query = query.where(si.name.isin(sales_invoice)) if filters.get("warehouse"): if frappe.db.get_value("Warehouse", filters.get("warehouse"), "is_group"): lft, rgt = frappe.db.get_all( "Warehouse", filters={"name": filters.get("warehouse")}, fields=["lft", "rgt"], as_list=True )[0] - conditions += f"and ifnull(`tabSales Invoice Item`.warehouse, '') in (select name from `tabWarehouse` where lft > {lft} and rgt < {rgt}) " + warehouses = frappe.db.get_all("Warehouse", {"lft": (">", lft), "rgt": ("<", rgt)}, pluck="name") + query = query.where(sii.warehouse.isin(warehouses)) else: - conditions += """and ifnull(`tabSales Invoice Item`.warehouse, '') = %(warehouse)s""" + query = query.where(sii.warehouse == filters.get("warehouse")) if filters.get("brand"): - conditions += """and ifnull(`tabSales Invoice Item`.brand, '') = %(brand)s""" + query = query.where(sii.brand == filters.get("brand")) if filters.get("item_group"): - conditions += """and ifnull(`tabSales Invoice Item`.item_group, '') = %(item_group)s""" + query = query.where(sii.item_group == filters.get("item_group")) +<<<<<<< HEAD +======= + if filters.get("income_account"): + query = query.where( + (sii.income_account == filters.get("income_account")) + | (sii.deferred_revenue_account == filters.get("income_account")) + | (si.unrealized_profit_loss_account == filters.get("income_account")) + ) + +>>>>>>> d2af36e1eb (chore: update condition queries in qb) if not filters.get("group_by"): - conditions += "ORDER BY `tabSales Invoice`.posting_date desc, `tabSales Invoice Item`.item_group desc" + query = query.orderby(si.posting_date, order=Order.desc) + query = query.orderby(sii.item_group, order=Order.desc) else: - conditions += get_group_by_conditions(filters, "Sales Invoice") + query = apply_group_by_conditions(query, si, sii, filters) - return conditions + return query -def get_group_by_conditions(filters, doctype): +def apply_group_by_conditions(query, si, ii, filters): if filters.get("group_by") == "Invoice": - return f"ORDER BY `tab{doctype} Item`.parent desc" + query = query.orderby(ii.parent, order=Order.desc) elif filters.get("group_by") == "Item": - return f"ORDER BY `tab{doctype} Item`.`item_code`" + query = query.orderby(ii.item_code) elif filters.get("group_by") == "Item Group": - return "ORDER BY `tab{} Item`.{}".format(doctype, frappe.scrub(filters.get("group_by"))) + query = query.orderby(ii.item_group) elif filters.get("group_by") in ("Customer", "Customer Group", "Territory", "Supplier"): - return "ORDER BY `tab{}`.{}".format(doctype, frappe.scrub(filters.get("group_by"))) + query = query.orderby(si[frappe.scrub(filters.get("group_by"))]) + + return query <<<<<<< HEAD @@ -460,16 +471,19 @@ def get_items(filters, additional_query_columns,additional_conditions=None): def get_items(filters, additional_query_columns, additional_conditions=None): si = frappe.qb.DocType("Sales Invoice") sii = frappe.qb.DocType("Sales Invoice Item") +<<<<<<< HEAD Item = frappe.qb.DocType("Item") >>>>>>> eafa88b8e9 (fix: fixing Item-wise sales register #41373) +======= + item = frappe.qb.DocType("Item") +>>>>>>> d2af36e1eb (chore: update condition queries in qb) query = ( frappe.qb.from_(si) .join(sii) .on(si.name == sii.parent) - # added left join - .left_join(Item) - .on(sii.item_code == Item.name) + .left_join(item) + .on(sii.item_code == item.name) .select( sii.name, sii.parent, @@ -489,8 +503,8 @@ def get_items(filters, additional_query_columns, additional_conditions=None): sii.item_group, sii.item_name.as_("si_item_name"), sii.item_group.as_("si_item_group"), - Item.item_name.as_("i_item_name"), - Item.item_group.as_("i_item_group"), + item.item_name.as_("i_item_name"), + item.item_group.as_("i_item_group"), sii.sales_order, sii.delivery_note, sii.income_account, @@ -510,15 +524,24 @@ def get_items(filters, additional_query_columns, additional_conditions=None): ) .where(si.docstatus == 1) ) + + if additional_query_columns: + query = query.select(*additional_query_columns) + if filters.get("customer"): query = query.where(si.customer == filters["customer"]) if filters.get("customer_group"): query = query.where(si.customer_group == filters["customer_group"]) +<<<<<<< HEAD <<<<<<< HEAD >>>>>>> 1b45ecfcae (fix: Item-wise Sales and Purchase register with no item codes #41373) ======= +======= + query = apply_conditions(query, si, sii, filters, additional_conditions) + +>>>>>>> d2af36e1eb (chore: update condition queries in qb) return query.run(as_dict=True) >>>>>>> eafa88b8e9 (fix: fixing Item-wise sales register #41373) @@ -528,16 +551,14 @@ def get_delivery_notes_against_sales_order(item_list): so_item_rows = list(set([d.so_detail for d in item_list])) if so_item_rows: - delivery_notes = frappe.db.sql( - """ - select parent, so_detail - from `tabDelivery Note Item` - where docstatus=1 and so_detail in (%s) - group by so_detail, parent - """ - % (", ".join(["%s"] * len(so_item_rows))), - tuple(so_item_rows), - as_dict=1, + dn_item = frappe.qb.DocType("Delivery Note Item") + delivery_notes = ( + frappe.qb.from_(dn_item) + .select(dn_item.parent, dn_item.so_detail) + .where(dn_item.docstatus == 1) + .where(dn_item.so_detail.isin(so_item_rows)) + .groupby(dn_item.so_detail, dn_item.parent) + .run(as_dict=True) ) for dn in delivery_notes: @@ -547,15 +568,16 @@ def get_delivery_notes_against_sales_order(item_list): def get_grand_total(filters, doctype): - return frappe.db.sql( - f""" SELECT - SUM(`tab{doctype}`.base_grand_total) - FROM `tab{doctype}` - WHERE `tab{doctype}`.docstatus = 1 - and posting_date between %s and %s - """, - (filters.get("from_date"), filters.get("to_date")), - )[0][0] # nosec + return flt( + frappe.db.get_value( + doctype, + { + "docstatus": 1, + "posting_date": ("between", [filters.get("from_date"), filters.get("to_date")]), + }, + "sum(base_grand_total)", + ) + ) def get_tax_accounts( diff --git a/erpnext/accounts/report/purchase_register/purchase_register.py b/erpnext/accounts/report/purchase_register/purchase_register.py index 1e24d96115d..504c74babcb 100644 --- a/erpnext/accounts/report/purchase_register/purchase_register.py +++ b/erpnext/accounts/report/purchase_register/purchase_register.py @@ -377,7 +377,7 @@ def get_account_columns(invoice_list, include_payments): def get_invoices(filters, additional_query_columns): pi = frappe.qb.DocType("Purchase Invoice") query = ( - frappe.qb.from_(pi) # qb + frappe.qb.from_(pi) .select( ConstantColumn("Purchase Invoice").as_("doctype"), pi.name, diff --git a/erpnext/accounts/report/sales_register/sales_register.py b/erpnext/accounts/report/sales_register/sales_register.py index f8b94d353f9..f27569531b1 100644 --- a/erpnext/accounts/report/sales_register/sales_register.py +++ b/erpnext/accounts/report/sales_register/sales_register.py @@ -415,7 +415,7 @@ def get_account_columns(invoice_list, include_payments): def get_invoices(filters, additional_query_columns): si = frappe.qb.DocType("Sales Invoice") query = ( - frappe.qb.from_(si) # qb + frappe.qb.from_(si) .select( ConstantColumn("Sales Invoice").as_("doctype"), si.name, From 1fca6ea5e70da34cdce637bf2a4b1beb94bc3507 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 7 Jun 2024 18:48:16 +0530 Subject: [PATCH 14/25] fix: Add additional condition application (cherry picked from commit 8ec364df6fe07817fd6c69c6e9db31af08d1a455) # Conflicts: # erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py --- .../item_wise_purchase_register.py | 20 +++++++++++++++---- .../item_wise_sales_register.py | 12 +++++++++-- 2 files changed, 26 insertions(+), 6 deletions(-) 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 dc4999a3b4e..bafd83db37a 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 @@ -30,7 +30,7 @@ def _execute(filters=None, additional_table_columns=None): company_currency = erpnext.get_company_currency(filters.company) - item_list = get_items(filters, get_query_columns(additional_table_columns)) + item_list = get_items(filters, additional_table_columns) aii_account_map = get_aii_accounts() if item_list: itemised_tax, tax_columns = get_tax_accounts( @@ -311,6 +311,7 @@ def apply_conditions(query, pi, pii, filters): return query +<<<<<<< HEAD def get_items(filters, additional_query_columns): <<<<<<< HEAD <<<<<<< HEAD @@ -363,6 +364,9 @@ def get_items(filters, additional_query_columns): ) .where(pi.docstatus == 1)) ======= +======= +def get_items(filters, additional_table_columns): +>>>>>>> 8ec364df6f (fix: Add additional condition application) pi = frappe.qb.DocType("Purchase Invoice") pii = frappe.qb.DocType("Purchase Invoice Item") Item = frappe.qb.DocType("Item") @@ -404,19 +408,27 @@ def get_items(filters, additional_query_columns): ) >>>>>>> 76073ae228 (fix: fixing Item-wise sales register and purchase register #41373) - if additional_query_columns: - query = query.select(*additional_query_columns) - if filters.get("supplier"): query = query.where(pi.supplier == filters["supplier"]) if filters.get("company"): query = query.where(pi.company == filters["company"]) +<<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD >>>>>>> 1b45ecfcae (fix: Item-wise Sales and Purchase register with no item codes #41373) ======= ======= +======= + if additional_table_columns: + for column in additional_table_columns: + if column.get("_doctype"): + table = frappe.qb.DocType(column.get("_doctype")) + query = query.select(table[column.get("fieldname")]) + else: + query = query.select(pi[column.get("fieldname")]) + +>>>>>>> 8ec364df6f (fix: Add additional condition application) query = apply_conditions(query, pi, pii, filters) >>>>>>> d2af36e1eb (chore: update condition queries in qb) 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 192cbc0e0cc..5a989a929ab 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 @@ -27,7 +27,7 @@ def _execute(filters=None, additional_table_columns=None, additional_conditions= company_currency = frappe.get_cached_value("Company", filters.get("company"), "default_currency") - item_list = get_items(filters, get_query_columns(additional_table_columns), additional_conditions) + item_list = get_items(filters, additional_table_columns, additional_conditions) if item_list: itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency) @@ -383,6 +383,9 @@ def apply_conditions(query, si, sii, filters, additional_conditions=None): else: query = apply_group_by_conditions(query, si, sii, filters) + for key, value in (additional_conditions or {}).items(): + query = query.where(si[key] == value) + return query @@ -526,7 +529,12 @@ def get_items(filters, additional_query_columns, additional_conditions=None): ) if additional_query_columns: - query = query.select(*additional_query_columns) + for column in additional_query_columns: + if column.get("_doctype"): + table = frappe.qb.DocType(column.get("_doctype")) + query = query.select(table[column.get("fieldname")]) + else: + query = query.select(si[column.get("fieldname")]) if filters.get("customer"): query = query.where(si.customer == filters["customer"]) From c29db5afcda6d283c32676c437f525e7aaba5ef6 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 9 Jun 2024 20:02:39 +0530 Subject: [PATCH 15/25] chore: resolve conflicts --- .../item_wise_purchase_register.py | 66 ------------- .../item_wise_sales_register.py | 97 ++----------------- 2 files changed, 10 insertions(+), 153 deletions(-) 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 bafd83db37a..80c246cad55 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 @@ -311,62 +311,7 @@ def apply_conditions(query, pi, pii, filters): return query -<<<<<<< HEAD -def get_items(filters, additional_query_columns): -<<<<<<< HEAD -<<<<<<< HEAD - conditions = get_conditions(filters) - if additional_query_columns: - additional_query_columns = "," + ",".join(additional_query_columns) - return frappe.db.sql( - f""" - select - `tabPurchase Invoice Item`.`name`, `tabPurchase Invoice Item`.`parent`, - `tabPurchase Invoice`.posting_date, `tabPurchase Invoice`.credit_to, `tabPurchase Invoice`.company, - `tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total, - `tabPurchase Invoice`.unrealized_profit_loss_account, - `tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description, - `tabPurchase Invoice Item`.`item_name` as pi_item_name, `tabPurchase Invoice Item`.`item_group` as pi_item_group, - `tabItem`.`item_name` as i_item_name, `tabItem`.`item_group` as i_item_group, - `tabPurchase Invoice Item`.`project`, `tabPurchase Invoice Item`.`purchase_order`, - `tabPurchase Invoice Item`.`purchase_receipt`, `tabPurchase Invoice Item`.`po_detail`, - `tabPurchase Invoice Item`.`expense_account`, `tabPurchase Invoice Item`.`stock_qty`, - `tabPurchase Invoice Item`.`stock_uom`, `tabPurchase Invoice Item`.`base_net_amount`, - `tabPurchase Invoice`.`supplier_name`, `tabPurchase Invoice`.`mode_of_payment` {additional_query_columns} - from `tabPurchase Invoice`, `tabPurchase Invoice Item`, `tabItem` - where `tabPurchase Invoice`.name = `tabPurchase Invoice Item`.`parent` and - `tabItem`.name = `tabPurchase Invoice Item`.`item_code` and - `tabPurchase Invoice`.docstatus = 1 {conditions} - """, - filters, - as_dict=1, - ) -======= - pi = frappe.qb.DocType('Purchase Invoice') - pii = frappe.qb.DocType('Purchase Invoice Item') - Item = frappe.qb.DocType('Item') - query = (frappe.qb.from_(pi) - .join(pii).on(pi.name == pii.parent) - .left_join(Item).on(pii.item_code == Item.name) - .select( - pii.name.as_('pii_name'), pii.parent, - pi.posting_date, pi.credit_to, pi.company, - pi.supplier, pi.remarks, pi.base_net_total, - pi.unrealized_profit_loss_account, - pii.item_code, pii.description, pii.item_group, - pii.item_name.as_('pi_item_name'), pii.item_group.as_('pi_item_group'), - Item.item_name.as_('i_item_name'), Item.item_group.as_('i_item_group'), - pii.project, pii.purchase_order, - pii.purchase_receipt, pii.po_detail, - pii.expense_account, pii.stock_qty, - pii.stock_uom, pii.base_net_amount, - pi.supplier_name, pi.mode_of_payment - ) - .where(pi.docstatus == 1)) -======= -======= def get_items(filters, additional_table_columns): ->>>>>>> 8ec364df6f (fix: Add additional condition application) pi = frappe.qb.DocType("Purchase Invoice") pii = frappe.qb.DocType("Purchase Invoice Item") Item = frappe.qb.DocType("Item") @@ -406,20 +351,12 @@ def get_items(filters, additional_table_columns): ) .where(pi.docstatus == 1) ) ->>>>>>> 76073ae228 (fix: fixing Item-wise sales register and purchase register #41373) if filters.get("supplier"): query = query.where(pi.supplier == filters["supplier"]) if filters.get("company"): query = query.where(pi.company == filters["company"]) -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD ->>>>>>> 1b45ecfcae (fix: Item-wise Sales and Purchase register with no item codes #41373) -======= -======= -======= if additional_table_columns: for column in additional_table_columns: if column.get("_doctype"): @@ -428,12 +365,9 @@ def get_items(filters, additional_table_columns): else: query = query.select(pi[column.get("fieldname")]) ->>>>>>> 8ec364df6f (fix: Add additional condition application) query = apply_conditions(query, pi, pii, filters) ->>>>>>> d2af36e1eb (chore: update condition queries in qb) return query.run(as_dict=True) ->>>>>>> 76073ae228 (fix: fixing Item-wise sales register and purchase register #41373) def get_aii_accounts(): 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 5a989a929ab..cd50b118715 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 @@ -84,9 +84,7 @@ def _execute(filters=None, additional_table_columns=None, additional_conditions= "company": d.company, "sales_order": d.sales_order, "delivery_note": d.delivery_note, - "income_account": d.unrealized_profit_loss_account - if d.is_internal_customer == 1 - else d.income_account, + "income_account": get_income_account(d), "cost_center": d.cost_center, "stock_qty": d.stock_qty, "stock_uom": d.stock_uom, @@ -151,6 +149,15 @@ def _execute(filters=None, additional_table_columns=None, additional_conditions= return columns, data, None, None, None, skip_total_row +def get_income_account(row): + if row.enable_deferred_revenue: + return row.deferred_revenue_account + elif row.is_internal_customer == 1: + return row.unrealized_profit_loss_account + else: + return row.income_account + + def get_columns(additional_table_columns, filters): columns = [] @@ -367,8 +374,6 @@ def apply_conditions(query, si, sii, filters, additional_conditions=None): if filters.get("item_group"): query = query.where(sii.item_group == filters.get("item_group")) -<<<<<<< HEAD -======= if filters.get("income_account"): query = query.where( (sii.income_account == filters.get("income_account")) @@ -376,7 +381,6 @@ def apply_conditions(query, si, sii, filters, additional_conditions=None): | (si.unrealized_profit_loss_account == filters.get("income_account")) ) ->>>>>>> d2af36e1eb (chore: update condition queries in qb) if not filters.get("group_by"): query = query.orderby(si.posting_date, order=Order.desc) query = query.orderby(sii.item_group, order=Order.desc) @@ -402,84 +406,10 @@ def apply_group_by_conditions(query, si, ii, filters): return query -<<<<<<< HEAD -<<<<<<< HEAD -def get_items(filters, additional_query_columns, additional_conditions=None): - conditions = get_conditions(filters, additional_conditions) - if additional_query_columns: - additional_query_columns = "," + ",".join(additional_query_columns) - return frappe.db.sql( - """ - select - `tabSales Invoice Item`.name, `tabSales Invoice Item`.parent, - `tabSales Invoice`.posting_date, `tabSales Invoice`.debit_to, - `tabSales Invoice`.unrealized_profit_loss_account, - `tabSales Invoice`.is_internal_customer, - `tabSales Invoice`.customer, `tabSales Invoice`.remarks, - `tabSales Invoice`.territory, `tabSales Invoice`.company, `tabSales Invoice`.base_net_total, - `tabSales Invoice Item`.project, - `tabSales Invoice Item`.item_code, `tabSales Invoice Item`.description, - `tabSales Invoice Item`.`item_name`, `tabSales Invoice Item`.`item_group`, - `tabSales Invoice Item`.`item_name` as si_item_name, `tabSales Invoice Item`.`item_group` as si_item_group, - `tabItem`.`item_name` as i_item_name, `tabItem`.`item_group` as i_item_group, - `tabSales Invoice Item`.sales_order, `tabSales Invoice Item`.delivery_note, - `tabSales Invoice Item`.income_account, `tabSales Invoice Item`.cost_center, - `tabSales Invoice Item`.stock_qty, `tabSales Invoice Item`.stock_uom, - `tabSales Invoice Item`.base_net_rate, `tabSales Invoice Item`.base_net_amount, - `tabSales Invoice`.customer_name, `tabSales Invoice`.customer_group, `tabSales Invoice Item`.so_detail, - `tabSales Invoice`.update_stock, `tabSales Invoice Item`.uom, `tabSales Invoice Item`.qty {} - from `tabSales Invoice`, `tabSales Invoice Item`, `tabItem` - where `tabSales Invoice`.name = `tabSales Invoice Item`.parent and - `tabItem`.name = `tabSales Invoice Item`.`item_code` and - `tabSales Invoice`.docstatus = 1 {} - """.format(additional_query_columns, conditions), - filters, - as_dict=1, - ) # nosec -======= -def get_items(filters, additional_query_columns,additional_conditions=None): - si = frappe.qb.DocType('Sales Invoice') - sii = frappe.qb.DocType('Sales Invoice Item') - Item = frappe.qb.DocType('Item') - - query = ( - frappe.qb.from_(si) - .join(sii).on(si.name == sii.parent) - .left_join(Item).on(sii.item_code == Item.name) - .select( - sii.name, sii.parent, - si.posting_date, si.debit_to, - si.unrealized_profit_loss_account, - si.is_internal_customer, - si.customer, si.remarks, - si.territory, si.company, si.base_net_total, - sii.project, - sii.item_code, sii.description, - sii.item_name, sii.item_group, - sii.item_name.as_('si_item_name'), sii.item_group.as_('si_item_group'), - Item.item_name.as_('i_item_name'), Item.item_group.as_('i_item_group'), - sii.sales_order, sii.delivery_note, - sii.income_account, sii.cost_center, - sii.enable_deferred_revenue, sii.deferred_revenue_account, - sii.stock_qty, sii.stock_uom, - sii.base_net_rate, sii.base_net_amount, - si.customer_name, si.customer_group, sii.so_detail, - si.update_stock, sii.uom, sii.qty - ) - .where(si.docstatus == 1) - ) - if filters.get("customer"): - query = query.where(si.customer == filters['customer']) -======= def get_items(filters, additional_query_columns, additional_conditions=None): si = frappe.qb.DocType("Sales Invoice") sii = frappe.qb.DocType("Sales Invoice Item") -<<<<<<< HEAD - Item = frappe.qb.DocType("Item") ->>>>>>> eafa88b8e9 (fix: fixing Item-wise sales register #41373) -======= item = frappe.qb.DocType("Item") ->>>>>>> d2af36e1eb (chore: update condition queries in qb) query = ( frappe.qb.from_(si) @@ -542,16 +472,9 @@ def get_items(filters, additional_query_columns, additional_conditions=None): if filters.get("customer_group"): query = query.where(si.customer_group == filters["customer_group"]) -<<<<<<< HEAD -<<<<<<< HEAD ->>>>>>> 1b45ecfcae (fix: Item-wise Sales and Purchase register with no item codes #41373) -======= -======= query = apply_conditions(query, si, sii, filters, additional_conditions) ->>>>>>> d2af36e1eb (chore: update condition queries in qb) return query.run(as_dict=True) ->>>>>>> eafa88b8e9 (fix: fixing Item-wise sales register #41373) def get_delivery_notes_against_sales_order(item_list): From 30c0b2bb546c1c78bd5db29311a78d71a02efdf1 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 10 Jun 2024 15:16:57 +0530 Subject: [PATCH 16/25] fix: do not fetch items with no active BOM in PP (backport #41833) (#41835) fix: do not fetch items with no active BOM in PP (#41833) (cherry picked from commit 36413d14d852f2e126f000fbfeda6b1a8ef51d55) Co-authored-by: rohitwaghchaure --- .../production_plan/production_plan.py | 6 ++++- .../production_plan/test_production_plan.py | 22 +++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index a378d8ae606..7d3aa000c87 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -452,6 +452,10 @@ class ProductionPlan(Document): {"sales_order": data.parent, "sales_order_item": data.name, "qty": data.pending_qty} ) + bom_no = data.bom_no or item_details and item_details.bom_no or "" + if not bom_no: + continue + pi = self.append( "po_items", { @@ -459,7 +463,7 @@ class ProductionPlan(Document): "item_code": data.item_code, "description": data.description or item_details.description, "stock_uom": item_details and item_details.stock_uom or "", - "bom_no": data.bom_no or item_details and item_details.bom_no or "", + "bom_no": bom_no, "planned_qty": data.pending_qty, "pending_qty": data.pending_qty, "planned_start_date": now_datetime(), diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index 8957c11d3f0..71d9b59c677 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -328,6 +328,28 @@ class TestProductionPlan(FrappeTestCase): self.assertEqual(pln2.po_items[0].bom_no, bom2.name) + def test_production_plan_with_non_active_bom_item(self): + item = make_item("Test Production Item 1 for Non Active BOM", {"is_stock_item": 1}).name + + so1 = make_sales_order(item_code=item, qty=1) + + pln = frappe.new_doc("Production Plan") + pln.company = so1.company + pln.get_items_from = "Sales Order" + pln.append( + "sales_orders", + { + "sales_order": so1.name, + "sales_order_date": so1.transaction_date, + "customer": so1.customer, + "grand_total": so1.grand_total, + }, + ) + + pln.get_items() + + self.assertFalse(pln.po_items) + def test_production_plan_combine_items(self): "Test combining FG items in Production Plan." item = "Test Production Item 1" From 1ead66721d92a25e8037da2198a80031c69049d4 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 10 Jun 2024 15:17:19 +0530 Subject: [PATCH 17/25] fix: terms and conditions for material request (backport #41834) (#41837) fix: terms and conditions for material request (#41834) (cherry picked from commit 4b026d66dc32de2a8447a8007e56719cf31872dc) Co-authored-by: rohitwaghchaure --- erpnext/selling/doctype/sales_order/sales_order.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index d6c08d957fe..e5e67ed38b7 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -794,6 +794,11 @@ def get_requested_item_qty(sales_order): def make_material_request(source_name, target_doc=None): requested_item_qty = get_requested_item_qty(source_name) + def postprocess(source, target): + if source.tc_name and frappe.db.get_value("Terms and Conditions", source.tc_name, "buying") != 1: + target.tc_name = None + target.terms = None + def get_remaining_qty(so_item): return flt( flt(so_item.qty) @@ -849,6 +854,7 @@ def make_material_request(source_name, target_doc=None): }, }, target_doc, + postprocess, ) return doc From 5544309048e905592355dc1f206c31db40126e41 Mon Sep 17 00:00:00 2001 From: "Nihantra C. Patel" <141945075+Nihantra-Patel@users.noreply.github.com> Date: Wed, 5 Jun 2024 15:49:44 +0530 Subject: [PATCH 18/25] fix: calculate totals and taxes (cherry picked from commit a7ec0c1cec3aa225f0ebc9148a71f86a28c933d5) --- erpnext/public/js/controllers/taxes_and_totals.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index cf6b34c9604..adf05ffb154 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -83,7 +83,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { this.frm.doc.paid_amount = flt(this.frm.doc.grand_total, precision("grand_total")); } - this.frm.refresh_field("taxes"); + this.frm.refresh_fields(); } calculate_discount_amount() { @@ -841,7 +841,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { }); } - this.frm.refresh_field("taxes"); + this.frm.refresh_fields(); } set_default_payment(total_amount_to_pay, update_paid_amount) { From c0668373c7fa19e30f0986e168a8e02123fdf7b2 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 30 May 2024 11:47:44 +0530 Subject: [PATCH 19/25] refactor: enabling partial TDS application on partial invoice (cherry picked from commit 091c5496b20864577d133b0804e957ff8995606f) --- .../purchase_invoice/purchase_invoice.js | 2 +- .../purchase_invoice/purchase_invoice.py | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index c56a083808d..c0a43395216 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -677,7 +677,7 @@ frappe.ui.form.on("Purchase Invoice", { if (frm.doc.supplier) { frm.doc.apply_tds = frm.doc.__onload.supplier_tds ? 1 : 0; } - if (!frm.doc.__onload.supplier_tds) { + if (!frm.doc.__onload.enable_apply_tds) { frm.set_df_property("apply_tds", "read_only", 1); } } diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index afccc21c313..df54a63b661 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -3,7 +3,7 @@ import frappe -from frappe import _, throw +from frappe import _, qb, throw from frappe.model.mapper import get_mapped_doc from frappe.query_builder.functions import Sum from frappe.utils import cint, cstr, flt, formatdate, get_link_to_form, getdate, nowdate @@ -347,6 +347,22 @@ class PurchaseInvoice(BuyingController): self.tax_withholding_category = tds_category self.set_onload("supplier_tds", tds_category) + # If Linked Purchase Order has TDS applied, enable 'apply_tds' checkbox + if purchase_orders := [x.purchase_order for x in self.items if x.purchase_order]: + po = qb.DocType("Purchase Order") + po_with_tds = ( + qb.from_(po) + .select(po.name) + .where( + po.docstatus.eq(1) + & (po.name.isin(purchase_orders)) + & (po.apply_tds.eq(1)) + & (po.tax_withholding_category.notnull()) + ) + .run() + ) + self.set_onload("enable_apply_tds", True if po_with_tds else False) + super().set_missing_values(for_validate) def validate_credit_to_acc(self): From c664a27b1e3b94339c23091fec72ae5e258f9cf8 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 31 May 2024 16:53:48 +0530 Subject: [PATCH 20/25] refactor: is_opening in payment entry (cherry picked from commit 05d17d0d7347fcb28e616ed297dfef41206d0960) --- .../doctype/payment_entry/payment_entry.json | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index 6b0115d3f56..c1e58b28e7d 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -89,6 +89,7 @@ "custom_remarks", "remarks", "base_in_words", + "is_opening", "column_break_16", "letter_head", "print_heading", @@ -777,6 +778,15 @@ "label": "Reconcile on Advance Payment Date", "no_copy": 1, "read_only": 1 + }, + { + "default": "No", + "fieldname": "is_opening", + "fieldtype": "Select", + "label": "Is Opening", + "options": "No\nYes", + "print_hide": 1, + "search_index": 1 } ], "index_web_pages_for_search": 1, @@ -790,7 +800,7 @@ "table_fieldname": "payment_entries" } ], - "modified": "2024-05-17 10:21:11.199445", + "modified": "2024-05-31 16:54:59.618516", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry", From 1fc802da6f4f213c22e3a3a61bb9714f6c5364ab Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 31 May 2024 17:11:40 +0530 Subject: [PATCH 21/25] refactor: restrict to 'Advance in Separate Party Account' type (cherry picked from commit c36f0e4a3376f28a82ba94d1fb46337b3fd62232) --- erpnext/accounts/doctype/payment_entry/payment_entry.json | 3 ++- erpnext/accounts/doctype/payment_entry/payment_entry.py | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index c1e58b28e7d..d420bcca342 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -781,6 +781,7 @@ }, { "default": "No", + "depends_on": "eval: doc.book_advance_payments_in_separate_party_account == 1", "fieldname": "is_opening", "fieldtype": "Select", "label": "Is Opening", @@ -800,7 +801,7 @@ "table_fieldname": "payment_entries" } ], - "modified": "2024-05-31 16:54:59.618516", + "modified": "2024-05-31 17:07:06.197249", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry", diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index e542ff68176..69075e5cf6c 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -116,11 +116,13 @@ class PaymentEntry(AccountsController): self.book_advance_payments_in_separate_party_account = False if self.party_type not in ("Customer", "Supplier"): + self.is_opening = "No" return if not frappe.db.get_value( "Company", self.company, "book_advance_payments_in_separate_party_account" ): + self.is_opening = "No" return # Important to set this flag for the gl building logic to work properly @@ -132,6 +134,7 @@ class PaymentEntry(AccountsController): if (account_type == "Payable" and self.party_type == "Customer") or ( account_type == "Receivable" and self.party_type == "Supplier" ): + self.is_opening = "No" return if self.references: @@ -141,6 +144,7 @@ class PaymentEntry(AccountsController): # If there are referencers other than `allowed_types`, treat this as a normal payment entry if reference_types - allowed_types: self.book_advance_payments_in_separate_party_account = False + self.is_opening = "No" return liability_account = get_party_account( From 818a5536689ac936bc0bee42197b45a3220c5387 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 11 Jun 2024 10:08:37 +0530 Subject: [PATCH 22/25] test: is_opening flag for advance in separate party account (cherry picked from commit 17f968e1e18a071e1708534bd28e3e6a037dde79) --- .../payment_entry/test_payment_entry.py | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index d22350d20fc..4631f8905e6 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -1729,6 +1729,68 @@ class TestPaymentEntry(FrappeTestCase): self.check_gl_entries() self.check_pl_entries() + def test_opening_flag_for_advance_as_liability(self): + company = "_Test Company" + + advance_account = create_account( + parent_account="Current Assets - _TC", + account_name="Advances Received", + company=company, + account_type="Receivable", + ) + + # Enable Advance in separate party account + frappe.db.set_value( + "Company", + company, + { + "book_advance_payments_in_separate_party_account": 1, + "default_advance_received_account": advance_account, + }, + ) + # Advance Payment + adv = create_payment_entry( + party_type="Customer", + party="_Test Customer", + payment_type="Receive", + paid_from="Debtors - _TC", + paid_to="_Test Cash - _TC", + ) + adv.is_opening = "Yes" + adv.save() # use save() to trigger set_liability_account() + adv.submit() + + gl_with_opening_set = frappe.db.get_all( + "GL Entry", filters={"voucher_no": adv.name, "is_opening": "Yes"} + ) + # 'Is Opening' can be 'Yes' for Advances in separate party account + self.assertNotEqual(gl_with_opening_set, []) + + # Disable Advance in separate party account + frappe.db.set_value( + "Company", + company, + { + "book_advance_payments_in_separate_party_account": 0, + "default_advance_received_account": None, + }, + ) + payment = create_payment_entry( + party_type="Customer", + party="_Test Customer", + payment_type="Receive", + paid_from="Debtors - _TC", + paid_to="_Test Cash - _TC", + ) + payment.is_opening = "Yes" + payment.save() + payment.submit() + gl_with_opening_set = frappe.db.get_all( + "GL Entry", filters={"voucher_no": payment.name, "is_opening": "Yes"} + ) + # 'Is Opening' should always be 'No' for normal advance payments + self.assertEqual(gl_with_opening_set, []) + def create_payment_entry(**args): payment_entry = frappe.new_doc("Payment Entry") From 9e9bc8b59c35bbf4fbb5548c0341296f858e06c9 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 11 Jun 2024 14:47:08 +0530 Subject: [PATCH 23/25] fix: Add timestamp to key for immutable views (cherry picked from commit 6bded59f1c5e7f5f63703ccf8fa03cd3dae935e9) --- erpnext/accounts/report/general_ledger/general_ledger.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 44fad110baa..e884deb2a17 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -421,6 +421,8 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map): if filters.get("show_net_values_in_party_account"): account_type_map = get_account_type_map(filters.get("company")) + immutable_ledger = frappe.db.get_single_value("Accounts Settings", "enable_immutable_ledger") + def update_value_in_dict(data, key, gle): data[key].debit += gle.debit data[key].credit += gle.credit @@ -489,6 +491,10 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map): gle.get("party_type"), gle.get("party"), ] + + if immutable_ledger: + keylist.append(gle.get("creation")) + if filters.get("include_dimensions"): for dim in accounting_dimensions: keylist.append(gle.get(dim)) From 28b7fad579bfca19f3b0be0331a38215f01521bc Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 11 Jun 2024 18:18:11 +0530 Subject: [PATCH 24/25] fix: asset depreciations and balances report correction (backport #41824) (#41851) fix: asset depreciations and balances report correction (#41824) * fix: asset depreciations and balances report correction * chore: suppress linter warnings with # nosemgrep (cherry picked from commit 857c6894052ed1dad31ab0b44d5c51f44a2c3bec) Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> --- .../asset_depreciations_and_balances.py | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py b/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py index 4fa485f54b3..f9a008ade7f 100644 --- a/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py +++ b/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py @@ -69,48 +69,50 @@ def get_asset_categories_for_grouped_by_category(filters): condition = "" if filters.get("asset_category"): condition += " and asset_category = %(asset_category)s" + # nosemgrep return frappe.db.sql( f""" - SELECT asset_category, - ifnull(sum(case when purchase_date < %(from_date)s then - case when ifnull(disposal_date, 0) = 0 or disposal_date >= %(from_date)s then - gross_purchase_amount + SELECT a.asset_category, + ifnull(sum(case when a.purchase_date < %(from_date)s then + case when ifnull(a.disposal_date, 0) = 0 or a.disposal_date >= %(from_date)s then + a.gross_purchase_amount else 0 end else 0 end), 0) as cost_as_on_from_date, - ifnull(sum(case when purchase_date >= %(from_date)s then - gross_purchase_amount + ifnull(sum(case when a.purchase_date >= %(from_date)s then + a.gross_purchase_amount else 0 end), 0) as cost_of_new_purchase, - ifnull(sum(case when ifnull(disposal_date, 0) != 0 - and disposal_date >= %(from_date)s - and disposal_date <= %(to_date)s then - case when status = "Sold" then - gross_purchase_amount + ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 + and a.disposal_date >= %(from_date)s + and a.disposal_date <= %(to_date)s then + case when a.status = "Sold" then + a.gross_purchase_amount else 0 end else 0 end), 0) as cost_of_sold_asset, - ifnull(sum(case when ifnull(disposal_date, 0) != 0 - and disposal_date >= %(from_date)s - and disposal_date <= %(to_date)s then - case when status = "Scrapped" then - gross_purchase_amount + ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 + and a.disposal_date >= %(from_date)s + and a.disposal_date <= %(to_date)s then + case when a.status = "Scrapped" then + a.gross_purchase_amount else 0 end else 0 end), 0) as cost_of_scrapped_asset - from `tabAsset` + from `tabAsset` a where docstatus=1 and company=%(company)s and purchase_date <= %(to_date)s {condition} - group by asset_category + and not exists(select name from `tabAsset Capitalization Asset Item` where asset = a.name) + group by a.asset_category """, { "to_date": filters.to_date, From 3e4c568ddccfde0dd1a03a2e90dfeedf38521ae3 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 11 Jun 2024 18:29:54 +0530 Subject: [PATCH 25/25] fix: valuation rate for serial and batch bundle for current bundle (backport #41850) (#41860) fix: valuation rate for serial and batch bundle for current bundle (#41850) (cherry picked from commit 7249a691b3ab24fc6ecb80545ff9a17b8d60a12d) Co-authored-by: rohitwaghchaure --- erpnext/controllers/stock_controller.py | 3 +++ .../stock_reconciliation.py | 22 ++++++++++++++----- .../test_stock_reconciliation.py | 14 ++++++++++++ 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index bde2f6bd099..cad9178366e 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -715,6 +715,9 @@ class StockController(AccountsController): row.db_set("rejected_serial_and_batch_bundle", None) + if row.get("current_serial_and_batch_bundle"): + row.db_set("current_serial_and_batch_bundle", None) + def set_serial_and_batch_bundle(self, table_name=None, ignore_validate=False): if not table_name: table_name = "items" diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 89e69c153d4..8301a706183 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -164,7 +164,11 @@ class StockReconciliation(StockController): for item in self.items: if not item.reconcile_all_serial_batch and item.serial_and_batch_bundle: bundle = self.get_bundle_for_specific_serial_batch(item) - item.current_serial_and_batch_bundle = bundle + item.current_serial_and_batch_bundle = bundle.name + item.current_valuation_rate = abs(bundle.avg_rate) + + if not item.valuation_rate: + item.valuation_rate = item.current_valuation_rate continue if not save and item.use_serial_batch_fields: @@ -282,7 +286,12 @@ class StockReconciliation(StockController): from erpnext.stock.serial_batch_bundle import SerialBatchCreation if row.current_serial_and_batch_bundle and not self.has_change_in_serial_batch(row): - return row.current_serial_and_batch_bundle + return frappe._dict( + { + "name": row.current_serial_and_batch_bundle, + "avg_rate": row.current_valuation_rate, + } + ) cls_obj = SerialBatchCreation( { @@ -316,12 +325,11 @@ class StockReconciliation(StockController): total_current_qty += current_qty entry.qty = current_qty * -1 - reco_obj.flags.ignore_validate = True reco_obj.save() row.current_qty = total_current_qty - return reco_obj.name + return reco_obj def has_change_in_serial_batch(self, row) -> bool: bundles = {row.serial_and_batch_bundle: [], row.current_serial_and_batch_bundle: []} @@ -721,7 +729,7 @@ class StockReconciliation(StockController): for d in serial_nos: frappe.db.set_value("Serial No", d, "purchase_rate", valuation_rate) - def get_sle_for_items(self, row, serial_nos=None): + def get_sle_for_items(self, row, serial_nos=None, current_bundle=True): """Insert Stock Ledger Entries""" if not serial_nos and row.serial_no: @@ -755,7 +763,7 @@ class StockReconciliation(StockController): has_dimensions = True if self.docstatus == 2 and (not row.batch_no or not row.serial_and_batch_bundle): - if row.current_qty: + if row.current_qty and current_bundle: data.actual_qty = -1 * row.current_qty data.qty_after_transaction = flt(row.current_qty) data.previous_qty_after_transaction = flt(row.qty) @@ -785,6 +793,8 @@ class StockReconciliation(StockController): has_serial_no = False for row in self.items: sl_entries.append(self.get_sle_for_items(row)) + if row.serial_and_batch_bundle and row.current_serial_and_batch_bundle: + sl_entries.append(self.get_sle_for_items(row, current_bundle=False)) if sl_entries: if has_serial_no: diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index 4397616e30c..8845bdbb753 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -1109,6 +1109,8 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin): ) sr.reload() + + self.assertTrue(sr.items[0].current_valuation_rate) current_sabb = sr.items[0].current_serial_and_batch_bundle doc = frappe.get_doc("Serial and Batch Bundle", current_sabb) for row in doc.entries: @@ -1118,6 +1120,18 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin): batch_qty = get_batch_qty(batches[0].batch_no, warehouse, item.name) self.assertEqual(batch_qty, 100) + for row in frappe.get_all("Repost Item Valuation", filters={"voucher_no": sr.name}): + rdoc = frappe.get_doc("Repost Item Valuation", row.name) + rdoc.cancel() + rdoc.delete() + + sr.cancel() + + for row in frappe.get_all( + "Serial and Batch Bundle", fields=["docstatus"], filters={"voucher_no": sr.name} + ): + self.assertEqual(row.docstatus, 2) + def test_not_reconcile_all_serial_nos(self): from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry from erpnext.stock.utils import get_incoming_rate