From 92eabe3cf50799c6709c44197e138738560aac14 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 27 Sep 2023 10:22:09 +0530 Subject: [PATCH 01/23] fix: PCV posting issues (#37029) * fix: PCV posting issues * fix: process closing entries separately in a background job * test: Update tests * chore: fix broken ci (cherry picked from commit 8c5fcb825716a63a074b1d83b830a06412b4c035) # Conflicts: # erpnext/accounts/doctype/payment_request/payment_request.json --- .../payment_request/payment_request.json | 11 ++++ .../period_closing_voucher.json | 10 +++- .../period_closing_voucher.py | 53 ++++++++++++++----- .../test_period_closing_voucher.py | 2 +- 4 files changed, 60 insertions(+), 16 deletions(-) diff --git a/erpnext/accounts/doctype/payment_request/payment_request.json b/erpnext/accounts/doctype/payment_request/payment_request.json index 381f3fb531a..8b139e27d46 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.json +++ b/erpnext/accounts/doctype/payment_request/payment_request.json @@ -317,9 +317,16 @@ }, { "fieldname": "payment_url", +<<<<<<< HEAD "fieldtype": "Small Text", "hidden": 1, "label": "payment_url", +======= + "fieldtype": "Data", + "hidden": 1, + "length": 500, + "options": "URL", +>>>>>>> 8c5fcb8257 (fix: PCV posting issues (#37029)) "read_only": 1 }, { @@ -394,7 +401,11 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], +<<<<<<< HEAD "modified": "2022-12-21 16:56:40.115737", +======= + "modified": "2023-09-27 09:51:42.277638", +>>>>>>> 8c5fcb8257 (fix: PCV posting issues (#37029)) "modified_by": "Administrator", "module": "Accounts", "name": "Payment Request", diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json index 54a76b34196..624b5f82f64 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json +++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json @@ -8,6 +8,7 @@ "transaction_date", "posting_date", "fiscal_year", + "year_start_date", "amended_from", "company", "column_break1", @@ -100,16 +101,22 @@ "fieldtype": "Text", "label": "Error Message", "read_only": 1 + }, + { + "fieldname": "year_start_date", + "fieldtype": "Date", + "label": "Year Start Date" } ], "icon": "fa fa-file-text", "idx": 1, "is_submittable": 1, "links": [], - "modified": "2022-07-20 14:51:04.714154", + "modified": "2023-09-11 20:19:11.810533", "modified_by": "Administrator", "module": "Accounts", "name": "Period Closing Voucher", + "naming_rule": "Expression (old style)", "owner": "Administrator", "permissions": [ { @@ -144,5 +151,6 @@ "search_fields": "posting_date, fiscal_year", "sort_field": "modified", "sort_order": "DESC", + "states": [], "title_field": "closing_account_head" } \ No newline at end of file diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py index d984d86af25..674db6c2e43 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py @@ -95,15 +95,23 @@ class PeriodClosingVoucher(AccountsController): self.check_if_previous_year_closed() - pce = frappe.db.sql( - """select name from `tabPeriod Closing Voucher` - where posting_date > %s and fiscal_year = %s and docstatus = 1 and company = %s""", - (self.posting_date, self.fiscal_year, self.company), + pcv = frappe.qb.DocType("Period Closing Voucher") + existing_entry = ( + frappe.qb.from_(pcv) + .select(pcv.name) + .where( + (pcv.posting_date >= self.posting_date) + & (pcv.fiscal_year == self.fiscal_year) + & (pcv.docstatus == 1) + & (pcv.company == self.company) + ) + .run() ) - if pce and pce[0][0]: + + if existing_entry and existing_entry[0][0]: frappe.throw( _("Another Period Closing Entry {0} has been made after {1}").format( - pce[0][0], self.posting_date + existing_entry[0][0], self.posting_date ) ) @@ -130,18 +138,27 @@ class PeriodClosingVoucher(AccountsController): frappe.enqueue( process_gl_entries, gl_entries=gl_entries, + voucher_name=self.name, + timeout=3000, + ) + + frappe.enqueue( + process_closing_entries, + gl_entries=gl_entries, closing_entries=closing_entries, voucher_name=self.name, company=self.company, closing_date=self.posting_date, - queue="long", + timeout=3000, ) + frappe.msgprint( _("The GL Entries will be processed in the background, it can take a few minutes."), alert=True, ) else: - process_gl_entries(gl_entries, closing_entries, self.name, self.company, self.posting_date) + process_gl_entries(gl_entries, self.name) + process_closing_entries(gl_entries, closing_entries, self.name, self.company, self.posting_date) def get_grouped_gl_entries(self, get_opening_entries=False): closing_entries = [] @@ -322,17 +339,12 @@ class PeriodClosingVoucher(AccountsController): return query.run(as_dict=1) -def process_gl_entries(gl_entries, closing_entries, voucher_name, company, closing_date): - from erpnext.accounts.doctype.account_closing_balance.account_closing_balance import ( - make_closing_entries, - ) +def process_gl_entries(gl_entries, voucher_name): from erpnext.accounts.general_ledger import make_gl_entries try: if gl_entries: make_gl_entries(gl_entries, merge_entries=False) - - make_closing_entries(gl_entries + closing_entries, voucher_name, company, closing_date) frappe.db.set_value("Period Closing Voucher", voucher_name, "gle_processing_status", "Completed") except Exception as e: frappe.db.rollback() @@ -340,6 +352,19 @@ def process_gl_entries(gl_entries, closing_entries, voucher_name, company, closi frappe.db.set_value("Period Closing Voucher", voucher_name, "gle_processing_status", "Failed") +def process_closing_entries(gl_entries, closing_entries, voucher_name, company, closing_date): + from erpnext.accounts.doctype.account_closing_balance.account_closing_balance import ( + make_closing_entries, + ) + + try: + if gl_entries + closing_entries: + make_closing_entries(gl_entries + closing_entries, voucher_name, company, closing_date) + except Exception as e: + frappe.db.rollback() + frappe.log_error(e) + + def make_reverse_gl_entries(voucher_type, voucher_no): from erpnext.accounts.general_ledger import make_reverse_gl_entries diff --git a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py index 2ea0971ee72..103aea965bb 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py @@ -10,7 +10,7 @@ from frappe.utils import add_months, today from erpnext.accounts.doctype.finance_book.test_finance_book import create_finance_book from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice -from erpnext.accounts.utils import get_fiscal_year, now +from erpnext.accounts.utils import get_fiscal_year class TestPeriodClosingVoucher(unittest.TestCase): From e483b4a78a260629e994eb2e0a36a59b96919288 Mon Sep 17 00:00:00 2001 From: vr-greycube <66350441+vr-greycube@users.noreply.github.com> Date: Wed, 27 Sep 2023 10:38:32 +0530 Subject: [PATCH 02/23] fix: Use default Cost Center of the Company for additional discount (#37234) fix: Set cost center as default company cost center When Discount Accounting in enabled in Selling Settings, use Company default Cost Center while making GL entries for additional_discount_account (cherry picked from commit 4ada5a488ebee75c3286af5c9492ea53274730c4) --- erpnext/controllers/accounts_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index bd2354e7452..c7e21638bc7 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1406,7 +1406,7 @@ class AccountsController(TransactionBase): "account": self.additional_discount_account, "against": supplier_or_customer, dr_or_cr: self.base_discount_amount, - "cost_center": self.cost_center, + "cost_center": self.cost_center or erpnext.get_default_cost_center(self.company), }, item=self, ) From 8d813e32565f2eb2ce5708067605e9cf1d816c81 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 27 Sep 2023 11:19:39 +0530 Subject: [PATCH 03/23] chore: resolve conflicts --- .../doctype/payment_request/payment_request.json | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/erpnext/accounts/doctype/payment_request/payment_request.json b/erpnext/accounts/doctype/payment_request/payment_request.json index 8b139e27d46..381f3fb531a 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.json +++ b/erpnext/accounts/doctype/payment_request/payment_request.json @@ -317,16 +317,9 @@ }, { "fieldname": "payment_url", -<<<<<<< HEAD "fieldtype": "Small Text", "hidden": 1, "label": "payment_url", -======= - "fieldtype": "Data", - "hidden": 1, - "length": 500, - "options": "URL", ->>>>>>> 8c5fcb8257 (fix: PCV posting issues (#37029)) "read_only": 1 }, { @@ -401,11 +394,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], -<<<<<<< HEAD "modified": "2022-12-21 16:56:40.115737", -======= - "modified": "2023-09-27 09:51:42.277638", ->>>>>>> 8c5fcb8257 (fix: PCV posting issues (#37029)) "modified_by": "Administrator", "module": "Accounts", "name": "Payment Request", From 6a8146ba8a2ef34da586adca3a3c27b12821deec Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 27 Sep 2023 12:36:58 +0530 Subject: [PATCH 04/23] fix: trial balance report freezes when adding filters (backport #37264) (#37265) fix: trial balance report freezes when adding filters (#37264) fix: Only add onclick if correct data is returned workaround for https://github.com/frappe/datatable/issues/177 (cherry picked from commit 2dc95e5d59526a531b064815d195df8413d1b837) Co-authored-by: Ankush Menat --- erpnext/public/js/financial_statements.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/public/js/financial_statements.js b/erpnext/public/js/financial_statements.js index 959cf507d53..907a775bfa5 100644 --- a/erpnext/public/js/financial_statements.js +++ b/erpnext/public/js/financial_statements.js @@ -6,8 +6,10 @@ erpnext.financial_statements = { if (data && column.fieldname=="account") { value = data.account_name || value; - column.link_onclick = - "erpnext.financial_statements.open_general_ledger(" + JSON.stringify(data) + ")"; + if (data.account) { + column.link_onclick = + "erpnext.financial_statements.open_general_ledger(" + JSON.stringify(data) + ")"; + } column.is_tree = true; } From 8fe4a4d3aa7857e5e241bcb7145065753ab8b4cb Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 27 Sep 2023 20:02:05 +0530 Subject: [PATCH 05/23] fix: incorrect qty for material request in Production Plan (#37270) --- .../production_plan/production_plan.py | 27 ++++----- .../production_plan/test_production_plan.py | 58 +++++++++++++++++++ 2 files changed, 70 insertions(+), 15 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index fcaae79d6c5..52bfeafea51 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -1508,6 +1508,10 @@ def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_d def get_materials_from_other_locations(item, warehouses, new_mr_items, company): from erpnext.stock.doctype.pick_list.pick_list import get_available_item_locations + stock_uom, purchase_uom = frappe.db.get_value( + "Item", item.get("item_code"), ["stock_uom", "purchase_uom"] + ) + locations = get_available_item_locations( item.get("item_code"), warehouses, item.get("quantity"), company, ignore_validation=True ) @@ -1518,6 +1522,10 @@ def get_materials_from_other_locations(item, warehouses, new_mr_items, company): if required_qty <= 0: return + conversion_factor = 1.0 + if purchase_uom != stock_uom and purchase_uom == item["uom"]: + conversion_factor = get_uom_conversion_factor(item["item_code"], item["uom"]) + new_dict = copy.deepcopy(item) quantity = required_qty if d.get("qty") > required_qty else d.get("qty") @@ -1530,25 +1538,14 @@ def get_materials_from_other_locations(item, warehouses, new_mr_items, company): } ) - required_qty -= quantity + required_qty -= quantity / conversion_factor new_mr_items.append(new_dict) # raise purchase request for remaining qty - if required_qty: - stock_uom, purchase_uom = frappe.db.get_value( - "Item", item["item_code"], ["stock_uom", "purchase_uom"] - ) - if purchase_uom != stock_uom and purchase_uom == item["uom"]: - conversion_factor = get_uom_conversion_factor(item["item_code"], item["uom"]) - if not (conversion_factor or frappe.flags.show_qty_in_stock_uom): - frappe.throw( - _("UOM Conversion factor ({0} -> {1}) not found for item: {2}").format( - purchase_uom, stock_uom, item["item_code"] - ) - ) - - required_qty = required_qty / conversion_factor + precision = frappe.get_precision("Material Request Plan Item", "quantity") + if flt(required_qty, precision) > 0: + required_qty = required_qty if frappe.db.get_value("UOM", purchase_uom, "must_be_whole_number"): required_qty = ceil(required_qty) diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index 9ea746cbd8b..c276f6a91b9 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -1219,6 +1219,64 @@ class TestProductionPlan(FrappeTestCase): if row.item_code == "SubAssembly2 For SUB Test": self.assertEqual(row.quantity, 10) + def test_transfer_and_purchase_mrp_for_purchase_uom(self): + from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom + from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse + + bom_tree = { + "Test FG Item INK PEN": { + "Test RM Item INK": {}, + } + } + + parent_bom = create_nested_bom(bom_tree, prefix="") + if not frappe.db.exists("UOM Conversion Detail", {"parent": "Test RM Item INK", "uom": "Kg"}): + doc = frappe.get_doc("Item", "Test RM Item INK") + doc.purchase_uom = "Kg" + doc.append("uoms", {"uom": "Kg", "conversion_factor": 0.5}) + doc.save() + + wh1 = create_warehouse("PNE Warehouse", company="_Test Company") + wh2 = create_warehouse("MBE Warehouse", company="_Test Company") + mrp_warhouse = create_warehouse("MRPBE Warehouse", company="_Test Company") + + make_stock_entry( + item_code="Test RM Item INK", + qty=2, + rate=100, + target=wh1, + ) + + make_stock_entry( + item_code="Test RM Item INK", + qty=2, + rate=100, + target=wh2, + ) + + plan = create_production_plan( + item_code=parent_bom.item, + planned_qty=10, + do_not_submit=1, + warehouse="_Test Warehouse - _TC", + ) + + plan.for_warehouse = mrp_warhouse + + items = get_items_for_material_requests( + plan.as_dict(), warehouses=[{"warehouse": wh1}, {"warehouse": wh2}] + ) + + for row in items: + row = frappe._dict(row) + if row.material_request_type == "Material Transfer": + self.assertTrue(row.from_warehouse in [wh1, wh2]) + self.assertEqual(row.quantity, 2) + + if row.material_request_type == "Purchase": + self.assertTrue(row.warehouse == mrp_warhouse) + self.assertEqual(row.quantity, 12) + def create_production_plan(**args): """ From 49f0f1ca09c6182d3e55a94733f1acc4ad5a1342 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 27 Sep 2023 13:01:18 +0530 Subject: [PATCH 06/23] fix: set route filter values for AP (cherry picked from commit 888ed36eed9eae73d23de71feeb67425a085e950) --- .../accounts_payable/accounts_payable.js | 38 ++++++++++++------- erpnext/buying/doctype/supplier/supplier.js | 2 +- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/erpnext/accounts/report/accounts_payable/accounts_payable.js b/erpnext/accounts/report/accounts_payable/accounts_payable.js index 484ff7fa2b3..9c73cbb344f 100644 --- a/erpnext/accounts/report/accounts_payable/accounts_payable.js +++ b/erpnext/accounts/report/accounts_payable/accounts_payable.js @@ -95,18 +95,11 @@ frappe.query_reports["Accounts Payable"] = { "options": "Payment Terms Template" }, { - "fieldname": "party_type", + "fieldname":"party_type", "label": __("Party Type"), - "fieldtype": "Link", - "options": "Party Type", - get_query: () => { - return { - filters: { - 'account_type': 'Payable' - } - }; - }, - on_change: () => { + "fieldtype": "Autocomplete", + options: get_party_type_options(), + on_change: function() { frappe.query_report.set_filter_value('party', ""); frappe.query_report.toggle_filter_display('supplier_group', frappe.query_report.get_filter_value('party_type') !== "Supplier"); } @@ -114,8 +107,15 @@ frappe.query_reports["Accounts Payable"] = { { "fieldname":"party", "label": __("Party"), - "fieldtype": "Dynamic Link", - "options": "party_type", + "fieldtype": "MultiSelectList", + get_data: function(txt) { + if (!frappe.query_report.filters) return; + + let party_type = frappe.query_report.get_filter_value('party_type'); + if (!party_type) return; + + return frappe.db.get_link_options(party_type, txt); + }, }, { "fieldname": "supplier_group", @@ -164,3 +164,15 @@ frappe.query_reports["Accounts Payable"] = { } erpnext.utils.add_dimensions('Accounts Payable', 9); + +function get_party_type_options() { + let options = []; + frappe.db.get_list( + "Party Type", {filters:{"account_type": "Payable"}, fields:['name']} + ).then((res) => { + res.forEach((party_type) => { + options.push(party_type.name); + }); + }); + return options; +} \ No newline at end of file diff --git a/erpnext/buying/doctype/supplier/supplier.js b/erpnext/buying/doctype/supplier/supplier.js index 1ae6f036474..e0e33b6848b 100644 --- a/erpnext/buying/doctype/supplier/supplier.js +++ b/erpnext/buying/doctype/supplier/supplier.js @@ -68,7 +68,7 @@ frappe.ui.form.on("Supplier", { }, __("View")); frm.add_custom_button(__('Accounts Payable'), function () { - frappe.set_route('query-report', 'Accounts Payable', { supplier: frm.doc.name }); + frappe.set_route('query-report', 'Accounts Payable', { party_type: "Supplier", party: frm.doc.name }); }, __("View")); frm.add_custom_button(__('Bank Account'), function () { From 2b30727fdcc447d8e96b4d735feb230f2956d04c Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 27 Sep 2023 13:01:48 +0530 Subject: [PATCH 07/23] fix: set route filter values for AR (cherry picked from commit 9d15124a6a3ad1bacc944f2f62b3999548bd40e8) --- .../accounts_receivable.js | 42 ++++++++++++------- erpnext/selling/doctype/customer/customer.js | 2 +- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js index 67a14e7880a..1073be0bdc4 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js @@ -1,6 +1,8 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt +frappe.provide("erpnext.utils"); + frappe.query_reports["Accounts Receivable"] = { "filters": [ { @@ -38,19 +40,11 @@ frappe.query_reports["Accounts Receivable"] = { } }, { - "fieldname": "party_type", + "fieldname":"party_type", "label": __("Party Type"), - "fieldtype": "Link", - "options": "Party Type", - "Default": "Customer", - get_query: () => { - return { - filters: { - 'account_type': 'Receivable' - } - }; - }, - on_change: () => { + "fieldtype": "Autocomplete", + options: get_party_type_options(), + on_change: function() { frappe.query_report.set_filter_value('party', ""); frappe.query_report.toggle_filter_display('customer_group', frappe.query_report.get_filter_value('party_type') !== "Customer"); } @@ -58,8 +52,15 @@ frappe.query_reports["Accounts Receivable"] = { { "fieldname":"party", "label": __("Party"), - "fieldtype": "Dynamic Link", - "options": "party_type", + "fieldtype": "MultiSelectList", + get_data: function(txt) { + if (!frappe.query_report.filters) return; + + let party_type = frappe.query_report.get_filter_value('party_type'); + if (!party_type) return; + + return frappe.db.get_link_options(party_type, txt); + }, }, { "fieldname": "party_account", @@ -192,3 +193,16 @@ frappe.query_reports["Accounts Receivable"] = { } erpnext.utils.add_dimensions('Accounts Receivable', 9); + + +function get_party_type_options() { + let options = []; + frappe.db.get_list( + "Party Type", {filters:{"account_type": "Receivable"}, fields:['name']} + ).then((res) => { + res.forEach((party_type) => { + options.push(party_type.name); + }); + }); + return options; +} \ No newline at end of file diff --git a/erpnext/selling/doctype/customer/customer.js b/erpnext/selling/doctype/customer/customer.js index b53f339229b..432abfce73b 100644 --- a/erpnext/selling/doctype/customer/customer.js +++ b/erpnext/selling/doctype/customer/customer.js @@ -118,7 +118,7 @@ frappe.ui.form.on("Customer", { // custom buttons frm.add_custom_button(__('Accounts Receivable'), function () { - frappe.set_route('query-report', 'Accounts Receivable', {customer:frm.doc.name}); + frappe.set_route('query-report', 'Accounts Receivable', { party_type: "Customer", party: frm.doc.name }); }, __('View')); frm.add_custom_button(__('Accounting Ledger'), function () { From 6d7aa2ae942c34daf35ed617f52d58f94d9c43e5 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 27 Sep 2023 13:02:52 +0530 Subject: [PATCH 08/23] fix: query for multiselect filter (cherry picked from commit e7239e02d4ff7ec0338f45aa4264dc65d12d3f36) --- .../accounts/report/accounts_receivable/accounts_receivable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 79424023652..e3b671f3973 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -801,7 +801,7 @@ class ReceivablePayableReport(object): self.qb_selection_filter.append(self.filters.party_type == self.ple.party_type) if self.filters.get("party"): - self.qb_selection_filter.append(self.filters.party == self.ple.party) + self.qb_selection_filter.append(self.ple.party.isin(self.filters.party)) if self.filters.party_account: self.qb_selection_filter.append(self.ple.account == self.filters.party_account) From 403ff697e94d79a31dd61d6ea9f5770106a07cf2 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 27 Sep 2023 13:03:37 +0530 Subject: [PATCH 09/23] fix: summary report filters (cherry picked from commit f7cb68a45fade2499d51d92a5a096f0640b039f2) --- .../accounts_payable_summary.js | 38 +++++++++++------- .../accounts_receivable_summary.js | 39 ++++++++++++------- 2 files changed, 50 insertions(+), 27 deletions(-) diff --git a/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js b/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js index 8a1725c0485..9e575e669d2 100644 --- a/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js +++ b/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js @@ -72,18 +72,11 @@ frappe.query_reports["Accounts Payable Summary"] = { } }, { - "fieldname": "party_type", + "fieldname":"party_type", "label": __("Party Type"), - "fieldtype": "Link", - "options": "Party Type", - get_query: () => { - return { - filters: { - 'account_type': 'Payable' - } - }; - }, - on_change: () => { + "fieldtype": "Autocomplete", + options: get_party_type_options(), + on_change: function() { frappe.query_report.set_filter_value('party', ""); frappe.query_report.toggle_filter_display('supplier_group', frappe.query_report.get_filter_value('party_type') !== "Supplier"); } @@ -91,8 +84,15 @@ frappe.query_reports["Accounts Payable Summary"] = { { "fieldname":"party", "label": __("Party"), - "fieldtype": "Dynamic Link", - "options": "party_type", + "fieldtype": "MultiSelectList", + get_data: function(txt) { + if (!frappe.query_report.filters) return; + + let party_type = frappe.query_report.get_filter_value('party_type'); + if (!party_type) return; + + return frappe.db.get_link_options(party_type, txt); + }, }, { "fieldname":"payment_terms_template", @@ -122,3 +122,15 @@ frappe.query_reports["Accounts Payable Summary"] = { } erpnext.utils.add_dimensions('Accounts Payable Summary', 9); + +function get_party_type_options() { + let options = []; + frappe.db.get_list( + "Party Type", {filters:{"account_type": "Payable"}, fields:['name']} + ).then((res) => { + res.forEach((party_type) => { + options.push(party_type.name); + }); + }); + return options; +} \ No newline at end of file diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js index a78fbeb0308..5ad10c7890a 100644 --- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js +++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js @@ -72,19 +72,11 @@ frappe.query_reports["Accounts Receivable Summary"] = { } }, { - "fieldname": "party_type", + "fieldname":"party_type", "label": __("Party Type"), - "fieldtype": "Link", - "options": "Party Type", - "Default": "Customer", - get_query: () => { - return { - filters: { - 'account_type': 'Receivable' - } - }; - }, - on_change: () => { + "fieldtype": "Autocomplete", + options: get_party_type_options(), + on_change: function() { frappe.query_report.set_filter_value('party', ""); frappe.query_report.toggle_filter_display('customer_group', frappe.query_report.get_filter_value('party_type') !== "Customer"); } @@ -92,8 +84,15 @@ frappe.query_reports["Accounts Receivable Summary"] = { { "fieldname":"party", "label": __("Party"), - "fieldtype": "Dynamic Link", - "options": "party_type", + "fieldtype": "MultiSelectList", + get_data: function(txt) { + if (!frappe.query_report.filters) return; + + let party_type = frappe.query_report.get_filter_value('party_type'); + if (!party_type) return; + + return frappe.db.get_link_options(party_type, txt); + }, }, { "fieldname":"customer_group", @@ -151,3 +150,15 @@ frappe.query_reports["Accounts Receivable Summary"] = { } erpnext.utils.add_dimensions('Accounts Receivable Summary', 9); + +function get_party_type_options() { + let options = []; + frappe.db.get_list( + "Party Type", {filters:{"account_type": "Receivable"}, fields:['name']} + ).then((res) => { + res.forEach((party_type) => { + options.push(party_type.name); + }); + }); + return options; +} \ No newline at end of file From 4962b67358a7b31cbe0c93f186dc7fe030271cc7 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 27 Sep 2023 13:04:13 +0530 Subject: [PATCH 10/23] fix: process soa filter for multiselect (cherry picked from commit 4b28154f5ee724d1a2c109f5b9e5b27bc707436b) --- .../process_statement_of_accounts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index 5055c345917..3f8731afe65 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -144,7 +144,7 @@ def get_ar_filters(doc, entry): return { "report_date": doc.posting_date if doc.posting_date else None, "party_type": "Customer", - "party": entry.customer, + "party": [entry.customer], "customer_name": entry.customer_name if entry.customer_name else None, "payment_terms_template": doc.payment_terms_template if doc.payment_terms_template else None, "sales_partner": doc.sales_partner if doc.sales_partner else None, From 28756bf7b6ddd0b012235e4c0c44d583586c08a5 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 27 Sep 2023 13:57:49 +0530 Subject: [PATCH 11/23] fix: party format in test (cherry picked from commit 59e8abfd57d85719147129d22e4cc2293a379d71) --- .../accounts/report/accounts_payable/test_accounts_payable.py | 2 +- .../report/accounts_receivable/test_accounts_receivable.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/accounts_payable/test_accounts_payable.py b/erpnext/accounts/report/accounts_payable/test_accounts_payable.py index 3cf93cc8659..9f03d92cd50 100644 --- a/erpnext/accounts/report/accounts_payable/test_accounts_payable.py +++ b/erpnext/accounts/report/accounts_payable/test_accounts_payable.py @@ -34,7 +34,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): filters = { "company": self.company, "party_type": "Supplier", - "party": self.supplier, + "party": [self.supplier], "report_date": today(), "range1": 30, "range2": 60, diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py index b98916ee443..8c13f85a98e 100644 --- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py @@ -573,7 +573,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): filters = { "company": self.company, "party_type": "Customer", - "party": self.customer, + "party": [self.customer], "report_date": today(), "range1": 30, "range2": 60, From 01b54134ae79b60d1599a828944c0b73ab8b860b Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 28 Sep 2023 11:16:57 +0530 Subject: [PATCH 12/23] test: multi select party filter in AR report (cherry picked from commit 2c7d6aec89327330be415b8c624c9272fb3d613b) --- .../test_accounts_receivable.py | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py index 8c13f85a98e..4307689158f 100644 --- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py @@ -605,3 +605,41 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): for field in expected: with self.subTest(field=field): self.assertEqual(report_output.get(field), expected.get(field)) + + def test_multi_select_party_filter(self): + self.customer1 = self.customer + self.create_customer("_Test Customer 2") + self.customer2 = self.customer + self.create_customer("_Test Customer 3") + self.customer3 = self.customer + + filters = { + "company": self.company, + "party_type": "Customer", + "party": [self.customer1, self.customer3], + "report_date": today(), + "range1": 30, + "range2": 60, + "range3": 90, + "range4": 120, + } + + si1 = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True) + si1.customer = self.customer1 + si1.save().submit() + + si2 = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True) + si2.customer = self.customer2 + si2.save().submit() + + si3 = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True) + si3.customer = self.customer3 + si3.save().submit() + + # check invoice grand total and invoiced column's value for 3 payment terms + report = execute(filters) + + expected_output = {self.customer1, self.customer3} + self.assertEqual(len(report[1]), 2) + output_for = set([x.party for x in report[1]]) + self.assertEqual(output_for, expected_output) From 21c1c7194bf5c7d24c2aec55ffac3ad3d0cdfc68 Mon Sep 17 00:00:00 2001 From: HENRY Florian Date: Fri, 29 Sep 2023 04:59:18 +0200 Subject: [PATCH 13/23] refactor: In Quotation Item, discount_and_margin section should have same collapsible_depends_on as other similar DocType (Sales Order Item,Sales Invoice Item,...) (#37272) --- erpnext/selling/doctype/quotation_item/quotation_item.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/quotation_item/quotation_item.json b/erpnext/selling/doctype/quotation_item/quotation_item.json index fb810318e93..f5e641dceb4 100644 --- a/erpnext/selling/doctype/quotation_item/quotation_item.json +++ b/erpnext/selling/doctype/quotation_item/quotation_item.json @@ -236,6 +236,7 @@ }, { "collapsible": 1, + "collapsible_depends_on": "eval: doc.margin_type || doc.discount_amount", "fieldname": "discount_and_margin", "fieldtype": "Section Break", "label": "Discount and Margin" @@ -667,7 +668,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2023-02-06 11:00:07.042364", + "modified": "2023-09-27 14:02:12.332407", "modified_by": "Administrator", "module": "Selling", "name": "Quotation Item", From 7f1483ad707feac6413994400217413e5c167c7b Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Fri, 29 Sep 2023 14:35:45 +0530 Subject: [PATCH 14/23] fix: Description field for the 'Ignore Available Stock' (#37293) --- .../doctype/production_plan/production_plan.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.json b/erpnext/manufacturing/doctype/production_plan/production_plan.json index 0d0fd5e2706..4a0041662bc 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.json +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.json @@ -228,7 +228,7 @@ }, { "default": "0", - "description": "If enabled, the system won't create material requests for the available items.", + "description": "If enabled, the system will create material requests even if the stock exists in the 'Raw Materials Warehouse'.", "fieldname": "ignore_existing_ordered_qty", "fieldtype": "Check", "label": "Ignore Available Stock" @@ -422,7 +422,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-07-28 13:37:43.926686", + "modified": "2023-09-29 11:41:03.246059", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Plan", From 95e0bf5e0edc7886bcb56c781ec56e0b72577b8e Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 29 Sep 2023 15:07:10 +0530 Subject: [PATCH 15/23] fix: Not unique table/alias: 'tabTask' (backport #37285) (#37298) fix: Not unique table/alias: 'tabTask' (#37285) (cherry picked from commit 361e55511886d97a8b3b03a2d56a7220200c76a6) Co-authored-by: s-aga-r --- erpnext/projects/doctype/task/task.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/erpnext/projects/doctype/task/task.json b/erpnext/projects/doctype/task/task.json index 33a8799f96c..3d3a463ec0d 100644 --- a/erpnext/projects/doctype/task/task.json +++ b/erpnext/projects/doctype/task/task.json @@ -60,7 +60,6 @@ "fieldname": "subject", "fieldtype": "Data", "in_global_search": 1, - "in_list_view": 1, "in_standard_filter": 1, "label": "Subject", "reqd": 1, @@ -140,7 +139,6 @@ "fieldname": "parent_task", "fieldtype": "Link", "ignore_user_permissions": 1, - "in_list_view": 1, "label": "Parent Task", "options": "Task", "search_index": 1 @@ -398,7 +396,7 @@ "is_tree": 1, "links": [], "max_attachments": 5, - "modified": "2023-09-06 13:52:05.861175", + "modified": "2023-09-28 13:52:05.861175", "modified_by": "Administrator", "module": "Projects", "name": "Task", From 04f0dfb691fa0f9ea03518b28a7053454610f7c7 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sun, 1 Oct 2023 21:31:17 +0530 Subject: [PATCH 16/23] fix: ignore user permissions for `Source Warehouse` (backport #37313) (#37314) * fix: ignore user permissions for `Source Warehouse` (#37313) (cherry picked from commit e7f4b7b190ab2da7fcd7579abdb0ba7bbed17b65) # Conflicts: # erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json # erpnext/stock/doctype/purchase_receipt/purchase_receipt.json * chore: `conflicts` --------- Co-authored-by: s-aga-r --- .../accounts/doctype/purchase_invoice/purchase_invoice.json | 3 ++- erpnext/buying/doctype/purchase_order/purchase_order.json | 3 ++- erpnext/stock/doctype/purchase_receipt/purchase_receipt.json | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 6fa71654f67..7e848a0b4e8 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -1377,6 +1377,7 @@ "depends_on": "eval:doc.is_subcontracted", "fieldname": "supplier_warehouse", "fieldtype": "Link", + "ignore_user_permissions": 1, "label": "Supplier Warehouse", "no_copy": 1, "options": "Warehouse", @@ -1574,7 +1575,7 @@ "idx": 204, "is_submittable": 1, "links": [], - "modified": "2023-07-04 17:23:59.145031", + "modified": "2023-10-01 21:01:47.282533", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index bacd98bea77..01ce8c33ff9 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -475,6 +475,7 @@ "depends_on": "eval:doc.is_subcontracted", "fieldname": "supplier_warehouse", "fieldtype": "Link", + "ignore_user_permissions": 1, "label": "Supplier Warehouse", "options": "Warehouse" }, @@ -1272,7 +1273,7 @@ "idx": 105, "is_submittable": 1, "links": [], - "modified": "2023-09-13 16:21:07.361700", + "modified": "2023-10-01 20:58:07.851037", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json index f33c7a65cff..aae1bad0977 100755 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json @@ -463,6 +463,7 @@ "depends_on": "eval:doc.is_subcontracted", "fieldname": "supplier_warehouse", "fieldtype": "Link", + "ignore_user_permissions": 1, "label": "Supplier Warehouse", "no_copy": 1, "oldfieldname": "supplier_warehouse", @@ -1240,7 +1241,7 @@ "idx": 261, "is_submittable": 1, "links": [], - "modified": "2023-07-04 17:24:17.025390", + "modified": "2023-10-01 21:00:44.556816", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt", From 6daea6ccb2355df957ba73cff917f15d6db4eba7 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 3 Oct 2023 13:35:10 +0530 Subject: [PATCH 17/23] feat: asset salvage_value_percentage (backport #37302) (#37334) * feat: asset salvage_value_percentage (#37302) * feat: asset salvage_value_percentage * chore: add missing parameter in get_item_details * chore: change asset depr table colors (cherry picked from commit fed94845ceca1a755cb997ec4a1f069b2f2a0534) # Conflicts: # erpnext/assets/doctype/asset/asset.js # erpnext/assets/doctype/asset_activity/asset_activity.json # erpnext/assets/doctype/asset_finance_book/asset_finance_book.json * chore: resolving conflicts --------- Co-authored-by: Anand Baburajan --- erpnext/assets/doctype/asset/asset.js | 29 +++++++++++++++++-- erpnext/assets/doctype/asset/asset.py | 12 ++++++-- .../asset_finance_book.json | 8 ++++- 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index 0923d0093f9..5c1da91fdf9 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -287,7 +287,8 @@ frappe.ui.form.on('Asset', { method: "erpnext.assets.doctype.asset.asset.get_item_details", args: { item_code: frm.doc.item_code, - asset_category: frm.doc.asset_category + asset_category: frm.doc.asset_category, + gross_purchase_amount: frm.doc.gross_purchase_amount }, callback: function(r, rt) { if(r.message) { @@ -504,7 +505,21 @@ frappe.ui.form.on('Asset', { } }); } - } + }, + + set_salvage_value_percentage_or_expected_value_after_useful_life: function(frm, row, salvage_value_percentage_changed, expected_value_after_useful_life_changed) { + if (expected_value_after_useful_life_changed) { + frappe.flags.from_set_salvage_value_percentage_or_expected_value_after_useful_life = true; + const new_salvage_value_percentage = flt((row.expected_value_after_useful_life * 100) / frm.doc.gross_purchase_amount, precision("salvage_value_percentage", row)); + frappe.model.set_value(row.doctype, row.name, "salvage_value_percentage", new_salvage_value_percentage); + frappe.flags.from_set_salvage_value_percentage_or_expected_value_after_useful_life = false; + } else if (salvage_value_percentage_changed) { + frappe.flags.from_set_salvage_value_percentage_or_expected_value_after_useful_life = true; + const new_expected_value_after_useful_life = flt(frm.doc.gross_purchase_amount * (row.salvage_value_percentage / 100), precision('gross_purchase_amount')); + frappe.model.set_value(row.doctype, row.name, "expected_value_after_useful_life", new_expected_value_after_useful_life); + frappe.flags.from_set_salvage_value_percentage_or_expected_value_after_useful_life = false; + } + }, }); frappe.ui.form.on('Asset Finance Book', { @@ -516,9 +531,19 @@ frappe.ui.form.on('Asset Finance Book', { expected_value_after_useful_life: function(frm, cdt, cdn) { const row = locals[cdt][cdn]; + if (!frappe.flags.from_set_salvage_value_percentage_or_expected_value_after_useful_life) { + frm.events.set_salvage_value_percentage_or_expected_value_after_useful_life(frm, row, false, true); + } frm.events.set_depreciation_rate(frm, row); }, + salvage_value_percentage: function(frm, cdt, cdn) { + const row = locals[cdt][cdn]; + if (!frappe.flags.from_set_salvage_value_percentage_or_expected_value_after_useful_life) { + frm.events.set_salvage_value_percentage_or_expected_value_after_useful_life(frm, row, true, false); + } + }, + frequency_of_depreciation: function(frm, cdt, cdn) { const row = locals[cdt][cdn]; frm.events.set_depreciation_rate(frm, row); diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index e72ddcb12a0..81a35ad8f93 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -204,7 +204,9 @@ class Asset(AccountsController): self.asset_category = frappe.get_cached_value("Item", self.item_code, "asset_category") if self.item_code and not self.get("finance_books"): - finance_books = get_item_details(self.item_code, self.asset_category) + finance_books = get_item_details( + self.item_code, self.asset_category, self.gross_purchase_amount + ) self.set("finance_books", finance_books) def validate_finance_books(self): @@ -1195,7 +1197,7 @@ def transfer_asset(args): @frappe.whitelist() -def get_item_details(item_code, asset_category): +def get_item_details(item_code, asset_category, gross_purchase_amount): asset_category_doc = frappe.get_doc("Asset Category", asset_category) books = [] for d in asset_category_doc.finance_books: @@ -1205,7 +1207,11 @@ def get_item_details(item_code, asset_category): "depreciation_method": d.depreciation_method, "total_number_of_depreciations": d.total_number_of_depreciations, "frequency_of_depreciation": d.frequency_of_depreciation, - "start_date": nowdate(), + "daily_depreciation": d.daily_depreciation, + "salvage_value_percentage": d.salvage_value_percentage, + "expected_value_after_useful_life": flt(gross_purchase_amount) + * flt(d.salvage_value_percentage / 100), + "depreciation_start_date": d.depreciation_start_date or nowdate(), } ) diff --git a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json index 1f80e3a67bd..ea1a8112843 100644 --- a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json +++ b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json @@ -12,6 +12,7 @@ "column_break_5", "frequency_of_depreciation", "depreciation_start_date", + "salvage_value_percentage", "expected_value_after_useful_life", "value_after_depreciation", "rate_of_depreciation" @@ -87,12 +88,17 @@ "fieldname": "daily_depreciation", "fieldtype": "Check", "label": "Daily Depreciation" + }, + { + "fieldname": "salvage_value_percentage", + "fieldtype": "Percent", + "label": "Salvage Value Percentage" } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-08-10 18:56:09.022246", + "modified": "2023-09-29 15:39:52.740594", "modified_by": "Administrator", "module": "Assets", "name": "Asset Finance Book", From 82e8606b3c4403d79556c896a98f942762cd8fa7 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 3 Oct 2023 18:53:43 +0530 Subject: [PATCH 18/23] fix: currency symbol in the Supplier Quotation Comparison report (#37337) fix: currency in the Supplier Quotation Comparison report --- .../supplier_quotation_comparison.py | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py index a7282909618..01ff28d8103 100644 --- a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py +++ b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py @@ -35,8 +35,12 @@ def get_data(filters): sq_item.parent, sq_item.item_code, sq_item.qty, + sq.currency, sq_item.stock_qty, sq_item.amount, + sq_item.base_rate, + sq_item.base_amount, + sq.price_list_currency, sq_item.uom, sq_item.stock_uom, sq_item.request_for_quotation, @@ -105,7 +109,11 @@ def prepare_data(supplier_quotation_data, filters): "qty": data.get("qty"), "price": flt(data.get("amount") * exchange_rate, float_precision), "uom": data.get("uom"), + "price_list_currency": data.get("price_list_currency"), + "currency": data.get("currency"), "stock_uom": data.get("stock_uom"), + "base_amount": flt(data.get("base_amount"), float_precision), + "base_rate": flt(data.get("base_rate"), float_precision), "request_for_quotation": data.get("request_for_quotation"), "valid_till": data.get("valid_till"), "lead_time_days": data.get("lead_time_days"), @@ -183,6 +191,8 @@ def prepare_chart_data(suppliers, qty_list, supplier_qty_price_map): def get_columns(filters): + currency = frappe.get_cached_value("Company", filters.get("company"), "default_currency") + group_by_columns = [ { "fieldname": "supplier_name", @@ -203,11 +213,18 @@ def get_columns(filters): columns = [ {"fieldname": "uom", "label": _("UOM"), "fieldtype": "Link", "options": "UOM", "width": 90}, {"fieldname": "qty", "label": _("Quantity"), "fieldtype": "Float", "width": 80}, + { + "fieldname": "currency", + "label": _("Currency"), + "fieldtype": "Link", + "options": "Currency", + "width": 110, + }, { "fieldname": "price", "label": _("Price"), "fieldtype": "Currency", - "options": "Company:company:default_currency", + "options": "currency", "width": 110, }, { @@ -221,9 +238,23 @@ def get_columns(filters): "fieldname": "price_per_unit", "label": _("Price per Unit (Stock UOM)"), "fieldtype": "Currency", - "options": "Company:company:default_currency", + "options": "currency", "width": 120, }, + { + "fieldname": "base_amount", + "label": _("Price ({0})").format(currency), + "fieldtype": "Currency", + "options": "price_list_currency", + "width": 180, + }, + { + "fieldname": "base_rate", + "label": _("Price Per Unit ({0})").format(currency), + "fieldtype": "Currency", + "options": "price_list_currency", + "width": 180, + }, { "fieldname": "quotation", "label": _("Supplier Quotation"), From 020aedb8b010a5503e8f57f7e690aaf0b3f07266 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Fri, 29 Sep 2023 18:20:02 +0530 Subject: [PATCH 19/23] fix: add only float row values for total (cherry picked from commit 1dab195560021347c2edfae4ffcedc93be647868) --- .../accounts_receivable_summary.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py index cffc87895ef..f89841523a1 100644 --- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py +++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py @@ -99,13 +99,11 @@ class AccountsReceivableSummary(ReceivablePayableReport): # Add all amount columns for k in list(self.party_total[d.party]): - if k not in ["currency", "sales_person"]: - - self.party_total[d.party][k] += d.get(k, 0.0) + if type(self.party_total[d.party][k]) == float: + self.party_total[d.party][k] += d.get(k) or 0.0 # set territory, customer_group, sales person etc self.set_party_details(d) - self.party_total[d.party].update({"party_type": d.party_type}) def init_party_total(self, row): self.party_total.setdefault( @@ -124,6 +122,7 @@ class AccountsReceivableSummary(ReceivablePayableReport): "total_due": 0.0, "future_amount": 0.0, "sales_person": [], + "party_type": row.party_type, } ), ) @@ -133,13 +132,12 @@ class AccountsReceivableSummary(ReceivablePayableReport): for key in ("territory", "customer_group", "supplier_group"): if row.get(key): - self.party_total[row.party][key] = row.get(key) - + self.party_total[row.party][key] = row.get(key, "") if row.sales_person: - self.party_total[row.party].sales_person.append(row.sales_person) + self.party_total[row.party].sales_person.append(row.get("sales_person", "")) if self.filters.sales_partner: - self.party_total[row.party]["default_sales_partner"] = row.get("default_sales_partner") + self.party_total[row.party]["default_sales_partner"] = row.get("default_sales_partner", "") def get_columns(self): self.columns = [] From f3b872a8e2081e2c51a015ce0fe87e328f0306bd Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 3 Oct 2023 17:38:43 +0530 Subject: [PATCH 20/23] refactor: use `isinstance` over `type` (cherry picked from commit 67440c38aef1f94432c0ec97f9bef065363d3a1b) --- .../accounts_receivable_summary/accounts_receivable_summary.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py index f89841523a1..60274cd8b10 100644 --- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py +++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py @@ -99,7 +99,7 @@ class AccountsReceivableSummary(ReceivablePayableReport): # Add all amount columns for k in list(self.party_total[d.party]): - if type(self.party_total[d.party][k]) == float: + if isinstance(self.party_total[d.party][k], float): self.party_total[d.party][k] += d.get(k) or 0.0 # set territory, customer_group, sales person etc From c3aeb2dec58190d16a18e2609fd57054bda54e43 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 3 Oct 2023 20:34:10 +0530 Subject: [PATCH 21/23] =?UTF-8?q?fix:=20do=20not=20consider=20submitted=20?= =?UTF-8?q?Work=20Orders=20in=20the=20Production=20Plan=20Res=E2=80=A6=20(?= =?UTF-8?q?#37343)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix: do not consider submitted Work Orders in the Production Plan Reserve qty --- .../production_plan/production_plan.py | 43 ++++++++++++++++-- .../production_plan/test_production_plan.py | 45 +++++++++++++++++++ .../doctype/work_order/work_order.py | 10 ++++- 3 files changed, 93 insertions(+), 5 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 52bfeafea51..f1817e3305b 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -8,6 +8,7 @@ import json import frappe from frappe import _, msgprint from frappe.model.document import Document +from frappe.query_builder import Case from frappe.query_builder.functions import IfNull, Sum from frappe.utils import ( add_days, @@ -1616,18 +1617,33 @@ def get_reserved_qty_for_production_plan(item_code, warehouse): table = frappe.qb.DocType("Production Plan") child = frappe.qb.DocType("Material Request Plan Item") + completed_production_plans = get_completed_production_plans() + + case = Case() query = ( frappe.qb.from_(table) .inner_join(child) .on(table.name == child.parent) - .select(Sum(child.quantity * IfNull(child.conversion_factor, 1.0))) + .select( + Sum( + child.quantity + * IfNull( + case.when(child.material_request_type == "Purchase", child.conversion_factor).else_(1.0), 1.0 + ) + ) + ) .where( (table.docstatus == 1) & (child.item_code == item_code) & (child.warehouse == warehouse) & (table.status.notin(["Completed", "Closed"])) ) - ).run() + ) + + if completed_production_plans: + query = query.where(table.name.notin(completed_production_plans)) + + query = query.run() if not query: return 0.0 @@ -1635,7 +1651,9 @@ def get_reserved_qty_for_production_plan(item_code, warehouse): reserved_qty_for_production_plan = flt(query[0][0]) reserved_qty_for_production = flt( - get_reserved_qty_for_production(item_code, warehouse, check_production_plan=True) + get_reserved_qty_for_production( + item_code, warehouse, completed_production_plans, check_production_plan=True + ) ) if reserved_qty_for_production > reserved_qty_for_production_plan: @@ -1644,6 +1662,25 @@ def get_reserved_qty_for_production_plan(item_code, warehouse): return reserved_qty_for_production_plan - reserved_qty_for_production +def get_completed_production_plans(): + table = frappe.qb.DocType("Production Plan") + child = frappe.qb.DocType("Production Plan Item") + + query = ( + frappe.qb.from_(table) + .inner_join(child) + .on(table.name == child.parent) + .select(table.name) + .where( + (table.docstatus == 1) + & (table.status.notin(["Completed", "Closed"])) + & (child.ordered_qty >= child.planned_qty) + ) + ).run(as_dict=True) + + return list(set([d.name for d in query])) + + def get_raw_materials_of_sub_assembly_items( item_details, company, bom_no, include_non_stock_items, sub_assembly_items, planned_qty=1 ): diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index c276f6a91b9..7014c067e2b 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -6,6 +6,7 @@ from frappe.utils import add_to_date, flt, getdate, now_datetime, nowdate from erpnext.controllers.item_variant import create_variant from erpnext.manufacturing.doctype.production_plan.production_plan import ( + get_completed_production_plans, get_items_for_material_requests, get_sales_orders, get_warehouse_list, @@ -1092,6 +1093,50 @@ class TestProductionPlan(FrappeTestCase): self.assertEqual(after_qty, before_qty) + def test_resered_qty_for_production_plan_for_less_rm_qty(self): + from erpnext.stock.utils import get_or_make_bin + + bin_name = get_or_make_bin("Raw Material Item 1", "_Test Warehouse - _TC") + before_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan")) + + pln = create_production_plan(item_code="Test Production Item 1", planned_qty=10) + + bin_name = get_or_make_bin("Raw Material Item 1", "_Test Warehouse - _TC") + after_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan")) + + self.assertEqual(after_qty - before_qty, 10) + + pln.make_work_order() + + plans = [] + for row in frappe.get_all("Work Order", filters={"production_plan": pln.name}, fields=["name"]): + wo_doc = frappe.get_doc("Work Order", row.name) + wo_doc.source_warehouse = "_Test Warehouse - _TC" + wo_doc.wip_warehouse = "_Test Warehouse 1 - _TC" + wo_doc.fg_warehouse = "_Test Warehouse - _TC" + for d in wo_doc.required_items: + d.source_warehouse = "_Test Warehouse - _TC" + print(d.required_qty, "before") + d.required_qty -= 5 + make_stock_entry( + item_code=d.item_code, + qty=d.required_qty, + rate=100, + target="_Test Warehouse - _TC", + ) + + wo_doc.submit() + plans.append(pln.name) + + bin_name = get_or_make_bin("Raw Material Item 1", "_Test Warehouse - _TC") + after_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan")) + + self.assertEqual(after_qty, before_qty) + + completed_plans = get_completed_production_plans() + for plan in plans: + self.assertTrue(plan in completed_plans) + def test_resered_qty_for_production_plan_for_material_requests_with_multi_UOM(self): from erpnext.stock.utils import get_or_make_bin diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 070aaf00459..a491501d4b7 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -362,10 +362,10 @@ class WorkOrder(Document): else: self.update_work_order_qty_in_so() + self.update_ordered_qty() self.update_reserved_qty_for_production() self.update_completed_qty_in_material_request() self.update_planned_qty() - self.update_ordered_qty() self.create_job_card() def on_cancel(self): @@ -1491,7 +1491,10 @@ def create_pick_list(source_name, target_doc=None, for_qty=None): def get_reserved_qty_for_production( - item_code: str, warehouse: str, check_production_plan: bool = False + item_code: str, + warehouse: str, + completed_production_plans: list = None, + check_production_plan: bool = False, ) -> float: """Get total reserved quantity for any item in specified warehouse""" wo = frappe.qb.DocType("Work Order") @@ -1524,6 +1527,9 @@ def get_reserved_qty_for_production( if check_production_plan: query = query.where(wo.production_plan.isnotnull()) + if completed_production_plans: + query = query.where(wo.production_plan.notin(completed_production_plans)) + return query.run()[0][0] or 0.0 From e975a10a751cb1eb190ea7e06cc1eccca80554d2 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 3 Oct 2023 22:08:37 +0530 Subject: [PATCH 22/23] chore: fix linter issue (#37349) --- .../doctype/production_plan/test_production_plan.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index 7014c067e2b..55122f7069c 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -1116,7 +1116,6 @@ class TestProductionPlan(FrappeTestCase): wo_doc.fg_warehouse = "_Test Warehouse - _TC" for d in wo_doc.required_items: d.source_warehouse = "_Test Warehouse - _TC" - print(d.required_qty, "before") d.required_qty -= 5 make_stock_entry( item_code=d.item_code, From 643bb0511ce6b858d84613d08b551b7f15c6364a Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 3 Oct 2023 22:56:33 +0530 Subject: [PATCH 23/23] fix: validation message for valuation rate (#37301) --- erpnext/controllers/selling_controller.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 8948b7e3f7c..5daaba8e892 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -282,7 +282,9 @@ class SellingController(StockController): last_valuation_rate_in_sales_uom = last_valuation_rate * (item.conversion_factor or 1) if flt(item.base_net_rate) < flt(last_valuation_rate_in_sales_uom): - throw_message(item.idx, item.item_name, last_valuation_rate_in_sales_uom, "valuation rate") + throw_message( + item.idx, item.item_name, last_valuation_rate_in_sales_uom, "valuation rate (Moving Average)" + ) def get_item_list(self): il = []