From 6644311c8b4c2398729e6b07ef5845f7fed6795e Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 3 Jul 2023 14:30:15 +0530 Subject: [PATCH 01/14] refactor: checkbox for enabling auto ERR creation --- erpnext/setup/doctype/company/company.json | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json index 6292ad7349d..7e55ca4fc0c 100644 --- a/erpnext/setup/doctype/company/company.json +++ b/erpnext/setup/doctype/company/company.json @@ -95,6 +95,9 @@ "depreciation_cost_center", "capital_work_in_progress_account", "asset_received_but_not_billed", + "exchange_rate_revaluation_settings_section", + "auto_exchange_rate_revaluation", + "auto_err_frequency", "budget_detail", "exception_budget_approver_role", "registration_info", @@ -731,6 +734,23 @@ "fieldname": "book_advance_payments_in_separate_party_account", "fieldtype": "Check", "label": "Book Advance Payments in Separate Party Account" + }, + { + "fieldname": "exchange_rate_revaluation_settings_section", + "fieldtype": "Section Break", + "label": "Exchange Rate Revaluation Settings" + }, + { + "default": "0", + "fieldname": "auto_exchange_rate_revaluation", + "fieldtype": "Check", + "label": "Auto Create Exchange Rate Revaluation" + }, + { + "fieldname": "auto_err_frequency", + "fieldtype": "Select", + "label": "Frequency", + "options": "Daily\nWeekly" } ], "icon": "fa fa-building", @@ -738,7 +758,7 @@ "image_field": "company_logo", "is_tree": 1, "links": [], - "modified": "2023-06-23 18:22:27.219706", + "modified": "2023-07-03 14:29:56.262188", "modified_by": "Administrator", "module": "Setup", "name": "Company", From 4f51c5a433ca8cf3fd688e262e100f63c98b65f9 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 3 Jul 2023 14:35:33 +0530 Subject: [PATCH 02/14] refactor: submit and make JV through background job --- .../exchange_rate_revaluation.py | 6 +++ erpnext/accounts/utils.py | 44 +++++++++++++++++++ erpnext/hooks.py | 4 ++ erpnext/setup/doctype/company/company.json | 9 +++- 4 files changed, 62 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py index 598db642f33..c52ea24f25a 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py +++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py @@ -93,6 +93,12 @@ class ExchangeRateRevaluation(Document): return True + def fetch_and_calculate_accounts_data(self): + accounts = self.get_accounts_data() + if accounts: + for acc in accounts: + self.append("accounts", acc) + @frappe.whitelist() def get_accounts_data(self): self.validate_mandatory() diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 9000b0d32ed..31473db675f 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -1408,6 +1408,50 @@ def check_and_delete_linked_reports(report): frappe.delete_doc("Desktop Icon", icon) +def create_err_and_its_journals(companies: list = None) -> None: + if companies: + for company in companies: + err = frappe.new_doc("Exchange Rate Revaluation") + err.company = company.name + err.posting_date = nowdate() + err.rounding_loss_allowance = 0.0 + + err.fetch_and_calculate_accounts_data() + if err.accounts: + err.save().submit() + response = err.make_jv_entries() + + if company.submit_err_jv: + jv = response.get("revaluation_jv", None) + jv and frappe.get_doc("Journal Entry", jv).submit() + jv = response.get("zero_balance_jv", None) + jv and frappe.get_doc("Journal Entry", jv).submit() + + +def auto_create_exchange_rate_revaluation_daily() -> None: + """ + Executed by background job + """ + companies = frappe.db.get_all( + "Company", + filters={"auto_exchange_rate_revaluation": 1, "auto_err_frequency": "Daily"}, + fields=["name", "submit_err_jv"], + ) + create_err_and_its_journals(companies) + + +def auto_create_exchange_rate_revaluation_weekly() -> None: + """ + Executed by background job + """ + companies = frappe.db.get_all( + "Company", + filters={"auto_exchange_rate_revaluation": 1, "auto_err_frequency": "Weekly"}, + fields=["name", "submit_err_jv"], + ) + create_err_and_its_journals(companies) + + def get_payment_ledger_entries(gl_entries, cancel=0): ple_map = [] if gl_entries: diff --git a/erpnext/hooks.py b/erpnext/hooks.py index a6d939e74f8..d02d318b2d8 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -415,6 +415,10 @@ scheduler_events = { "erpnext.selling.doctype.quotation.quotation.set_expired_status", "erpnext.buying.doctype.supplier_quotation.supplier_quotation.set_expired_status", "erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.send_auto_email", + "erpnext.accounts.utils.auto_create_exchange_rate_revaluation_daily", + ], + "weekly": [ + "erpnext.accounts.utils.auto_create_exchange_rate_revaluation_weekly", ], "daily_long": [ "erpnext.setup.doctype.email_digest.email_digest.send", diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json index 7e55ca4fc0c..ed2852e87a1 100644 --- a/erpnext/setup/doctype/company/company.json +++ b/erpnext/setup/doctype/company/company.json @@ -98,6 +98,7 @@ "exchange_rate_revaluation_settings_section", "auto_exchange_rate_revaluation", "auto_err_frequency", + "submit_err_jv", "budget_detail", "exception_budget_approver_role", "registration_info", @@ -751,6 +752,12 @@ "fieldtype": "Select", "label": "Frequency", "options": "Daily\nWeekly" + }, + { + "default": "0", + "fieldname": "submit_err_jv", + "fieldtype": "Check", + "label": "Submit ERR Journals?" } ], "icon": "fa fa-building", @@ -758,7 +765,7 @@ "image_field": "company_logo", "is_tree": 1, "links": [], - "modified": "2023-07-03 14:29:56.262188", + "modified": "2023-07-07 05:41:41.537256", "modified_by": "Administrator", "module": "Setup", "name": "Company", From 407642869a7de8f3e77b0aba89f546e3cf3afb5b Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 10 Jul 2023 12:48:47 +0530 Subject: [PATCH 03/14] ci: auto release beta version [skip ci] --- .github/workflows/initiate_release.yml | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/.github/workflows/initiate_release.yml b/.github/workflows/initiate_release.yml index ef38974ae27..ee60bad1049 100644 --- a/.github/workflows/initiate_release.yml +++ b/.github/workflows/initiate_release.yml @@ -9,7 +9,7 @@ on: workflow_dispatch: jobs: - release: + stable-release: name: Release runs-on: ubuntu-latest strategy: @@ -30,3 +30,23 @@ jobs: head: version-${{ matrix.version }}-hotfix env: GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} + + beta-release: + name: Release + runs-on: ubuntu-latest + strategy: + fail-fast: false + + steps: + - uses: octokit/request-action@v2.x + with: + route: POST /repos/{owner}/{repo}/pulls + owner: frappe + repo: erpnext + title: |- + "chore: release v15 beta" + body: "Automated beta release." + base: version-15-beta + head: develop + env: + GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} From aeaf8fd89c362423e3fc6e9c5a80da5c7709d382 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 10 Jul 2023 13:03:10 +0530 Subject: [PATCH 04/14] fix: incorrect stock levels in the Batch --- erpnext/stock/doctype/batch/batch.js | 2 - erpnext/stock/doctype/batch/test_batch.py | 67 +++++++++++++++++++ .../serial_and_batch_bundle.py | 31 ++++++--- 3 files changed, 88 insertions(+), 12 deletions(-) diff --git a/erpnext/stock/doctype/batch/batch.js b/erpnext/stock/doctype/batch/batch.js index fa8b2bee558..3b07e4e80c1 100644 --- a/erpnext/stock/doctype/batch/batch.js +++ b/erpnext/stock/doctype/batch/batch.js @@ -47,8 +47,6 @@ frappe.ui.form.on('Batch', { return; } - debugger - const section = frm.dashboard.add_section('', __("Stock Levels")); // sort by qty diff --git a/erpnext/stock/doctype/batch/test_batch.py b/erpnext/stock/doctype/batch/test_batch.py index 0e4132db8ed..7fb672c0cb1 100644 --- a/erpnext/stock/doctype/batch/test_batch.py +++ b/erpnext/stock/doctype/batch/test_batch.py @@ -59,6 +59,73 @@ class TestBatch(FrappeTestCase): return receipt + def test_batch_stock_levels(self, batch_qty=100): + """Test automated batch creation from Purchase Receipt""" + self.make_batch_item("ITEM-BATCH-1") + + receipt = frappe.get_doc( + dict( + doctype="Purchase Receipt", + supplier="_Test Supplier", + company="_Test Company", + items=[dict(item_code="ITEM-BATCH-1", qty=10, rate=10, warehouse="Stores - _TC")], + ) + ).insert() + receipt.submit() + + receipt.load_from_db() + batch_no = get_batch_from_bundle(receipt.items[0].serial_and_batch_bundle) + + bundle_id = ( + SerialBatchCreation( + { + "item_code": "ITEM-BATCH-1", + "warehouse": "_Test Warehouse - _TC", + "actual_qty": 20, + "voucher_type": "Purchase Receipt", + "batches": frappe._dict({batch_no: 20}), + "type_of_transaction": "Inward", + "company": receipt.company, + } + ) + .make_serial_and_batch_bundle() + .name + ) + + receipt2 = frappe.get_doc( + dict( + doctype="Purchase Receipt", + supplier="_Test Supplier", + company="_Test Company", + items=[ + dict( + item_code="ITEM-BATCH-1", + qty=20, + rate=10, + warehouse="_Test Warehouse - _TC", + serial_and_batch_bundle=bundle_id, + ) + ], + ) + ).insert() + receipt2.submit() + + receipt.load_from_db() + receipt2.load_from_db() + + self.assertTrue(receipt.items[0].serial_and_batch_bundle) + self.assertTrue(receipt2.items[0].serial_and_batch_bundle) + + batchwise_qty = frappe._dict({}) + for receipt in [receipt, receipt2]: + batch_no = get_batch_from_bundle(receipt.items[0].serial_and_batch_bundle) + key = (batch_no, receipt.items[0].warehouse) + batchwise_qty[key] = receipt.items[0].qty + + batches = get_batch_qty(batch_no) + for d in batches: + self.assertEqual(d.qty, batchwise_qty[(d.batch_no, d.warehouse)]) + def test_stock_entry_incoming(self): """Test batch creation via Stock Entry (Work Order)""" 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 75b6ec7ef8f..0c6d33bae21 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 @@ -1272,24 +1272,29 @@ def get_reserved_batches_for_pos(kwargs): if ids: for d in get_serial_batch_ledgers(kwargs.item_code, docstatus=1, name=ids): - if d.batch_no not in pos_batches: - pos_batches[d.batch_no] = frappe._dict( + key = (d.batch_no, d.warehouse) + if key not in pos_batches: + pos_batches[key] = frappe._dict( { "qty": d.qty, "warehouse": d.warehouse, } ) else: - pos_batches[d.batch_no].qty += d.qty + pos_batches[key].qty += d.qty for row in pos_invoices: if not row.batch_no: continue - if row.batch_no in pos_batches: - pos_batches[row.batch_no] -= row.qty * -1 if row.is_return else row.qty + if kwargs.get("batch_no") and row.batch_no != kwargs.get("batch_no"): + continue + + key = (row.batch_no, row.warehouse) + if key in pos_batches: + pos_batches[key] -= row.qty * -1 if row.is_return else row.qty else: - pos_batches[row.batch_no] = frappe._dict( + pos_batches[key] = frappe._dict( { "qty": (row.qty * -1 if row.is_return else row.qty), "warehouse": row.warehouse, @@ -1309,6 +1314,7 @@ def get_auto_batch_nos(kwargs): update_available_batches(available_batches, stock_ledgers_batches, pos_invoice_batches) available_batches = list(filter(lambda x: x.qty > 0, available_batches)) + if not qty: return available_batches @@ -1351,10 +1357,11 @@ def get_qty_based_available_batches(available_batches, qty): def update_available_batches(available_batches, reserved_batches=None, pos_invoice_batches=None): for batches in [reserved_batches, pos_invoice_batches]: if batches: - for batch_no, data in batches.items(): + for key, data in batches.items(): + batch_no, warehouse = key batch_not_exists = True for batch in available_batches: - if batch.batch_no == batch_no and batch.warehouse == data.warehouse: + if batch.batch_no == batch_no and batch.warehouse == warehouse: batch.qty += data.qty batch_not_exists = False @@ -1563,7 +1570,7 @@ def get_stock_ledgers_batches(kwargs): .groupby(stock_ledger_entry.batch_no, stock_ledger_entry.warehouse) ) - for field in ["warehouse", "item_code"]: + for field in ["warehouse", "item_code", "batch_no"]: if not kwargs.get(field): continue @@ -1582,6 +1589,10 @@ def get_stock_ledgers_batches(kwargs): data = query.run(as_dict=True) batches = {} for d in data: - batches[d.batch_no] = d + key = (d.batch_no, d.warehouse) + if key not in batches: + batches[key] = d + else: + batches[key].qty += d.qty return batches From d618aaef3240d5051cacdae72f35d07b9b02dc06 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 4 Jul 2023 17:26:48 +0530 Subject: [PATCH 05/14] fix: accepted warehouse and rejected warehouse can't be same --- .../purchase_invoice/purchase_invoice.json | 3 ++- .../purchase_invoice_item.json | 3 ++- erpnext/controllers/buying_controller.py | 17 +++++++++++------ .../purchase_receipt/purchase_receipt.json | 3 ++- .../purchase_receipt/test_purchase_receipt.py | 9 +++++++++ .../purchase_receipt_item.json | 3 ++- .../subcontracting_receipt.json | 3 ++- .../subcontracting_receipt.py | 18 ++++++++++++++---- .../subcontracting_receipt_item.json | 3 ++- 9 files changed, 46 insertions(+), 16 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index e247e802536..d8759e95b87 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -549,6 +549,7 @@ "depends_on": "update_stock", "fieldname": "rejected_warehouse", "fieldtype": "Link", + "ignore_user_permissions": 1, "label": "Rejected Warehouse", "no_copy": 1, "options": "Warehouse", @@ -1576,7 +1577,7 @@ "idx": 204, "is_submittable": 1, "links": [], - "modified": "2023-06-03 16:21:54.637245", + "modified": "2023-07-04 17:22:59.145031", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json index c5187a2f469..4afc4512ff7 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -423,6 +423,7 @@ { "fieldname": "rejected_warehouse", "fieldtype": "Link", + "ignore_user_permissions": 1, "label": "Rejected Warehouse", "options": "Warehouse" }, @@ -904,7 +905,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2023-07-02 18:39:41.495723", + "modified": "2023-07-04 17:22:21.501152", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Item", diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index fec494a84c7..7b7c53ecfe1 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -437,18 +437,23 @@ class BuyingController(SubcontractingController): # validate rate with ref PR def validate_rejected_warehouse(self): - for d in self.get("items"): - if flt(d.rejected_qty) and not d.rejected_warehouse: + for item in self.get("items"): + if flt(item.rejected_qty) and not item.rejected_warehouse: if self.rejected_warehouse: - d.rejected_warehouse = self.rejected_warehouse + item.rejected_warehouse = self.rejected_warehouse - if not d.rejected_warehouse: + if not item.rejected_warehouse: frappe.throw( - _("Row #{0}: Rejected Warehouse is mandatory against rejected Item {1}").format( - d.idx, d.item_code + _("Row #{0}: Rejected Warehouse is mandatory for the rejected Item {1}").format( + item.idx, item.item_code ) ) + if item.get("rejected_warehouse") and (item.get("rejected_warehouse") == item.get("warehouse")): + frappe.throw( + _("Row #{0}: Accepted Warehouse and Rejected Warehouse cannot be same").format(item.idx) + ) + # validate accepted and rejected qty def validate_accepted_rejected_qty(self): for d in self.get("items"): diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json index b41e971c8ac..912b9086dd6 100755 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json @@ -438,6 +438,7 @@ { "fieldname": "rejected_warehouse", "fieldtype": "Link", + "ignore_user_permissions": 1, "label": "Rejected Warehouse", "no_copy": 1, "oldfieldname": "rejected_warehouse", @@ -1240,7 +1241,7 @@ "idx": 261, "is_submittable": 1, "links": [], - "modified": "2023-06-03 16:23:20.781368", + "modified": "2023-07-04 17:23:17.025390", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt", diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 07d6e86795a..ced894634f6 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -350,6 +350,15 @@ class TestPurchaseReceipt(FrappeTestCase): pr.cancel() self.assertFalse(frappe.db.get_value("Serial No", pr_row_1_serial_no, "warehouse")) + def test_rejected_warehouse_filter(self): + pr = frappe.copy_doc(test_records[0]) + pr.get("items")[0].item_code = "_Test Serialized Item With Series" + pr.get("items")[0].qty = 3 + pr.get("items")[0].rejected_qty = 2 + pr.get("items")[0].received_qty = 5 + pr.get("items")[0].rejected_warehouse = pr.get("items")[0].warehouse + self.assertRaises(frappe.ValidationError, pr.save) + def test_rejected_serial_no(self): pr = frappe.copy_doc(test_records[0]) pr.get("items")[0].item_code = "_Test Serialized Item With Series" diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json index 3929616f7cd..bc5e8a0f37d 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -502,6 +502,7 @@ { "fieldname": "rejected_warehouse", "fieldtype": "Link", + "ignore_user_permissions": 1, "label": "Rejected Warehouse", "no_copy": 1, "oldfieldname": "rejected_warehouse", @@ -1058,7 +1059,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2023-07-02 18:40:48.152637", + "modified": "2023-07-04 17:22:02.830029", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt Item", diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json index 9dee3aae466..4b3cc8365c5 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json @@ -251,6 +251,7 @@ "description": "Sets 'Rejected Warehouse' in each row of the Items table.", "fieldname": "rejected_warehouse", "fieldtype": "Link", + "ignore_user_permissions": 1, "label": "Rejected Warehouse", "no_copy": 1, "options": "Warehouse", @@ -630,7 +631,7 @@ "in_create": 1, "is_submittable": 1, "links": [], - "modified": "2023-06-03 16:18:39.088518", + "modified": "2023-07-06 18:43:16.171842", "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Receipt", diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py index 4af38e516f1..60746d95f39 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -192,13 +192,23 @@ class SubcontractingReceipt(SubcontractingController): self.total = total_amount def validate_rejected_warehouse(self): - if not self.rejected_warehouse: - for item in self.items: - if item.rejected_qty: + for item in self.items: + if flt(item.rejected_qty) and not item.rejected_warehouse: + if self.rejected_warehouse: + item.rejected_warehouse = self.rejected_warehouse + + if not item.rejected_warehouse: frappe.throw( - _("Rejected Warehouse is mandatory against rejected Item {0}").format(item.item_code) + _("Row #{0}: Rejected Warehouse is mandatory for the rejected Item {1}").format( + item.idx, item.item_code + ) ) + if item.get("rejected_warehouse") and (item.get("rejected_warehouse") == item.get("warehouse")): + frappe.throw( + _("Row #{0}: Accepted Warehouse and Rejected Warehouse cannot be same").format(item.idx) + ) + def validate_available_qty_for_consumption(self): for item in self.get("supplied_items"): precision = item.precision("consumed_qty") diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json index d550b758390..d72878061c1 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json +++ b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json @@ -254,6 +254,7 @@ "depends_on": "eval: !parent.is_return", "fieldname": "rejected_warehouse", "fieldtype": "Link", + "ignore_user_permissions": 1, "label": "Rejected Warehouse", "no_copy": 1, "options": "Warehouse", @@ -494,7 +495,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2023-03-12 14:00:41.418681", + "modified": "2023-07-06 18:43:45.599761", "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Receipt Item", From b3a99e38cc1cd7319ab91a860e83d10dbc91164d Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 10 Jul 2023 14:31:19 +0530 Subject: [PATCH 06/14] chore: add asset depr posting error in error log (backport #36052) (#36055) chore: add asset depr posting error in error log (#36052) (cherry picked from commit 0f9a6ee70acc88a1190fc66fb95a3bd5d6dea02f) Co-authored-by: Anand Baburajan --- erpnext/assets/doctype/asset/depreciation.py | 29 ++++++++++++-------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index 259568a24b1..e1431eae176 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -40,6 +40,7 @@ def post_depreciation_entries(date=None): date = today() failed_asset_names = [] + error_log_names = [] for asset_name in get_depreciable_assets(date): asset_doc = frappe.get_doc("Asset", asset_name) @@ -50,10 +51,12 @@ def post_depreciation_entries(date=None): except Exception as e: frappe.db.rollback() failed_asset_names.append(asset_name) + error_log = frappe.log_error(e) + error_log_names.append(error_log.name) if failed_asset_names: set_depr_entry_posting_status_for_failed_assets(failed_asset_names) - notify_depr_entry_posting_error(failed_asset_names) + notify_depr_entry_posting_error(failed_asset_names, error_log_names) frappe.db.commit() @@ -239,7 +242,7 @@ def set_depr_entry_posting_status_for_failed_assets(failed_asset_names): frappe.db.set_value("Asset", asset_name, "depr_entry_posting_status", "Failed") -def notify_depr_entry_posting_error(failed_asset_names): +def notify_depr_entry_posting_error(failed_asset_names, error_log_names): recipients = get_users_with_role("Accounts Manager") if not recipients: @@ -247,7 +250,8 @@ def notify_depr_entry_posting_error(failed_asset_names): subject = _("Error while posting depreciation entries") - asset_links = get_comma_separated_asset_links(failed_asset_names) + asset_links = get_comma_separated_links(failed_asset_names, "Asset") + error_log_links = get_comma_separated_links(error_log_names, "Error Log") message = ( _("Hello,") @@ -257,23 +261,26 @@ def notify_depr_entry_posting_error(failed_asset_names): ) + "." + "

" - + _( - "Please raise a support ticket and share this email, or forward this email to your development team so that they can find the issue in the developer console by manually creating the depreciation entry via the asset's depreciation schedule table." + + _("Here are the error logs for the aforementioned failed depreciation entries: {0}").format( + error_log_links ) + + "." + + "

" + + _("Please share this email with your support team so that they can find and fix the issue.") ) frappe.sendmail(recipients=recipients, subject=subject, message=message) -def get_comma_separated_asset_links(asset_names): - asset_links = [] +def get_comma_separated_links(names, doctype): + links = [] - for asset_name in asset_names: - asset_links.append(get_link_to_form("Asset", asset_name)) + for name in names: + links.append(get_link_to_form(doctype, name)) - asset_links = ", ".join(asset_links) + links = ", ".join(links) - return asset_links + return links @frappe.whitelist() From 5c820ecc20f261b26d02b94e3778c331bb7ec0be Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Mon, 10 Jul 2023 16:20:45 +0530 Subject: [PATCH 07/14] fix: precision causing outstanding issue on partly paid invoices (#36030) * fix: precision causing outstanding issue on partly paid invoices * chore: linters --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 65ed4669b1e..699447bb7fc 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -237,10 +237,9 @@ class PaymentEntry(AccountsController): _("{0} {1} has already been fully paid.").format(_(d.reference_doctype), d.reference_name) ) # The reference has already been partly paid - elif ( - latest.outstanding_amount < latest.invoice_amount - and flt(d.outstanding_amount, d.precision("outstanding_amount")) != latest.outstanding_amount - ): + elif latest.outstanding_amount < latest.invoice_amount and flt( + d.outstanding_amount, d.precision("outstanding_amount") + ) != flt(latest.outstanding_amount, d.precision("outstanding_amount")): frappe.throw( _( "{0} {1} has already been partly paid. Please use the 'Get Outstanding Invoice' or the 'Get Outstanding Orders' button to get the latest outstanding amounts." From 6a10ae662ce102dd4014a85a2dd6a51a87692652 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 10 Jul 2023 17:06:02 +0530 Subject: [PATCH 08/14] fix: Delivery Note return valuation --- .../controllers/sales_and_purchase_return.py | 6 +++- .../delivery_note/test_delivery_note.py | 31 +++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index 954668055e1..173e812dbd0 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -669,7 +669,11 @@ def get_filters( if reference_voucher_detail_no: filters["voucher_detail_no"] = reference_voucher_detail_no - if item_row and item_row.get("warehouse"): + if ( + voucher_type in ["Purchase Receipt", "Purchase Invoice"] + and item_row + and item_row.get("warehouse") + ): filters["warehouse"] = item_row.get("warehouse") return filters diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index 8baae8a19c6..0ef3027bce3 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -318,6 +318,37 @@ class TestDeliveryNote(FrappeTestCase): self.assertEqual(dn.per_returned, 100) self.assertEqual(dn.status, "Return Issued") + def test_delivery_note_return_valuation_on_different_warehuose(self): + from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse + + company = frappe.db.get_value("Warehouse", "Stores - TCP1", "company") + item_code = "Test Return Valuation For DN" + make_item("Test Return Valuation For DN", {"is_stock_item": 1}) + return_warehouse = create_warehouse("Returned Test Warehouse", company=company) + + make_stock_entry(item_code=item_code, target="Stores - TCP1", qty=5, basic_rate=150) + + dn = create_delivery_note( + item_code=item_code, + qty=5, + rate=500, + warehouse="Stores - TCP1", + company=company, + expense_account="Cost of Goods Sold - TCP1", + cost_center="Main - TCP1", + ) + + dn.submit() + self.assertEqual(dn.items[0].incoming_rate, 150) + + from erpnext.controllers.sales_and_purchase_return import make_return_doc + + return_dn = make_return_doc(dn.doctype, dn.name) + return_dn.items[0].warehouse = return_warehouse + return_dn.save().submit() + + self.assertEqual(return_dn.items[0].incoming_rate, 150) + def test_return_single_item_from_bundled_items(self): company = frappe.db.get_value("Warehouse", "Stores - TCP1", "company") From 49c61e7ebb40c5e812600757584d908313fdf5a8 Mon Sep 17 00:00:00 2001 From: Wolfram Schmidt Date: Mon, 10 Jul 2023 14:33:50 +0200 Subject: [PATCH 09/14] fix: Add company filter in list view (#36047) fix: Add company filter in list view --- .../doctype/item_tax_template/item_tax_template.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/item_tax_template/item_tax_template.json b/erpnext/accounts/doctype/item_tax_template/item_tax_template.json index b42d712d88a..87f0ad10483 100644 --- a/erpnext/accounts/doctype/item_tax_template/item_tax_template.json +++ b/erpnext/accounts/doctype/item_tax_template/item_tax_template.json @@ -35,6 +35,7 @@ { "fieldname": "company", "fieldtype": "Link", + "in_filter": 1, "in_list_view": 1, "label": "Company", "options": "Company", @@ -56,7 +57,7 @@ } ], "links": [], - "modified": "2022-01-18 21:11:23.105589", + "modified": "2023-07-09 18:11:23.105589", "modified_by": "Administrator", "module": "Accounts", "name": "Item Tax Template", @@ -102,4 +103,4 @@ "states": [], "title_field": "title", "track_changes": 1 -} \ No newline at end of file +} From 361a35708852d2b8dda2a8dfcb33119ff743b94d Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Mon, 10 Jul 2023 19:32:59 +0530 Subject: [PATCH 10/14] fix: payment entry `voucher_type` error (#35779) * fix: payment entry `voucher_type` error * chore: linters --- .../doctype/payment_entry/payment_entry.py | 96 ++++++++++--------- 1 file changed, 50 insertions(+), 46 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 699447bb7fc..dcd7295bae3 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1432,6 +1432,9 @@ def get_outstanding_reference_documents(args, validate=False): if args.get("party_type") == "Member": return + if not args.get("get_outstanding_invoices") and not args.get("get_orders_to_be_billed"): + args["get_outstanding_invoices"] = True + ple = qb.DocType("Payment Ledger Entry") common_filter = [] accounting_dimensions_filter = [] @@ -1626,60 +1629,59 @@ def get_orders_to_be_billed( cost_center=None, filters=None, ): + voucher_type = None if party_type == "Customer": voucher_type = "Sales Order" elif party_type == "Supplier": voucher_type = "Purchase Order" - elif party_type == "Employee": - voucher_type = None + + if not voucher_type: + return [] # Add cost center condition - if voucher_type: - doc = frappe.get_doc({"doctype": voucher_type}) - condition = "" - if doc and hasattr(doc, "cost_center") and doc.cost_center: - condition = " and cost_center='%s'" % cost_center + doc = frappe.get_doc({"doctype": voucher_type}) + condition = "" + if doc and hasattr(doc, "cost_center") and doc.cost_center: + condition = " and cost_center='%s'" % cost_center - orders = [] - if voucher_type: - if party_account_currency == company_currency: - grand_total_field = "base_grand_total" - rounded_total_field = "base_rounded_total" - else: - grand_total_field = "grand_total" - rounded_total_field = "rounded_total" + if party_account_currency == company_currency: + grand_total_field = "base_grand_total" + rounded_total_field = "base_rounded_total" + else: + grand_total_field = "grand_total" + rounded_total_field = "rounded_total" - orders = frappe.db.sql( - """ - select - name as voucher_no, - if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) as invoice_amount, - (if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) - advance_paid) as outstanding_amount, - transaction_date as posting_date - from - `tab{voucher_type}` - where - {party_type} = %s - and docstatus = 1 - and company = %s - and ifnull(status, "") != "Closed" - and if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) > advance_paid - and abs(100 - per_billed) > 0.01 - {condition} - order by - transaction_date, name - """.format( - **{ - "rounded_total_field": rounded_total_field, - "grand_total_field": grand_total_field, - "voucher_type": voucher_type, - "party_type": scrub(party_type), - "condition": condition, - } - ), - (party, company), - as_dict=True, - ) + orders = frappe.db.sql( + """ + select + name as voucher_no, + if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) as invoice_amount, + (if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) - advance_paid) as outstanding_amount, + transaction_date as posting_date + from + `tab{voucher_type}` + where + {party_type} = %s + and docstatus = 1 + and company = %s + and ifnull(status, "") != "Closed" + and if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) > advance_paid + and abs(100 - per_billed) > 0.01 + {condition} + order by + transaction_date, name + """.format( + **{ + "rounded_total_field": rounded_total_field, + "grand_total_field": grand_total_field, + "voucher_type": voucher_type, + "party_type": scrub(party_type), + "condition": condition, + } + ), + (party, company), + as_dict=True, + ) order_list = [] for d in orders: @@ -1712,6 +1714,8 @@ def get_negative_outstanding_invoices( cost_center=None, condition=None, ): + if party_type not in ["Customer", "Supplier"]: + return [] voucher_type = "Sales Invoice" if party_type == "Customer" else "Purchase Invoice" account = "debit_to" if voucher_type == "Sales Invoice" else "credit_to" supplier_condition = "" From 872a23c77d6fed002be10bbcc476e75ad7ba63e2 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 10 Jul 2023 20:34:54 +0530 Subject: [PATCH 11/14] fix: also check on_hold (#35910) fix: also check on_hold (#35910) (cherry picked from commit 5aa02b8571efdc1710a9b3069a18a80ed17856a2) Co-authored-by: RJPvT <48353029+RJPvT@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 31473db675f..8b44b22e3d7 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -850,7 +850,7 @@ def get_held_invoices(party_type, party): if party_type == "Supplier": held_invoices = frappe.db.sql( - "select name from `tabPurchase Invoice` where release_date IS NOT NULL and release_date > CURDATE()", + "select name from `tabPurchase Invoice` where on_hold = 1 and release_date IS NOT NULL and release_date > CURDATE()", as_dict=1, ) held_invoices = set(d["name"] for d in held_invoices) From bf84e0d441809d14ffa1c6e8d1bdedd63a43095c Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Mon, 10 Jul 2023 17:52:06 +0200 Subject: [PATCH 12/14] refactor: remove frappe.dynamic_link (#35096) --- erpnext/accounts/doctype/bank/bank.js | 3 --- erpnext/accounts/doctype/shareholder/shareholder.js | 2 -- erpnext/buying/doctype/supplier/supplier.js | 2 -- erpnext/crm/doctype/lead/lead.js | 5 ----- erpnext/crm/doctype/prospect/prospect.js | 2 -- erpnext/selling/doctype/customer/customer.js | 2 -- erpnext/setup/doctype/company/company.js | 2 -- erpnext/setup/doctype/sales_partner/sales_partner.js | 2 -- erpnext/stock/doctype/manufacturer/manufacturer.js | 1 - erpnext/stock/doctype/warehouse/warehouse.js | 6 ------ 10 files changed, 27 deletions(-) diff --git a/erpnext/accounts/doctype/bank/bank.js b/erpnext/accounts/doctype/bank/bank.js index 35d606ba3ae..6667193a54c 100644 --- a/erpnext/accounts/doctype/bank/bank.js +++ b/erpnext/accounts/doctype/bank/bank.js @@ -8,9 +8,6 @@ frappe.ui.form.on('Bank', { }, refresh: function(frm) { add_fields_to_mapping_table(frm); - - frappe.dynamic_link = { doc: frm.doc, fieldname: 'name', doctype: 'Bank' }; - frm.toggle_display(['address_html','contact_html'], !frm.doc.__islocal); if (frm.doc.__islocal) { diff --git a/erpnext/accounts/doctype/shareholder/shareholder.js b/erpnext/accounts/doctype/shareholder/shareholder.js index c6f101e7f31..544d417a0e5 100644 --- a/erpnext/accounts/doctype/shareholder/shareholder.js +++ b/erpnext/accounts/doctype/shareholder/shareholder.js @@ -3,8 +3,6 @@ frappe.ui.form.on('Shareholder', { refresh: function(frm) { - frappe.dynamic_link = { doc: frm.doc, fieldname: 'name', doctype: 'Shareholder' }; - frm.toggle_display(['contact_html'], !frm.doc.__islocal); if (frm.doc.__islocal) { diff --git a/erpnext/buying/doctype/supplier/supplier.js b/erpnext/buying/doctype/supplier/supplier.js index 5b95d0fde37..372ca56b86b 100644 --- a/erpnext/buying/doctype/supplier/supplier.js +++ b/erpnext/buying/doctype/supplier/supplier.js @@ -66,8 +66,6 @@ frappe.ui.form.on("Supplier", { }, refresh: function (frm) { - frappe.dynamic_link = { doc: frm.doc, fieldname: 'name', doctype: 'Supplier' } - if (frappe.defaults.get_default("supp_master_name") != "Naming Series") { frm.toggle_display("naming_series", false); } else { diff --git a/erpnext/crm/doctype/lead/lead.js b/erpnext/crm/doctype/lead/lead.js index b98a27ede8e..9ac54183a21 100644 --- a/erpnext/crm/doctype/lead/lead.js +++ b/erpnext/crm/doctype/lead/lead.js @@ -30,11 +30,6 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller var me = this; let doc = this.frm.doc; erpnext.toggle_naming_series(); - frappe.dynamic_link = { - doc: doc, - fieldname: 'name', - doctype: 'Lead' - }; if (!this.frm.is_new() && doc.__onload && !doc.__onload.is_customer) { this.frm.add_custom_button(__("Customer"), this.make_customer, __("Create")); diff --git a/erpnext/crm/doctype/prospect/prospect.js b/erpnext/crm/doctype/prospect/prospect.js index 495ed291ae9..c1a7ff576c1 100644 --- a/erpnext/crm/doctype/prospect/prospect.js +++ b/erpnext/crm/doctype/prospect/prospect.js @@ -3,8 +3,6 @@ frappe.ui.form.on('Prospect', { refresh (frm) { - frappe.dynamic_link = { doc: frm.doc, fieldname: "name", doctype: frm.doctype }; - if (!frm.is_new() && frappe.boot.user.can_create.includes("Customer")) { frm.add_custom_button(__("Customer"), function() { frappe.model.open_mapped_doc({ diff --git a/erpnext/selling/doctype/customer/customer.js b/erpnext/selling/doctype/customer/customer.js index 540e767d323..60f0941559a 100644 --- a/erpnext/selling/doctype/customer/customer.js +++ b/erpnext/selling/doctype/customer/customer.js @@ -131,8 +131,6 @@ frappe.ui.form.on("Customer", { erpnext.toggle_naming_series(); } - frappe.dynamic_link = {doc: frm.doc, fieldname: 'name', doctype: 'Customer'} - if(!frm.doc.__islocal) { frappe.contacts.render_address_and_contact(frm); diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index 333538722ee..f4682c1b806 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -81,8 +81,6 @@ frappe.ui.form.on("Company", { disbale_coa_fields(frm); frappe.contacts.render_address_and_contact(frm); - frappe.dynamic_link = {doc: frm.doc, fieldname: 'name', doctype: 'Company'} - if (frappe.perm.has_perm("Cost Center", 0, 'read')) { frm.add_custom_button(__('Cost Centers'), function() { frappe.set_route('Tree', 'Cost Center', {'company': frm.doc.name}); diff --git a/erpnext/setup/doctype/sales_partner/sales_partner.js b/erpnext/setup/doctype/sales_partner/sales_partner.js index 5656d43e852..f9e37705604 100644 --- a/erpnext/setup/doctype/sales_partner/sales_partner.js +++ b/erpnext/setup/doctype/sales_partner/sales_partner.js @@ -3,8 +3,6 @@ frappe.ui.form.on('Sales Partner', { refresh: function(frm) { - frappe.dynamic_link = {doc: frm.doc, fieldname: 'name', doctype: 'Sales Partner'} - if(frm.doc.__islocal){ hide_field(['address_html', 'contact_html', 'address_contacts']); frappe.contacts.clear_address_and_contact(frm); diff --git a/erpnext/stock/doctype/manufacturer/manufacturer.js b/erpnext/stock/doctype/manufacturer/manufacturer.js index bb7e314e14e..5b4990f08be 100644 --- a/erpnext/stock/doctype/manufacturer/manufacturer.js +++ b/erpnext/stock/doctype/manufacturer/manufacturer.js @@ -3,7 +3,6 @@ frappe.ui.form.on('Manufacturer', { refresh: function(frm) { - frappe.dynamic_link = { doc: frm.doc, fieldname: 'name', doctype: 'Manufacturer' }; if (frm.doc.__islocal) { hide_field(['address_html','contact_html']); frappe.contacts.clear_address_and_contact(frm); diff --git a/erpnext/stock/doctype/warehouse/warehouse.js b/erpnext/stock/doctype/warehouse/warehouse.js index 746a1cbaf17..3819c0b24a1 100644 --- a/erpnext/stock/doctype/warehouse/warehouse.js +++ b/erpnext/stock/doctype/warehouse/warehouse.js @@ -83,12 +83,6 @@ frappe.ui.form.on("Warehouse", { } frm.toggle_enable(["is_group", "company"], false); - - frappe.dynamic_link = { - doc: frm.doc, - fieldname: "name", - doctype: "Warehouse", - }; }, }); From 176966daabea4fe59a85858583f47ff5bd6ef38f Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 11 Jul 2023 10:04:17 +0530 Subject: [PATCH 13/14] fix: possible type error on ERR creation --- .../exchange_rate_revaluation/exchange_rate_revaluation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py index 598db642f33..89f827a4859 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py +++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py @@ -252,8 +252,8 @@ class ExchangeRateRevaluation(Document): new_balance_in_base_currency = 0 new_balance_in_account_currency = 0 - current_exchange_rate = calculate_exchange_rate_using_last_gle( - company, d.account, d.party_type, d.party + current_exchange_rate = ( + calculate_exchange_rate_using_last_gle(company, d.account, d.party_type, d.party) or 0.0 ) gain_loss = new_balance_in_account_currency - ( From ce9164ec69f6cbc67edae644dd0e0e17ff39af74 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 11 Jul 2023 12:03:38 +0530 Subject: [PATCH 14/14] fix: Validate for missing expense account (#36078) * fix: Validate for missing expense account * fix: Validate for missing expense account --- erpnext/controllers/stock_controller.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 5137e030582..caf4b6f18bc 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -201,6 +201,12 @@ class StockController(AccountsController): warehouse_asset_account = warehouse_account[item_row.get("warehouse")]["account"] expense_account = frappe.get_cached_value("Company", self.company, "default_expense_account") + if not expense_account: + frappe.throw( + _( + "Please set default cost of goods sold account in company {0} for booking rounding gain and loss during stock transfer" + ).format(frappe.bold(self.company)) + ) gl_list.append( self.get_gl_dict(