From bfaa93b0ca641352917be16a50e602a7fe458386 Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Mon, 20 Nov 2023 09:43:02 +0000 Subject: [PATCH 01/16] fix: honour currency precision while fetching balance --- erpnext/accounts/utils.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 7d91309fcc..2636a8d4f6 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -237,7 +237,6 @@ def get_balance_on( cond.append("""gle.cost_center = %s """ % (frappe.db.escape(cost_center, percent=False),)) if account: - if not (frappe.flags.ignore_account_permission or ignore_account_permission): acc.check_permission("read") @@ -283,11 +282,11 @@ def get_balance_on( cond.append("""gle.company = %s """ % (frappe.db.escape(company, percent=False))) if account or (party_type and party) or account_type: - + precision = get_currency_precision() if in_account_currency: - select_field = "sum(debit_in_account_currency) - sum(credit_in_account_currency)" + select_field = f"sum(round(debit_in_account_currency, {precision})) - sum(round(credit_in_account_currency, {precision}))" else: - select_field = "sum(debit) - sum(credit)" + select_field = f"sum(round(debit, {precision})) - sum(round(credit, {precision}))" bal = frappe.db.sql( """ SELECT {0} From 383a4b132ed5cc3383b035de8c22ba759b1e1241 Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Tue, 21 Nov 2023 12:46:56 +0000 Subject: [PATCH 02/16] chore: change f-string to sql params --- erpnext/accounts/utils.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 2636a8d4f6..857796b17b 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -282,18 +282,22 @@ def get_balance_on( cond.append("""gle.company = %s """ % (frappe.db.escape(company, percent=False))) if account or (party_type and party) or account_type: - precision = get_currency_precision() + precision = frappe.db.escape(get_currency_precision()) if in_account_currency: - select_field = f"sum(round(debit_in_account_currency, {precision})) - sum(round(credit_in_account_currency, {precision}))" + select_field = ( + "sum(round(debit_in_account_currency, %s)) - sum(round(credit_in_account_currency, %s))" + ) else: - select_field = f"sum(round(debit, {precision})) - sum(round(credit, {precision}))" + select_field = "sum(round(debit, %s)) - sum(round(credit, %s))" + bal = frappe.db.sql( """ SELECT {0} FROM `tabGL Entry` gle WHERE {1}""".format( select_field, " and ".join(cond) - ) + ), + (precision, precision), )[0][0] # if bal is None, return 0 return flt(bal) From a8949174c878499e14228517abe66050f9b9ddb9 Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Sat, 9 Dec 2023 09:30:17 +0530 Subject: [PATCH 03/16] chore: avoid explicit escaping for precision Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> --- erpnext/accounts/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 857796b17b..950660f59d 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -282,7 +282,7 @@ def get_balance_on( cond.append("""gle.company = %s """ % (frappe.db.escape(company, percent=False))) if account or (party_type and party) or account_type: - precision = frappe.db.escape(get_currency_precision()) + precision = get_currency_precision() if in_account_currency: select_field = ( "sum(round(debit_in_account_currency, %s)) - sum(round(credit_in_account_currency, %s))" From 6c8f52b26f0601135ec0dd1be94835951936cd11 Mon Sep 17 00:00:00 2001 From: Florian HENRY Date: Thu, 11 Jan 2024 16:00:48 +0100 Subject: [PATCH 04/16] fix: Payment Terms Status for Sales Order report should show all payment terms from order not only this comming from template --- .../payment_terms_status_for_sales_order.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.py b/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.py index 3682c5fd62..c6e4647538 100644 --- a/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.py +++ b/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.py @@ -210,7 +210,6 @@ def get_so_with_invoices(filters): .where( (so.docstatus == 1) & (so.status.isin(["To Deliver and Bill", "To Bill"])) - & (so.payment_terms_template != "NULL") & (so.company == conditions.company) & (so.transaction_date[conditions.start_date : conditions.end_date]) ) From 527cfcd87fcf05f8252cc6932da71f023ac212d4 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 18 Jan 2024 11:58:06 +0530 Subject: [PATCH 05/16] fix: ignore user permissions for company field --- .../doctype/payment_reconciliation/payment_reconciliation.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json index ccb9e648cb..93ed5c8e64 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json @@ -39,6 +39,7 @@ { "fieldname": "company", "fieldtype": "Link", + "ignore_user_permissions": 1, "label": "Company", "options": "Company", "reqd": 1 @@ -215,7 +216,7 @@ "is_virtual": 1, "issingle": 1, "links": [], - "modified": "2023-11-17 17:33:55.701726", + "modified": "2024-01-18 11:56:20.234667", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Reconciliation", From 31592b8f3ac16298d7c5f58c9d83bc764a3ea51b Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 23 Jan 2024 12:42:54 +0530 Subject: [PATCH 06/16] fix: cancellation of asset/asset capitalization --- erpnext/assets/doctype/asset/asset.py | 13 +++++-------- erpnext/assets/doctype/asset/depreciation.py | 2 ++ .../asset_capitalization/asset_capitalization.py | 1 + 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 73572499f2..67018106a7 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -519,14 +519,11 @@ class Asset(AccountsController): movement.cancel() def cancel_capitalization(self): - asset_capitalization = frappe.db.get_value( - "Asset Capitalization", - {"target_asset": self.name, "docstatus": 1, "entry_type": "Capitalization"}, - ) - - if asset_capitalization: - asset_capitalization = frappe.get_doc("Asset Capitalization", asset_capitalization) - asset_capitalization.cancel() + if self.capitalized_in: + self.capitalized_in = None + asset_capitalization = frappe.get_doc("Asset Capitalization", self.capitalized_in) + if asset_capitalization.docstatus == 1: + asset_capitalization.cancel() def delete_depreciation_entries(self): if self.calculate_depreciation: diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index a93af94664..df4593bb69 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -561,6 +561,8 @@ def modify_depreciation_schedule_for_asset_repairs(asset, notes): def reverse_depreciation_entry_made_after_disposal(asset, date): for row in asset.get("finance_books"): asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset.name, "Active", row.finance_book) + if not asset_depr_schedule_doc: + continue for schedule_idx, schedule in enumerate(asset_depr_schedule_doc.get("depreciation_schedule")): if schedule.schedule_date == date: diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py index cad74df51e..5e251a5658 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py @@ -146,6 +146,7 @@ class AssetCapitalization(StockController): def cancel_target_asset(self): if self.entry_type == "Capitalization" and self.target_asset: asset_doc = frappe.get_doc("Asset", self.target_asset) + frappe.db.set_value("Asset", self.target_asset, "capitalized_in", None) if asset_doc.docstatus == 1: asset_doc.cancel() From 1a686cb66d92d595a64ed1dd41a96f579e0c6e8a Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 23 Jan 2024 12:46:15 +0530 Subject: [PATCH 07/16] fix: Use db_set to set a value in on_cancel --- erpnext/assets/doctype/asset/asset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 67018106a7..259857f554 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -520,7 +520,7 @@ class Asset(AccountsController): def cancel_capitalization(self): if self.capitalized_in: - self.capitalized_in = None + self.db_set("capitalized_in", None) asset_capitalization = frappe.get_doc("Asset Capitalization", self.capitalized_in) if asset_capitalization.docstatus == 1: asset_capitalization.cancel() From b127aa308eeda0246cdf885c36f0b36f270e2ae1 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 24 Jan 2024 11:42:37 +0530 Subject: [PATCH 08/16] fix: AttributeError in company transaction deletion --- erpnext/setup/doctype/company/company.py | 2 +- .../test_transaction_deletion_record.py | 21 ++++++++++++++----- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 68a3854b0d..876b6a4ac8 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -908,8 +908,8 @@ def generate_id_for_deletion_job(company): @frappe.whitelist() def is_deletion_job_running(company): job_id = generate_id_for_deletion_job(company) - job_name = get_job(job_id).get_id() # job name will have site prefix if is_job_enqueued(job_id): + job_name = get_job(job_id).get_id() # job name will have site prefix frappe.throw( _("A Transaction Deletion Job: {0} is already running for {1}").format( frappe.bold(get_link_to_form("RQ Job", job_name)), frappe.bold(company) diff --git a/erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py index 319d435ca6..844e7865e3 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py +++ b/erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py @@ -4,9 +4,10 @@ import unittest import frappe +from frappe.tests.utils import FrappeTestCase -class TestTransactionDeletionRecord(unittest.TestCase): +class TestTransactionDeletionRecord(FrappeTestCase): def setUp(self): create_company("Dunder Mifflin Paper Co") @@ -14,7 +15,7 @@ class TestTransactionDeletionRecord(unittest.TestCase): frappe.db.rollback() def test_doctypes_contain_company_field(self): - tdr = create_transaction_deletion_request("Dunder Mifflin Paper Co") + tdr = create_transaction_deletion_doc("Dunder Mifflin Paper Co") for doctype in tdr.doctypes: contains_company = False doctype_fields = frappe.get_meta(doctype.doctype_name).as_dict()["fields"] @@ -27,17 +28,27 @@ class TestTransactionDeletionRecord(unittest.TestCase): def test_no_of_docs_is_correct(self): for i in range(5): create_task("Dunder Mifflin Paper Co") - tdr = create_transaction_deletion_request("Dunder Mifflin Paper Co") + tdr = create_transaction_deletion_doc("Dunder Mifflin Paper Co") for doctype in tdr.doctypes: if doctype.doctype_name == "Task": self.assertEqual(doctype.no_of_docs, 5) def test_deletion_is_successful(self): create_task("Dunder Mifflin Paper Co") - create_transaction_deletion_request("Dunder Mifflin Paper Co") + create_transaction_deletion_doc("Dunder Mifflin Paper Co") tasks_containing_company = frappe.get_all("Task", filters={"company": "Dunder Mifflin Paper Co"}) self.assertEqual(tasks_containing_company, []) + def test_company_transaction_deletion_request(self): + from erpnext.setup.doctype.company.company import create_transaction_deletion_request + + # don't reuse below company for other test cases + company = "Deep Space Exploration" + create_company(company) + + # below call should not raise any exceptions or throw errors + create_transaction_deletion_request(company) + def create_company(company_name): company = frappe.get_doc( @@ -46,7 +57,7 @@ def create_company(company_name): company.insert(ignore_if_duplicate=True) -def create_transaction_deletion_request(company): +def create_transaction_deletion_doc(company): tdr = frappe.get_doc({"doctype": "Transaction Deletion Record", "company": company}) tdr.insert() tdr.submit() From 9fcd89d45693b8a36cde18221d83037c11a70d12 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 24 Jan 2024 13:27:31 +0530 Subject: [PATCH 09/16] refactor: use generic name for advance doctypes variable --- erpnext/accounts/doctype/journal_entry/journal_entry.py | 4 ++-- erpnext/accounts/doctype/payment_entry/payment_entry.py | 8 ++++---- .../accounts/doctype/payment_request/payment_request.py | 8 ++++---- erpnext/accounts/utils.py | 8 ++++---- erpnext/controllers/accounts_controller.py | 8 ++++---- erpnext/hooks.py | 4 ++-- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 7579da86cd..f722c907be 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -187,8 +187,8 @@ class JournalEntry(AccountsController): def update_advance_paid(self): advance_paid = frappe._dict() advance_payment_doctypes = frappe.get_hooks( - "advance_payment_customer_doctypes" - ) + frappe.get_hooks("advance_payment_supplier_doctypes") + "advance_payment_receivable_doctypes" + ) + frappe.get_hooks("advance_payment_payable_doctypes") for d in self.get("accounts"): if d.is_advance: if d.reference_type in advance_payment_doctypes: diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index cfe0d9c9df..a298b89935 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -927,8 +927,8 @@ class PaymentEntry(AccountsController): def calculate_base_allocated_amount_for_reference(self, d) -> float: base_allocated_amount = 0 advance_payment_doctypes = frappe.get_hooks( - "advance_payment_customer_doctypes" - ) + frappe.get_hooks("advance_payment_supplier_doctypes") + "advance_payment_receivable_doctypes" + ) + frappe.get_hooks("advance_payment_payable_doctypes") if d.reference_doctype in advance_payment_doctypes: # When referencing Sales/Purchase Order, use the source/target exchange rate depending on payment type. # This is so there are no Exchange Gain/Loss generated for such doctypes @@ -1428,8 +1428,8 @@ class PaymentEntry(AccountsController): def update_advance_paid(self): if self.payment_type in ("Receive", "Pay") and self.party: advance_payment_doctypes = frappe.get_hooks( - "advance_payment_customer_doctypes" - ) + frappe.get_hooks("advance_payment_supplier_doctypes") + "advance_payment_receivable_doctypes" + ) + frappe.get_hooks("advance_payment_payable_doctypes") for d in self.get("references"): if d.allocated_amount and d.reference_doctype in advance_payment_doctypes: frappe.get_doc( diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index 839348a541..a18104eeb5 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -170,8 +170,8 @@ class PaymentRequest(Document): self.request_phone_payment() advance_payment_doctypes = frappe.get_hooks( - "advance_payment_customer_doctypes" - ) + frappe.get_hooks("advance_payment_supplier_doctypes") + "advance_payment_receivable_doctypes" + ) + frappe.get_hooks("advance_payment_payable_doctypes") if self.reference_doctype in advance_payment_doctypes: # set advance payment status ref_doc.set_total_advance_paid() @@ -216,8 +216,8 @@ class PaymentRequest(Document): ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name) advance_payment_doctypes = frappe.get_hooks( - "advance_payment_customer_doctypes" - ) + frappe.get_hooks("advance_payment_supplier_doctypes") + "advance_payment_receivable_doctypes" + ) + frappe.get_hooks("advance_payment_payable_doctypes") if self.reference_doctype in advance_payment_doctypes: # set advance payment status ref_doc.set_total_advance_paid() diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 65b3abafdb..6b0a5e3891 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -619,8 +619,8 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False): # Update Advance Paid in SO/PO since they might be getting unlinked advance_payment_doctypes = frappe.get_hooks( - "advance_payment_customer_doctypes" - ) + frappe.get_hooks("advance_payment_supplier_doctypes") + "advance_payment_receivable_doctypes" + ) + frappe.get_hooks("advance_payment_payable_doctypes") if jv_detail.get("reference_type") in advance_payment_doctypes: frappe.get_doc(jv_detail.reference_type, jv_detail.reference_name).set_total_advance_paid() @@ -696,8 +696,8 @@ def update_reference_in_payment_entry( # Update Advance Paid in SO/PO since they are getting unlinked advance_payment_doctypes = frappe.get_hooks( - "advance_payment_customer_doctypes" - ) + frappe.get_hooks("advance_payment_supplier_doctypes") + "advance_payment_receivable_doctypes" + ) + frappe.get_hooks("advance_payment_payable_doctypes") if existing_row.get("reference_doctype") in advance_payment_doctypes: frappe.get_doc( existing_row.reference_doctype, existing_row.reference_name diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index afbea61052..e7f0fe8773 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1761,9 +1761,9 @@ class AccountsController(TransactionBase): def set_total_advance_paid(self): ple = frappe.qb.DocType("Payment Ledger Entry") - if self.doctype in frappe.get_hooks("advance_payment_customer_doctypes"): + if self.doctype in frappe.get_hooks("advance_payment_receivable_doctypes"): party = self.customer - if self.doctype in frappe.get_hooks("advance_payment_supplier_doctypes"): + if self.doctype in frappe.get_hooks("advance_payment_payable_doctypes"): party = self.supplier advance = ( frappe.qb.from_(ple) @@ -1829,9 +1829,9 @@ class AccountsController(TransactionBase): "docstatus": 1, }, ) - if self.doctype in frappe.get_hooks("advance_payment_customer_doctypes"): + if self.doctype in frappe.get_hooks("advance_payment_receivable_doctypes"): new_status = "Requested" if prs else "Not Requested" - if self.doctype in frappe.get_hooks("advance_payment_supplier_doctypes"): + if self.doctype in frappe.get_hooks("advance_payment_payable_doctypes"): new_status = "Initiated" if prs else "Not Initiated" if new_status == self.advance_payment_status: diff --git a/erpnext/hooks.py b/erpnext/hooks.py index f33fff0076..14b7656491 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -481,8 +481,8 @@ payment_gateway_enabled = "erpnext.accounts.utils.create_payment_gateway_account communication_doctypes = ["Customer", "Supplier"] -advance_payment_customer_doctypes = ["Sales Order"] -advance_payment_supplier_doctypes = ["Purchase Order"] +advance_payment_receivable_doctypes = ["Sales Order"] +advance_payment_payable_doctypes = ["Purchase Order"] invoice_doctypes = ["Sales Invoice", "Purchase Invoice"] From 64cb1153de2d09883234e4e80e5306de27db328f Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 15 Jan 2024 19:39:41 +0530 Subject: [PATCH 10/16] fix: incorrect active serial nos --- .../js/utils/serial_no_batch_selector.js | 4 + .../serial_and_batch_bundle.py | 16 ++- .../stock_reconciliation.py | 88 ++++++++++-- .../test_stock_reconciliation.py | 68 +++++++++ erpnext/stock/stock_ledger.py | 129 ++++++++++++++---- 5 files changed, 263 insertions(+), 42 deletions(-) diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index 44a4957b41..80ade7086c 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -71,6 +71,10 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { let warehouse = this.item?.type_of_transaction === "Outward" ? (this.item.warehouse || this.item.s_warehouse) : ""; + if (!warehouse && this.frm.doc.doctype === 'Stock Reconciliation') { + warehouse = this.get_warehouse(); + } + return { 'item_code': this.item.item_code, 'warehouse': ["=", warehouse] diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index 63cc938c09..9cad8f62b8 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -250,6 +250,7 @@ class SerialandBatchBundle(Document): for d in self.entries: available_qty = 0 + if self.has_serial_no: d.incoming_rate = abs(sn_obj.serial_no_incoming_rate.get(d.serial_no, 0.0)) else: @@ -892,6 +893,13 @@ class SerialandBatchBundle(Document): elif batch_nos: self.set("entries", batch_nos) + def delete_serial_batch_entries(self): + SBBE = frappe.qb.DocType("Serial and Batch Entry") + + frappe.qb.from_(SBBE).delete().where(SBBE.parent == self.name).run() + + self.set("entries", []) + @frappe.whitelist() def download_blank_csv_template(content): @@ -1374,10 +1382,12 @@ def get_available_serial_nos(kwargs): elif kwargs.based_on == "Expiry": order_by = "amc_expiry_date asc" - filters = {"item_code": kwargs.item_code, "warehouse": ("is", "set")} + filters = {"item_code": kwargs.item_code} - if kwargs.warehouse: - filters["warehouse"] = kwargs.warehouse + if not kwargs.get("ignore_warehouse"): + filters["warehouse"] = ("is", "set") + if kwargs.warehouse: + filters["warehouse"] = kwargs.warehouse # Since SLEs are not present against Reserved Stock [POS invoices, SRE], need to ignore reserved serial nos. ignore_serial_nos = get_reserved_serial_nos(kwargs) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 6819968394..788ae0d3ab 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -156,6 +156,7 @@ class StockReconciliation(StockController): "warehouse": item.warehouse, "posting_date": self.posting_date, "posting_time": self.posting_time, + "ignore_warehouse": 1, } ) ) @@ -780,7 +781,20 @@ class StockReconciliation(StockController): current_qty = 0.0 if row.current_serial_and_batch_bundle: - current_qty = self.get_qty_for_serial_and_batch_bundle(row) + current_qty = self.get_current_qty_for_serial_or_batch(row) + elif row.serial_no: + item_dict = get_stock_balance_for( + row.item_code, + row.warehouse, + self.posting_date, + self.posting_time, + voucher_no=self.name, + ) + + current_qty = item_dict.get("qty") + row.current_serial_no = item_dict.get("serial_nos") + row.current_valuation_rate = item_dict.get("rate") + val_rate = item_dict.get("rate") elif row.batch_no: current_qty = get_batch_qty_for_stock_reco( row.item_code, row.warehouse, row.batch_no, self.posting_date, self.posting_time, self.name @@ -788,15 +802,16 @@ class StockReconciliation(StockController): precesion = row.precision("current_qty") if flt(current_qty, precesion) != flt(row.current_qty, precesion): - val_rate = get_valuation_rate( - row.item_code, - row.warehouse, - self.doctype, - self.name, - company=self.company, - batch_no=row.batch_no, - serial_and_batch_bundle=row.current_serial_and_batch_bundle, - ) + if not row.serial_no: + val_rate = get_valuation_rate( + row.item_code, + row.warehouse, + self.doctype, + self.name, + company=self.company, + batch_no=row.batch_no, + serial_and_batch_bundle=row.current_serial_and_batch_bundle, + ) row.current_valuation_rate = val_rate row.current_qty = current_qty @@ -842,11 +857,56 @@ class StockReconciliation(StockController): return allow_negative_stock - def get_qty_for_serial_and_batch_bundle(self, row): + def get_current_qty_for_serial_or_batch(self, row): doc = frappe.get_doc("Serial and Batch Bundle", row.current_serial_and_batch_bundle) - precision = doc.entries[0].precision("qty") + current_qty = 0.0 + if doc.has_serial_no: + current_qty = self.get_current_qty_for_serial_nos(doc) + elif doc.has_batch_no: + current_qty = self.get_current_qty_for_batch_nos(doc) - current_qty = 0 + return abs(current_qty) + + def get_current_qty_for_serial_nos(self, doc): + serial_nos_details = get_available_serial_nos( + frappe._dict( + { + "item_code": doc.item_code, + "warehouse": doc.warehouse, + "posting_date": self.posting_date, + "posting_time": self.posting_time, + "voucher_no": self.name, + "ignore_warehouse": 1, + } + ) + ) + + if not serial_nos_details: + return 0.0 + + doc.delete_serial_batch_entries() + current_qty = 0.0 + for serial_no_row in serial_nos_details: + current_qty += 1 + doc.append( + "entries", + { + "serial_no": serial_no_row.serial_no, + "qty": -1, + "warehouse": doc.warehouse, + "batch_no": serial_no_row.batch_no, + }, + ) + + doc.set_incoming_rate(save=True) + doc.calculate_qty_and_amount(save=True) + doc.db_update_all() + + return current_qty + + def get_current_qty_for_batch_nos(self, doc): + current_qty = 0.0 + precision = doc.entries[0].precision("qty") for d in doc.entries: qty = ( get_batch_qty( @@ -864,7 +924,7 @@ class StockReconciliation(StockController): current_qty += qty - return abs(current_qty) + return current_qty def get_batch_qty_for_stock_reco( diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index 70e9fb2205..0bbfed40d8 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -925,6 +925,74 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin): self.assertEqual(len(serial_batch_bundle), 0) + def test_backdated_purchase_receipt_with_stock_reco(self): + item_code = self.make_item( + properties={ + "is_stock_item": 1, + "has_serial_no": 1, + "serial_no_series": "TEST-SERIAL-.###", + } + ).name + + warehouse = "_Test Warehouse - _TC" + + # Step - 1: Create a Backdated Purchase Receipt + + pr1 = make_purchase_receipt( + item_code=item_code, warehouse=warehouse, qty=10, rate=100, posting_date=add_days(nowdate(), -3) + ) + pr1.reload() + + serial_nos = sorted(get_serial_nos_from_bundle(pr1.items[0].serial_and_batch_bundle))[:5] + + # Step - 2: Create a Stock Reconciliation + sr1 = create_stock_reconciliation( + item_code=item_code, + warehouse=warehouse, + qty=5, + serial_no=serial_nos, + ) + + data = frappe.get_all( + "Stock Ledger Entry", + fields=["serial_no", "actual_qty", "stock_value_difference"], + filters={"voucher_no": sr1.name, "is_cancelled": 0}, + order_by="creation", + ) + + for d in data: + if d.actual_qty < 0: + self.assertEqual(d.actual_qty, -10.0) + self.assertAlmostEqual(d.stock_value_difference, -1000.0) + else: + self.assertEqual(d.actual_qty, 5.0) + self.assertAlmostEqual(d.stock_value_difference, 500.0) + + # Step - 3: Create a Purchase Receipt before the first Purchase Receipt + make_purchase_receipt( + item_code=item_code, warehouse=warehouse, qty=10, rate=200, posting_date=add_days(nowdate(), -5) + ) + + data = frappe.get_all( + "Stock Ledger Entry", + fields=["serial_no", "actual_qty", "stock_value_difference"], + filters={"voucher_no": sr1.name, "is_cancelled": 0}, + order_by="creation", + ) + + for d in data: + if d.actual_qty < 0: + self.assertEqual(d.actual_qty, -20.0) + self.assertAlmostEqual(d.stock_value_difference, -3000.0) + else: + self.assertEqual(d.actual_qty, 5.0) + self.assertAlmostEqual(d.stock_value_difference, 500.0) + + active_serial_no = frappe.get_all( + "Serial No", filters={"status": "Active", "item_code": item_code} + ) + self.assertEqual(len(active_serial_no), 5) + def create_batch_item_with_batch(item_name, batch_id): batch_item_doc = create_item(item_name, is_stock_item=1) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 0370666263..45764f3ec0 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -9,9 +9,18 @@ from typing import Optional, Set, Tuple import frappe from frappe import _, scrub from frappe.model.meta import get_field_precision -from frappe.query_builder import Case from frappe.query_builder.functions import CombineDatetime, Sum -from frappe.utils import cint, flt, get_link_to_form, getdate, now, nowdate, nowtime, parse_json +from frappe.utils import ( + cint, + cstr, + flt, + get_link_to_form, + getdate, + now, + nowdate, + nowtime, + parse_json, +) import erpnext from erpnext.stock.doctype.bin.bin import update_qty as update_bin_qty @@ -712,11 +721,10 @@ class update_entries_after(object): if ( sle.voucher_type == "Stock Reconciliation" - and ( - sle.batch_no or (sle.has_batch_no and sle.serial_and_batch_bundle and not sle.has_serial_no) - ) + and (sle.batch_no or sle.serial_no or sle.serial_and_batch_bundle) and sle.voucher_detail_no and not self.args.get("sle_id") + and sle.is_cancelled == 0 ): self.reset_actual_qty_for_stock_reco(sle) @@ -737,6 +745,23 @@ class update_entries_after(object): if sle.serial_and_batch_bundle: self.calculate_valuation_for_serial_batch_bundle(sle) + elif sle.serial_no and not self.args.get("sle_id"): + # Only run in reposting + self.get_serialized_values(sle) + self.wh_data.qty_after_transaction += flt(sle.actual_qty) + if sle.voucher_type == "Stock Reconciliation" and not sle.batch_no: + self.wh_data.qty_after_transaction = sle.qty_after_transaction + + self.wh_data.stock_value = flt(self.wh_data.qty_after_transaction) * flt( + self.wh_data.valuation_rate + ) + elif ( + sle.batch_no + and frappe.db.get_value("Batch", sle.batch_no, "use_batchwise_valuation", cache=True) + and not self.args.get("sle_id") + ): + # Only run in reposting + self.update_batched_values(sle) else: if sle.voucher_type == "Stock Reconciliation" and not sle.batch_no and not has_dimensions: # assert @@ -782,6 +807,45 @@ class update_entries_after(object): ): self.update_outgoing_rate_on_transaction(sle) + def get_serialized_values(self, sle): + 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 + incoming_rate = self.wh_data.valuation_rate + + stock_value_change = 0 + if actual_qty > 0: + stock_value_change = actual_qty * incoming_rate + else: + # 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) + stock_value_change = -1 * outgoing_value + else: + stock_value_change = actual_qty * sle.outgoing_rate + + new_stock_qty = self.wh_data.qty_after_transaction + actual_qty + + if new_stock_qty > 0: + new_stock_value = ( + self.wh_data.qty_after_transaction * self.wh_data.valuation_rate + ) + stock_value_change + if new_stock_value >= 0: + # calculate new valuation rate only if stock value is positive + # else it remains the same as that of previous entry + self.wh_data.valuation_rate = new_stock_value / new_stock_qty + + if not self.wh_data.valuation_rate and sle.voucher_detail_no: + allow_zero_rate = self.check_if_allow_zero_valuation_rate( + sle.voucher_type, sle.voucher_detail_no + ) + if not allow_zero_rate: + self.wh_data.valuation_rate = self.get_fallback_rate(sle) + def reset_actual_qty_for_stock_reco(self, sle): doc = frappe.get_cached_doc("Stock Reconciliation", sle.voucher_no) doc.recalculate_current_qty(sle.voucher_detail_no, sle.creation, sle.actual_qty > 0) @@ -795,6 +859,36 @@ class update_entries_after(object): if abs(sle.actual_qty) == 0.0: sle.is_cancelled = 1 + if sle.serial_and_batch_bundle and frappe.get_cached_value( + "Item", sle.item_code, "has_serial_no" + ): + self.update_serial_no_status(sle) + + def update_serial_no_status(self, sle): + from erpnext.stock.serial_batch_bundle import get_serial_nos + + serial_nos = get_serial_nos(sle.serial_and_batch_bundle) + if not serial_nos: + return + + warehouse = None + status = "Inactive" + + if sle.actual_qty > 0: + warehouse = sle.warehouse + status = "Active" + + sn_table = frappe.qb.DocType("Serial No") + + query = ( + frappe.qb.update(sn_table) + .set(sn_table.warehouse, warehouse) + .set(sn_table.status, status) + .where(sn_table.name.isin(serial_nos)) + ) + + query.run() + def calculate_valuation_for_serial_batch_bundle(self, sle): doc = frappe.get_cached_doc("Serial and Batch Bundle", sle.serial_and_batch_bundle) @@ -1171,11 +1265,12 @@ class update_entries_after(object): outgoing_rate = get_batch_incoming_rate( item_code=sle.item_code, warehouse=sle.warehouse, - serial_and_batch_bundle=sle.serial_and_batch_bundle, + batch_no=sle.batch_no, posting_date=sle.posting_date, posting_time=sle.posting_time, creation=sle.creation, ) + 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. @@ -1449,11 +1544,10 @@ def get_sle_by_voucher_detail_no(voucher_detail_no, excluded_sle=None): def get_batch_incoming_rate( - item_code, warehouse, serial_and_batch_bundle, posting_date, posting_time, creation=None + item_code, warehouse, batch_no, posting_date, posting_time, creation=None ): sle = frappe.qb.DocType("Stock Ledger Entry") - batch_ledger = frappe.qb.DocType("Serial and Batch Entry") timestamp_condition = CombineDatetime(sle.posting_date, sle.posting_time) < CombineDatetime( posting_date, posting_time @@ -1464,28 +1558,13 @@ def get_batch_incoming_rate( == CombineDatetime(posting_date, posting_time) ) & (sle.creation < creation) - batches = frappe.get_all( - "Serial and Batch Entry", fields=["batch_no"], filters={"parent": serial_and_batch_bundle} - ) - batch_details = ( frappe.qb.from_(sle) - .inner_join(batch_ledger) - .on(sle.serial_and_batch_bundle == batch_ledger.parent) - .select( - Sum( - Case() - .when(sle.actual_qty > 0, batch_ledger.qty * batch_ledger.incoming_rate) - .else_(batch_ledger.qty * batch_ledger.outgoing_rate * -1) - ).as_("batch_value"), - Sum(Case().when(sle.actual_qty > 0, batch_ledger.qty).else_(batch_ledger.qty * -1)).as_( - "batch_qty" - ), - ) + .select(Sum(sle.stock_value_difference).as_("batch_value"), Sum(sle.actual_qty).as_("batch_qty")) .where( (sle.item_code == item_code) & (sle.warehouse == warehouse) - & (batch_ledger.batch_no.isin([row.batch_no for row in batches])) + & (sle.batch_no == batch_no) & (sle.is_cancelled == 0) ) .where(timestamp_condition) From 7d3240ae3a2e8fc761d460793fbddfb2b5cffc09 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 24 Jan 2024 23:45:06 +0530 Subject: [PATCH 11/16] fix: Item Tax template is not working for e-commerce --- erpnext/controllers/accounts_controller.py | 32 +++++++++++ .../doctype/quotation/test_quotation.py | 55 +++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index afbea61052..7dfcb30ff5 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -22,6 +22,7 @@ from frappe.utils import ( get_link_to_form, getdate, nowdate, + parse_json, today, ) @@ -833,6 +834,37 @@ class AccountsController(TransactionBase): self.extend("taxes", get_taxes_and_charges(tax_master_doctype, self.get("taxes_and_charges"))) + def append_taxes_from_item_tax_template(self): + if not frappe.db.get_single_value("Accounts Settings", "add_taxes_from_item_tax_template"): + return + + for row in self.items: + item_tax_rate = row.get("item_tax_rate") + if not item_tax_rate: + continue + + if isinstance(item_tax_rate, str): + item_tax_rate = parse_json(item_tax_rate) + + for account_head, rate in item_tax_rate.items(): + row = self.get_tax_row(account_head) + + if not row: + self.append( + "taxes", + { + "charge_type": "On Net Total", + "account_head": account_head, + "rate": 0, + "description": account_head, + }, + ) + + def get_tax_row(self, account_head): + for row in self.taxes: + if row.account_head == account_head: + return row + def set_other_charges(self): self.set("taxes", []) self.set_taxes() diff --git a/erpnext/selling/doctype/quotation/test_quotation.py b/erpnext/selling/doctype/quotation/test_quotation.py index 2a4855e318..86c7a72f73 100644 --- a/erpnext/selling/doctype/quotation/test_quotation.py +++ b/erpnext/selling/doctype/quotation/test_quotation.py @@ -609,6 +609,61 @@ class TestQuotation(FrappeTestCase): quotation.items[0].conversion_factor = 2.23 self.assertRaises(frappe.ValidationError, quotation.save) + def test_item_tax_template_for_quotation(self): + from erpnext.stock.doctype.item.test_item import make_item + + if not frappe.db.exists("Account", {"account_name": "_Test Vat", "company": "_Test Company"}): + frappe.get_doc( + { + "doctype": "Account", + "account_name": "_Test Vat", + "company": "_Test Company", + "account_type": "Tax", + "root_type": "Asset", + "is_group": 0, + "parent_account": "Tax Assets - _TC", + "tax_rate": 10, + } + ).insert() + + if not frappe.db.exists("Item Tax Template", "Vat Template - _TC"): + doc = frappe.get_doc( + { + "doctype": "Item Tax Template", + "name": "Vat Template", + "title": "Vat Template", + "company": "_Test Company", + "taxes": [ + { + "tax_type": "_Test Vat - _TC", + "tax_rate": 5, + } + ], + } + ).insert() + + item_doc = make_item("_Test Item Tax Template QTN", {"is_stock_item": 1}) + if not frappe.db.exists( + "Item Tax", {"parent": item_doc.name, "item_tax_template": "Vat Template - _TC"} + ): + item_doc.append("taxes", {"item_tax_template": "Vat Template - _TC"}) + item_doc.save() + + quotation = make_quotation( + item_code="_Test Item Tax Template QTN", qty=1, rate=100, do_not_submit=1 + ) + self.assertFalse(quotation.taxes) + + quotation.append_taxes_from_item_tax_template() + quotation.save() + self.assertTrue(quotation.taxes) + for row in quotation.taxes: + self.assertEqual(row.account_head, "_Test Vat - _TC") + self.assertAlmostEqual(row.base_tax_amount, quotation.total * 5 / 100) + + item_doc.taxes = [] + item_doc.save() + test_records = frappe.get_test_records("Quotation") From d1fb90edffbbbd48722fddda1ed3d9144783b774 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Thu, 25 Jan 2024 12:42:16 +0530 Subject: [PATCH 12/16] fix: default enable closing stock balance (#39551) --- erpnext/stock/report/stock_balance/stock_balance.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/report/stock_balance/stock_balance.js b/erpnext/stock/report/stock_balance/stock_balance.js index 6de5f00ece..fe6e83edda 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.js +++ b/erpnext/stock/report/stock_balance/stock_balance.js @@ -99,7 +99,7 @@ frappe.query_reports["Stock Balance"] = { "fieldname": 'ignore_closing_balance', "label": __('Ignore Closing Balance'), "fieldtype": 'Check', - "default": 1 + "default": 0 }, ], From dfda5ad67320c000afdbcc7facde03a7cf695f6f Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 25 Jan 2024 14:18:27 +0530 Subject: [PATCH 13/16] ci: Add fake passing tests when CI is skipped (#39555) --- .github/workflows/patch_faux.yml | 22 +++++++++++++++++ .../workflows/server-tests-mariadb-faux.yml | 24 +++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 .github/workflows/patch_faux.yml create mode 100644 .github/workflows/server-tests-mariadb-faux.yml diff --git a/.github/workflows/patch_faux.yml b/.github/workflows/patch_faux.yml new file mode 100644 index 0000000000..93d88bdd99 --- /dev/null +++ b/.github/workflows/patch_faux.yml @@ -0,0 +1,22 @@ +# Tests are skipped for these files but github doesn't allow "passing" hence this is required. + +name: Skipped Patch Test + +on: + pull_request: + paths: + - "**.js" + - "**.css" + - "**.md" + - "**.html" + - "**.csv" + +jobs: + test: + runs-on: ubuntu-latest + + name: Patch Test + + steps: + - name: Pass skipped tests unconditionally + run: "echo Skipped" diff --git a/.github/workflows/server-tests-mariadb-faux.yml b/.github/workflows/server-tests-mariadb-faux.yml new file mode 100644 index 0000000000..8334661cb0 --- /dev/null +++ b/.github/workflows/server-tests-mariadb-faux.yml @@ -0,0 +1,24 @@ +# Tests are skipped for these files but github doesn't allow "passing" hence this is required. + +name: Skipped Tests + +on: + pull_request: + paths: + - "**.js" + - "**.css" + - "**.md" + - "**.html" + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + container: [1, 2, 3, 4] + + name: Python Unit Tests + + steps: + - name: Pass skipped tests unconditionally + run: "echo Skipped" From 7f8303a493c15a0e32f0b13e464a411c5c4c283b Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 25 Jan 2024 14:26:01 +0530 Subject: [PATCH 14/16] fix: make SO item code reqd --- erpnext/selling/doctype/sales_order_item/sales_order_item.json | 3 ++- erpnext/selling/doctype/sales_order_item/sales_order_item.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/selling/doctype/sales_order_item/sales_order_item.json b/erpnext/selling/doctype/sales_order_item/sales_order_item.json index 87aeeac368..9599980418 100644 --- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json +++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json @@ -118,6 +118,7 @@ "oldfieldtype": "Link", "options": "Item", "print_width": "150px", + "reqd": 1, "width": "150px" }, { @@ -908,7 +909,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2023-11-24 13:24:55.756320", + "modified": "2024-01-25 14:24:00.330219", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order Item", diff --git a/erpnext/selling/doctype/sales_order_item/sales_order_item.py b/erpnext/selling/doctype/sales_order_item/sales_order_item.py index 25f5b4b0e7..fa7b9b968f 100644 --- a/erpnext/selling/doctype/sales_order_item/sales_order_item.py +++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.py @@ -43,7 +43,8 @@ class SalesOrderItem(Document): gross_profit: DF.Currency image: DF.Attach | None is_free_item: DF.Check - item_code: DF.Link | None + is_stock_item: DF.Check + item_code: DF.Link item_group: DF.Link | None item_name: DF.Data item_tax_rate: DF.Code | None From 06f48c678be51c85a98bd0c6175fa210a47788f0 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 22 Jan 2024 16:58:31 +0530 Subject: [PATCH 15/16] fix: fetch correct quantity and amount for grouped asset --- erpnext/assets/doctype/asset/asset.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index 02e7a9bb29..673fe549ee 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -571,16 +571,16 @@ frappe.ui.form.on('Asset', { indicator: 'red' }); } - var is_grouped_asset = frappe.db.get_value('Item', item.item_code, 'is_grouped_asset'); - var asset_quantity = is_grouped_asset ? item.qty : 1; - var purchase_amount = flt(item.valuation_rate * asset_quantity, precision('gross_purchase_amount')); - - frm.set_value('gross_purchase_amount', purchase_amount); - frm.set_value('purchase_receipt_amount', purchase_amount); - frm.set_value('asset_quantity', asset_quantity); - frm.set_value('cost_center', item.cost_center || purchase_doc.cost_center); - if(item.asset_location) { frm.set_value('location', item.asset_location); } + frappe.db.get_value('Item', item.item_code, 'is_grouped_asset', (r) => { + var asset_quantity = r.is_grouped_asset ? item.qty : 1; + var purchase_amount = flt(item.valuation_rate * asset_quantity, precision('gross_purchase_amount')); + frm.set_value('gross_purchase_amount', purchase_amount); + frm.set_value('purchase_receipt_amount', purchase_amount); + frm.set_value('asset_quantity', asset_quantity); + frm.set_value('cost_center', item.cost_center || purchase_doc.cost_center); + if(item.asset_location) { frm.set_value('location', item.asset_location); } + }); }, set_depreciation_rate: function(frm, row) { From 2bdfdeeb9a5f2fd98cf67fc983a920790e56e1e1 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Thu, 25 Jan 2024 16:49:12 +0530 Subject: [PATCH 16/16] fix: incorrect amount in the material request item (#39567) fix: incoorect amount in the material request --- erpnext/stock/doctype/material_request/material_request.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js index d90b71a47a..03fe20b93a 100644 --- a/erpnext/stock/doctype/material_request/material_request.js +++ b/erpnext/stock/doctype/material_request/material_request.js @@ -514,6 +514,13 @@ erpnext.buying.MaterialRequestController = class MaterialRequestController exten schedule_date() { set_schedule_date(this.frm); } + + qty(doc, cdt, cdn) { + var row = frappe.get_doc(cdt, cdn); + row.amount = flt(row.qty) * flt(row.rate); + frappe.model.set_value(cdt, cdn, "amount", row.amount); + refresh_field("amount", row.name, row.parentfield); + } }; // for backward compatibility: combine new and previous states