From 75396c02d28c37eeb44d2429e51d3b9f55a39fe9 Mon Sep 17 00:00:00 2001 From: Dhananjay Palshikar Date: Thu, 1 Sep 2022 19:07:13 +0530 Subject: [PATCH 01/34] Allow Item Templates in Work Order Items Item Variants can be a part of the items tables, however, when BOM items are fetched to be a part of the Work Order items, item variants were being filtered out. The filtering out does not serve a purpose. Having Item variants in BOMs allows for template like behaviour. --- erpnext/manufacturing/doctype/bom/bom.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 70637d3ef2..ff84991c36 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -1019,7 +1019,6 @@ def get_bom_items_as_dict( where bom_item.docstatus < 2 and bom.name = %(bom)s - and ifnull(item.has_variants, 0) = 0 and item.is_stock_item in (1, {is_stock_item}) {where_conditions} group by item_code, stock_uom From ff5cad1cd617a23d6ffc9903f29d713a8db8d949 Mon Sep 17 00:00:00 2001 From: anandbaburajan Date: Thu, 15 Sep 2022 13:15:34 +0530 Subject: [PATCH 02/34] fix: calculate depreciation properly on asset sale entry and scrap entry --- .../doctype/sales_invoice/sales_invoice.py | 15 ++-- erpnext/assets/doctype/asset/depreciation.py | 89 ++++++++++++++++++- erpnext/controllers/accounts_controller.py | 83 ----------------- 3 files changed, 97 insertions(+), 90 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 608f0828fe..4207156187 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -22,9 +22,12 @@ from erpnext.accounts.general_ledger import get_round_off_account_and_cost_cente from erpnext.accounts.party import get_due_date, get_party_account, get_party_details from erpnext.accounts.utils import get_account_currency from erpnext.assets.doctype.asset.depreciation import ( + depreciate_asset, get_disposal_account_and_cost_center, get_gl_entries_on_asset_disposal, get_gl_entries_on_asset_regain, + reset_depreciation_schedule, + reverse_depreciation_entry_made_after_disposal, ) from erpnext.controllers.accounts_controller import validate_account_head from erpnext.controllers.selling_controller import SellingController @@ -1086,18 +1089,20 @@ class SalesInvoice(SellingController): asset.db_set("disposal_date", None) if asset.calculate_depreciation: - self.reverse_depreciation_entry_made_after_disposal(asset) - self.reset_depreciation_schedule(asset) + posting_date = frappe.db.get_value("Sales Invoice", self.return_against, "posting_date") + reverse_depreciation_entry_made_after_disposal(asset, posting_date) + reset_depreciation_schedule(asset, self.posting_date) else: + if asset.calculate_depreciation: + depreciate_asset(asset, self.posting_date) + asset.reload() + fixed_asset_gl_entries = get_gl_entries_on_asset_disposal( asset, item.base_net_amount, item.finance_book ) asset.db_set("disposal_date", self.posting_date) - if asset.calculate_depreciation: - self.depreciate_asset(asset) - for gle in fixed_asset_gl_entries: gle["against"] = self.customer gl_entries.append(self.get_gl_dict(gle, item=item)) diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index 74386384c5..0299e28c11 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -4,11 +4,12 @@ import frappe from frappe import _ -from frappe.utils import cint, flt, getdate, today +from frappe.utils import add_months, cint, flt, getdate, nowdate, today from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( get_checks_for_pl_and_bs_accounts, ) +from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry def post_depreciation_entries(date=None, commit=True): @@ -196,6 +197,11 @@ def scrap_asset(asset_name): _("Asset {0} cannot be scrapped, as it is already {1}").format(asset.name, asset.status) ) + date = today() + + depreciate_asset(asset, date) + asset.reload() + depreciation_series = frappe.get_cached_value( "Company", asset.company, "series_for_depreciation_entry" ) @@ -203,7 +209,7 @@ def scrap_asset(asset_name): je = frappe.new_doc("Journal Entry") je.voucher_type = "Journal Entry" je.naming_series = depreciation_series - je.posting_date = today() + je.posting_date = date je.company = asset.company je.remark = "Scrap Entry for asset {0}".format(asset_name) @@ -225,6 +231,9 @@ def scrap_asset(asset_name): def restore_asset(asset_name): asset = frappe.get_doc("Asset", asset_name) + reverse_depreciation_entry_made_after_disposal(asset, asset.disposal_date) + reset_depreciation_schedule(asset, asset.disposal_date) + je = asset.journal_entry_for_scrap asset.db_set("disposal_date", None) @@ -235,6 +244,82 @@ def restore_asset(asset_name): asset.set_status() +def depreciate_asset(asset, date): + asset.flags.ignore_validate_update_after_submit = True + asset.prepare_depreciation_data(date_of_disposal=date) + asset.save() + + make_depreciation_entry(asset.name, date) + + +def reset_depreciation_schedule(asset, date): + asset.flags.ignore_validate_update_after_submit = True + + # recreate original depreciation schedule of the asset + asset.prepare_depreciation_data(date_of_return=date) + + modify_depreciation_schedule_for_asset_repairs(asset) + asset.save() + + +def modify_depreciation_schedule_for_asset_repairs(asset): + asset_repairs = frappe.get_all( + "Asset Repair", filters={"asset": asset.name}, fields=["name", "increase_in_asset_life"] + ) + + for repair in asset_repairs: + if repair.increase_in_asset_life: + asset_repair = frappe.get_doc("Asset Repair", repair.name) + asset_repair.modify_depreciation_schedule() + asset.prepare_depreciation_data() + + +def reverse_depreciation_entry_made_after_disposal(asset, date): + row = -1 + finance_book = asset.get("schedules")[0].get("finance_book") + for schedule in asset.get("schedules"): + if schedule.finance_book != finance_book: + row = 0 + finance_book = schedule.finance_book + else: + row += 1 + + if schedule.schedule_date == date: + if not disposal_was_made_on_original_schedule_date( + asset, schedule, row, date + ) or disposal_happens_in_the_future(date): + + reverse_journal_entry = make_reverse_journal_entry(schedule.journal_entry) + reverse_journal_entry.posting_date = nowdate() + frappe.flags.is_reverse_depr_entry = True + reverse_journal_entry.submit() + + frappe.flags.is_reverse_depr_entry = False + asset.flags.ignore_validate_update_after_submit = True + schedule.journal_entry = None + asset.save() + + +# if the invoice had been posted on the date the depreciation was initially supposed to happen, the depreciation shouldn't be undone +def disposal_was_made_on_original_schedule_date(asset, schedule, row, posting_date_of_disposal): + for finance_book in asset.get("finance_books"): + if schedule.finance_book == finance_book.finance_book: + orginal_schedule_date = add_months( + finance_book.depreciation_start_date, row * cint(finance_book.frequency_of_depreciation) + ) + + if orginal_schedule_date == posting_date_of_disposal: + return True + return False + + +def disposal_happens_in_the_future(posting_date_of_disposal): + if posting_date_of_disposal > getdate(): + return True + + return False + + def get_gl_entries_on_asset_regain(asset, selling_amount=0, finance_book=None): ( fixed_asset_account, diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 938de63f33..e9d3c7afba 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -38,7 +38,6 @@ from erpnext.accounts.party import ( validate_party_frozen_disabled, ) from erpnext.accounts.utils import get_account_currency, get_fiscal_years, validate_fiscal_year -from erpnext.assets.doctype.asset.depreciation import make_depreciation_entry from erpnext.buying.utils import update_last_purchase_rate from erpnext.controllers.print_settings import ( set_print_templates_for_item_table, @@ -1886,88 +1885,6 @@ class AccountsController(TransactionBase): _("Select finance book for the item {0} at row {1}").format(item.item_code, item.idx) ) - def depreciate_asset(self, asset): - asset.flags.ignore_validate_update_after_submit = True - asset.prepare_depreciation_data(date_of_disposal=self.posting_date) - asset.save() - - make_depreciation_entry(asset.name, self.posting_date) - - def reset_depreciation_schedule(self, asset): - asset.flags.ignore_validate_update_after_submit = True - - # recreate original depreciation schedule of the asset - asset.prepare_depreciation_data(date_of_return=self.posting_date) - - self.modify_depreciation_schedule_for_asset_repairs(asset) - asset.save() - - def modify_depreciation_schedule_for_asset_repairs(self, asset): - asset_repairs = frappe.get_all( - "Asset Repair", filters={"asset": asset.name}, fields=["name", "increase_in_asset_life"] - ) - - for repair in asset_repairs: - if repair.increase_in_asset_life: - asset_repair = frappe.get_doc("Asset Repair", repair.name) - asset_repair.modify_depreciation_schedule() - asset.prepare_depreciation_data() - - def reverse_depreciation_entry_made_after_disposal(self, asset): - from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry - - posting_date_of_original_disposal = self.get_posting_date_of_disposal_entry() - - row = -1 - finance_book = asset.get("schedules")[0].get("finance_book") - for schedule in asset.get("schedules"): - if schedule.finance_book != finance_book: - row = 0 - finance_book = schedule.finance_book - else: - row += 1 - - if schedule.schedule_date == posting_date_of_original_disposal: - if not self.disposal_was_made_on_original_schedule_date( - asset, schedule, row, posting_date_of_original_disposal - ) or self.disposal_happens_in_the_future(posting_date_of_original_disposal): - - reverse_journal_entry = make_reverse_journal_entry(schedule.journal_entry) - reverse_journal_entry.posting_date = nowdate() - frappe.flags.is_reverse_depr_entry = True - reverse_journal_entry.submit() - - frappe.flags.is_reverse_depr_entry = False - asset.flags.ignore_validate_update_after_submit = True - schedule.journal_entry = None - asset.save() - - def get_posting_date_of_disposal_entry(self): - if self.doctype == "Sales Invoice" and self.return_against: - return frappe.db.get_value("Sales Invoice", self.return_against, "posting_date") - else: - return self.posting_date - - # if the invoice had been posted on the date the depreciation was initially supposed to happen, the depreciation shouldn't be undone - def disposal_was_made_on_original_schedule_date( - self, asset, schedule, row, posting_date_of_disposal - ): - for finance_book in asset.get("finance_books"): - if schedule.finance_book == finance_book.finance_book: - orginal_schedule_date = add_months( - finance_book.depreciation_start_date, row * cint(finance_book.frequency_of_depreciation) - ) - - if orginal_schedule_date == posting_date_of_disposal: - return True - return False - - def disposal_happens_in_the_future(self, posting_date_of_disposal): - if posting_date_of_disposal > getdate(): - return True - - return False - @frappe.whitelist() def get_tax_rate(account_head): From 11ac20e5ee1f9ba00bbccd15388144dc4b4eea83 Mon Sep 17 00:00:00 2001 From: anandbaburajan Date: Thu, 15 Sep 2022 22:43:18 +0530 Subject: [PATCH 03/34] fix: asset tests --- erpnext/assets/doctype/asset/depreciation.py | 2 +- erpnext/assets/doctype/asset/test_asset.py | 52 ++++++++++++++------ 2 files changed, 39 insertions(+), 15 deletions(-) diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index 0299e28c11..9f378407f6 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -220,7 +220,7 @@ def scrap_asset(asset_name): je.flags.ignore_permissions = True je.submit() - frappe.db.set_value("Asset", asset_name, "disposal_date", today()) + frappe.db.set_value("Asset", asset_name, "disposal_date", date) frappe.db.set_value("Asset", asset_name, "journal_entry_for_scrap", je.name) asset.set_status("Scrapped") diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index e7af9bd5bc..bdda509e91 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -4,7 +4,16 @@ import unittest import frappe -from frappe.utils import add_days, add_months, cstr, flt, get_last_day, getdate, nowdate +from frappe.utils import ( + add_days, + add_months, + cstr, + flt, + get_first_day, + get_last_day, + getdate, + nowdate, +) from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice from erpnext.assets.doctype.asset.asset import make_sales_invoice, split_asset @@ -178,17 +187,20 @@ class TestAsset(AssetSetup): self.assertEqual(doc.items[0].is_fixed_asset, 1) def test_scrap_asset(self): + date = nowdate() + purchase_date = add_months(get_first_day(date), -2) + asset = create_asset( calculate_depreciation=1, - available_for_use_date="2020-01-01", - purchase_date="2020-01-01", + available_for_use_date=purchase_date, + purchase_date=purchase_date, expected_value_after_useful_life=10000, total_number_of_depreciations=10, frequency_of_depreciation=1, submit=1, ) - post_depreciation_entries(date=add_months("2020-01-01", 4)) + post_depreciation_entries(date=add_months(purchase_date, 2)) scrap_asset(asset.name) @@ -196,10 +208,15 @@ class TestAsset(AssetSetup): self.assertEqual(asset.status, "Scrapped") self.assertTrue(asset.journal_entry_for_scrap) + pro_rata_amount, _, _ = asset.get_pro_rata_amt( + asset.finance_books[0], 9000, add_months(get_last_day(purchase_date), 1), date + ) + pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount")) + expected_gle = ( - ("_Test Accumulated Depreciations - _TC", 36000.0, 0.0), + ("_Test Accumulated Depreciations - _TC", 18000.0 + pro_rata_amount, 0.0), ("_Test Fixed Asset - _TC", 0.0, 100000.0), - ("_Test Gain/Loss on Asset Disposal - _TC", 64000.0, 0.0), + ("_Test Gain/Loss on Asset Disposal - _TC", 82000.0 - pro_rata_amount, 0.0), ) gle = frappe.db.sql( @@ -217,18 +234,20 @@ class TestAsset(AssetSetup): self.assertEqual(asset.status, "Partially Depreciated") def test_gle_made_by_asset_sale(self): + date = nowdate() + purchase_date = add_months(get_first_day(date), -2) + asset = create_asset( calculate_depreciation=1, - available_for_use_date="2020-06-06", - purchase_date="2020-01-01", + available_for_use_date=purchase_date, + purchase_date=purchase_date, expected_value_after_useful_life=10000, - total_number_of_depreciations=3, - frequency_of_depreciation=10, - depreciation_start_date="2020-12-31", + total_number_of_depreciations=10, + frequency_of_depreciation=1, submit=1, ) - post_depreciation_entries(date="2021-01-01") + post_depreciation_entries(date=add_months(purchase_date, 2)) si = make_sales_invoice(asset=asset.name, item_code="Macbook Pro", company="_Test Company") si.customer = "_Test Customer" @@ -239,10 +258,15 @@ class TestAsset(AssetSetup): self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold") + pro_rata_amount, _, _ = asset.get_pro_rata_amt( + asset.finance_books[0], 9000, add_months(get_last_day(purchase_date), 1), date + ) + pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount")) + expected_gle = ( - ("_Test Accumulated Depreciations - _TC", 20490.2, 0.0), + ("_Test Accumulated Depreciations - _TC", 18000.0 + pro_rata_amount, 0.0), ("_Test Fixed Asset - _TC", 0.0, 100000.0), - ("_Test Gain/Loss on Asset Disposal - _TC", 54509.8, 0.0), + ("_Test Gain/Loss on Asset Disposal - _TC", 57000.0 - pro_rata_amount, 0.0), ("Debtors - _TC", 25000.0, 0.0), ) From 5a8b28c1943d24b7aedf7b96c57fe93d2b5ea5f6 Mon Sep 17 00:00:00 2001 From: anandbaburajan Date: Thu, 15 Sep 2022 22:52:25 +0530 Subject: [PATCH 04/34] fix: refactor asset capitilization --- .../doctype/asset_capitalization/asset_capitalization.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py index 2e6f0ad7b0..30ff0601e4 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py @@ -12,8 +12,11 @@ from six import string_types import erpnext from erpnext.assets.doctype.asset.depreciation import ( + depreciate_asset, get_gl_entries_on_asset_disposal, get_value_after_depreciation_on_disposal_date, + reset_depreciation_schedule, + reverse_depreciation_entry_made_after_disposal, ) from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account from erpnext.assets.doctype.asset_value_adjustment.asset_value_adjustment import ( @@ -424,7 +427,7 @@ class AssetCapitalization(StockController): asset = self.get_asset(item) if asset.calculate_depreciation: - self.depreciate_asset(asset) + depreciate_asset(asset, self.posting_date) asset.reload() fixed_asset_gl_entries = get_gl_entries_on_asset_disposal( @@ -516,8 +519,8 @@ class AssetCapitalization(StockController): self.set_consumed_asset_status(asset) if asset.calculate_depreciation: - self.reverse_depreciation_entry_made_after_disposal(asset) - self.reset_depreciation_schedule(asset) + reverse_depreciation_entry_made_after_disposal(asset, self.posting_date) + reset_depreciation_schedule(asset, self.posting_date) def get_asset(self, item): asset = frappe.get_doc("Asset", item.asset) From 43a3400221118cb6ad86c6ef15e201d07bc1941e Mon Sep 17 00:00:00 2001 From: anandbaburajan Date: Thu, 15 Sep 2022 23:25:04 +0530 Subject: [PATCH 05/34] fix: fix restore asset value after depreciation --- erpnext/assets/doctype/asset/depreciation.py | 9 ++++++++ erpnext/assets/doctype/asset/test_asset.py | 24 +++++++++++++++++--- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index 9f378407f6..e4765634c8 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -297,9 +297,18 @@ def reverse_depreciation_entry_made_after_disposal(asset, date): frappe.flags.is_reverse_depr_entry = False asset.flags.ignore_validate_update_after_submit = True schedule.journal_entry = None + depreciation_amount = get_depreciation_amount_in_je(reverse_journal_entry) + asset.finance_books[0].value_after_depreciation += depreciation_amount asset.save() +def get_depreciation_amount_in_je(journal_entry): + if journal_entry.accounts[0].debit_in_account_currency: + return journal_entry.accounts[0].debit_in_account_currency + else: + return journal_entry.accounts[0].credit_in_account_currency + + # if the invoice had been posted on the date the depreciation was initially supposed to happen, the depreciation shouldn't be undone def disposal_was_made_on_original_schedule_date(asset, schedule, row, posting_date_of_disposal): for finance_book in asset.get("finance_books"): diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index bdda509e91..f72b5249a4 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -201,17 +201,29 @@ class TestAsset(AssetSetup): ) post_depreciation_entries(date=add_months(purchase_date, 2)) + asset.load_from_db() + + accumulated_depr_amount = flt( + asset.gross_purchase_amount - asset.finance_books[0].value_after_depreciation, + asset.precision("gross_purchase_amount"), + ) + self.assertEquals(accumulated_depr_amount, 18000.0) scrap_asset(asset.name) - asset.load_from_db() - self.assertEqual(asset.status, "Scrapped") - self.assertTrue(asset.journal_entry_for_scrap) + accumulated_depr_amount = flt( + asset.gross_purchase_amount - asset.finance_books[0].value_after_depreciation, + asset.precision("gross_purchase_amount"), + ) pro_rata_amount, _, _ = asset.get_pro_rata_amt( asset.finance_books[0], 9000, add_months(get_last_day(purchase_date), 1), date ) pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount")) + self.assertEquals(accumulated_depr_amount, 18000.00 + pro_rata_amount) + + self.assertEqual(asset.status, "Scrapped") + self.assertTrue(asset.journal_entry_for_scrap) expected_gle = ( ("_Test Accumulated Depreciations - _TC", 18000.0 + pro_rata_amount, 0.0), @@ -233,6 +245,12 @@ class TestAsset(AssetSetup): self.assertFalse(asset.journal_entry_for_scrap) self.assertEqual(asset.status, "Partially Depreciated") + accumulated_depr_amount = flt( + asset.gross_purchase_amount - asset.finance_books[0].value_after_depreciation, + asset.precision("gross_purchase_amount"), + ) + self.assertEquals(accumulated_depr_amount, 18000.0) + def test_gle_made_by_asset_sale(self): date = nowdate() purchase_date = add_months(get_first_day(date), -2) From 613c8158a8d939065439ccfaf8e3ebfe13bb067b Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Fri, 16 Sep 2022 17:49:00 -0400 Subject: [PATCH 06/34] fix: actually reject process_scan when update_table fails. --- erpnext/public/js/utils/barcode_scanner.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/erpnext/public/js/utils/barcode_scanner.js b/erpnext/public/js/utils/barcode_scanner.js index a6bff2c148..6a80f92bc1 100644 --- a/erpnext/public/js/utils/barcode_scanner.js +++ b/erpnext/public/js/utils/barcode_scanner.js @@ -59,14 +59,14 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner { } me.update_table(data).then(row => { - row ? resolve(row) : reject(); - }); + resolve(row); + }).catch(() => reject()); }); }); } update_table(data) { - return new Promise(resolve => { + return new Promise((resolve, reject) => { let cur_grid = this.frm.fields_dict[this.items_table_name].grid; const {item_code, barcode, batch_no, serial_no, uom} = data; @@ -77,6 +77,7 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner { if (this.dont_allow_new_row) { this.show_alert(__("Maximum quantity scanned for item {0}.", [item_code]), "red"); this.clean_up(); + reject(); return; } @@ -88,6 +89,7 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner { if (this.is_duplicate_serial_no(row, serial_no)) { this.clean_up(); + reject(); return; } From 32e75ff80809bb7f9317ef1bf8d94b9dbbe6286f Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Fri, 16 Sep 2022 17:58:01 -0400 Subject: [PATCH 07/34] feat: audible indication of scan status. --- erpnext/public/js/utils/barcode_scanner.js | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/erpnext/public/js/utils/barcode_scanner.js b/erpnext/public/js/utils/barcode_scanner.js index 6a80f92bc1..83b108b874 100644 --- a/erpnext/public/js/utils/barcode_scanner.js +++ b/erpnext/public/js/utils/barcode_scanner.js @@ -21,6 +21,11 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner { this.items_table_name = opts.items_table_name || "items"; this.items_table = this.frm.doc[this.items_table_name]; + // optional sound name to play when scan either fails or passes. + // see https://frappeframework.com/docs/v14/user/en/python-api/hooks#sounds + this.success_sound = opts.play_success_sound; + this.fail_sound = opts.play_fail_sound; + // any API that takes `search_value` as input and returns dictionary as follows // { // item_code: "HORSESHOE", // present if any item was found @@ -54,13 +59,18 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner { if (!data || Object.keys(data).length === 0) { this.show_alert(__("Cannot find Item with this Barcode"), "red"); this.clean_up(); + this.play_fail_sound(); reject(); return; } me.update_table(data).then(row => { + this.play_success_sound(); resolve(row); - }).catch(() => reject()); + }).catch(() => { + this.play_fail_sound(); + reject(); + }); }); }); } @@ -221,6 +231,14 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner { return this.items_table.find((d) => !d.item_code); } + play_success_sound() { + this.success_sound && frappe.utils.play_sound(this.success_sound); + } + + play_fail_sound() { + this.fail_sound && frappe.utils.play_sound(this.fail_sound); + } + clean_up() { this.scan_barcode_field.set_value(""); refresh_field(this.items_table_name); From 785eaf8e8f9b0efc5674e499d82b7e54213e5c36 Mon Sep 17 00:00:00 2001 From: Shashank Shirke Date: Sun, 18 Sep 2022 05:42:47 -0700 Subject: [PATCH 08/34] fix: broken link for item valuation updated Old URL (404 Not Found): https://docs.erpnext.com/docs/v13/user/manual/en/stock/item/item-valuation-fifo-and-moving-average Updated URL: https://docs.erpnext.com/docs/v13/user/manual/en/stock/articles/calculation-of-valuation-rate-in-fifo-and-moving-average --- erpnext/public/js/help_links.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/help_links.js b/erpnext/public/js/help_links.js index b643ccae94..1c3f43e9cf 100644 --- a/erpnext/public/js/help_links.js +++ b/erpnext/public/js/help_links.js @@ -671,7 +671,7 @@ frappe.help.help_links["List/Item"] = [ label: "Item Valuation", url: docsUrl + - "user/manual/en/stock/articles/item-valuation-fifo-and-moving-average", + "user/manual/en/stock/articles/calculation-of-valuation-rate-in-fifo-and-moving-average", }, ]; From 9decebe6e1960687934dc0b99535019c547889c0 Mon Sep 17 00:00:00 2001 From: Ernesto Ruiz Date: Mon, 19 Sep 2022 10:13:30 -0600 Subject: [PATCH 09/34] fix: Add strings to translate function --- erpnext/projects/report/project_summary/project_summary.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/projects/report/project_summary/project_summary.py b/erpnext/projects/report/project_summary/project_summary.py index 606c0c2d81..7a35fd236a 100644 --- a/erpnext/projects/report/project_summary/project_summary.py +++ b/erpnext/projects/report/project_summary/project_summary.py @@ -91,9 +91,9 @@ def get_chart_data(data): "data": { "labels": labels[:30], "datasets": [ - {"name": "Overdue", "values": overdue[:30]}, - {"name": "Completed", "values": completed[:30]}, - {"name": "Total Tasks", "values": total[:30]}, + {"name": _("Overdue"), "values": overdue[:30]}, + {"name": _("Completed"), "values": completed[:30]}, + {"name": _("Total Tasks"), "values": total[:30]}, ], }, "type": "bar", From aa49ec815a3837ff5777903f929477b836d92472 Mon Sep 17 00:00:00 2001 From: Ernesto Ruiz Date: Mon, 19 Sep 2022 11:55:13 -0600 Subject: [PATCH 10/34] fix: add translate function to strings --- .../report/work_order_summary/work_order_summary.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/manufacturing/report/work_order_summary/work_order_summary.py b/erpnext/manufacturing/report/work_order_summary/work_order_summary.py index 2368bfdf2c..41ffcbb190 100644 --- a/erpnext/manufacturing/report/work_order_summary/work_order_summary.py +++ b/erpnext/manufacturing/report/work_order_summary/work_order_summary.py @@ -83,6 +83,7 @@ def get_chart_based_on_status(data): for d in data: status_wise_data[d.status] += 1 + labels = [_(label) for label in labels] values = [status_wise_data[label] for label in labels] chart = { @@ -95,7 +96,7 @@ def get_chart_based_on_status(data): def get_chart_based_on_age(data): - labels = ["0-30 Days", "30-60 Days", "60-90 Days", "90 Above"] + labels = [_("0-30 Days"), _("30-60 Days"), _("60-90 Days"), _("90 Above")] age_wise_data = {"0-30 Days": 0, "30-60 Days": 0, "60-90 Days": 0, "90 Above": 0} @@ -135,8 +136,8 @@ def get_chart_based_on_qty(data, filters): pending.append(periodic_data.get("Pending").get(d)) completed.append(periodic_data.get("Completed").get(d)) - datasets.append({"name": "Pending", "values": pending}) - datasets.append({"name": "Completed", "values": completed}) + datasets.append({"name": _("Pending"), "values": pending}) + datasets.append({"name": _("Completed"), "values": completed}) chart = { "data": {"labels": labels, "datasets": datasets}, From 94199b78675e8d20c98bf9d7d95fb3d6d7d2b831 Mon Sep 17 00:00:00 2001 From: Ernesto Ruiz Date: Mon, 19 Sep 2022 11:56:42 -0600 Subject: [PATCH 11/34] fix: add translate function to strings --- .../manufacturing/report/job_card_summary/job_card_summary.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/report/job_card_summary/job_card_summary.py b/erpnext/manufacturing/report/job_card_summary/job_card_summary.py index 5083b7369d..63c2d97d57 100644 --- a/erpnext/manufacturing/report/job_card_summary/job_card_summary.py +++ b/erpnext/manufacturing/report/job_card_summary/job_card_summary.py @@ -85,8 +85,8 @@ def get_chart_data(job_card_details, filters): open_job_cards.append(periodic_data.get("Open").get(d)) completed.append(periodic_data.get("Completed").get(d)) - datasets.append({"name": "Open", "values": open_job_cards}) - datasets.append({"name": "Completed", "values": completed}) + datasets.append({"name": _("Open"), "values": open_job_cards}) + datasets.append({"name": _("Completed"), "values": completed}) chart = {"data": {"labels": labels, "datasets": datasets}, "type": "bar"} From c4919cf5ec6ab6c3dd60364e82dd072cf6f4b023 Mon Sep 17 00:00:00 2001 From: Ernesto Ruiz Date: Thu, 22 Sep 2022 15:42:46 -0600 Subject: [PATCH 12/34] fix: typo on opportunity summary by sales stage report --- .../opportunity_summary_by_sales_stage.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.js b/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.js index 116db2f5a2..7cd1710a7f 100644 --- a/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.js +++ b/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.js @@ -44,7 +44,7 @@ frappe.query_reports["Opportunity Summary by Sales Stage"] = { }, { fieldname: "opportunity_source", - label: __("Oppoturnity Source"), + label: __("Opportunity Source"), fieldtype: "Link", options: "Lead Source", }, @@ -62,4 +62,4 @@ frappe.query_reports["Opportunity Summary by Sales Stage"] = { default: frappe.defaults.get_user_default("Company") } ] -}; \ No newline at end of file +}; From bc3ab45af2be165988b143b62318d865f83ee673 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 23 Sep 2022 15:21:09 +0530 Subject: [PATCH 13/34] fix: opening entry causing discepancy between stock and trial balance --- erpnext/accounts/report/general_ledger/general_ledger.py | 4 ++-- erpnext/accounts/report/trial_balance/trial_balance.py | 3 ++- .../trial_balance_for_party/trial_balance_for_party.py | 9 +++++++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index e77e828e16..82f38dacd2 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -237,9 +237,9 @@ def get_conditions(filters): or filters.get("party") or filters.get("group_by") in ["Group by Account", "Group by Party"] ): - conditions.append("posting_date >=%(from_date)s") + conditions.append("(posting_date >=%(from_date)s or is_opening = 'Yes')") - conditions.append("(posting_date <=%(to_date)s or is_opening = 'Yes')") + conditions.append("(posting_date <=%(to_date)s)") if filters.get("project"): conditions.append("project in %(project)s") diff --git a/erpnext/accounts/report/trial_balance/trial_balance.py b/erpnext/accounts/report/trial_balance/trial_balance.py index 6bd08ad837..6d2cd8ed41 100644 --- a/erpnext/accounts/report/trial_balance/trial_balance.py +++ b/erpnext/accounts/report/trial_balance/trial_balance.py @@ -172,6 +172,7 @@ def get_rootwise_opening_balances(filters, report_type): query_filters = { "company": filters.company, "from_date": filters.from_date, + "to_date": filters.to_date, "report_type": report_type, "year_start_date": filters.year_start_date, "project": filters.project, @@ -200,7 +201,7 @@ def get_rootwise_opening_balances(filters, report_type): where company=%(company)s {additional_conditions} - and (posting_date < %(from_date)s or ifnull(is_opening, 'No') = 'Yes') + and (posting_date < %(from_date)s or (ifnull(is_opening, 'No') = 'Yes' and posting_date <= %(to_date)s)) and account in (select name from `tabAccount` where report_type=%(report_type)s) and is_cancelled = 0 group by account""".format( diff --git a/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py b/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py index 5fcfdff6f1..ee223484d4 100644 --- a/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py +++ b/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py @@ -104,12 +104,17 @@ def get_opening_balances(filters): where company=%(company)s and is_cancelled=0 and ifnull(party_type, '') = %(party_type)s and ifnull(party, '') != '' - and (posting_date < %(from_date)s or ifnull(is_opening, 'No') = 'Yes') + and (posting_date < %(from_date)s or (ifnull(is_opening, 'No') = 'Yes' and posting_date <= %(to_date)s)) {account_filter} group by party""".format( account_filter=account_filter ), - {"company": filters.company, "from_date": filters.from_date, "party_type": filters.party_type}, + { + "company": filters.company, + "from_date": filters.from_date, + "to_date": filters.to_date, + "party_type": filters.party_type, + }, as_dict=True, ) From 22299d2382c912e8bc8dddca3af7d4cf94374339 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Mon, 26 Sep 2022 09:40:49 +0530 Subject: [PATCH 14/34] refactor: rewrite `Item Price Stock Report` queries in `QB` --- .../item_price_stock/item_price_stock.py | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/erpnext/stock/report/item_price_stock/item_price_stock.py b/erpnext/stock/report/item_price_stock/item_price_stock.py index 15218e63a8..1b07f596c7 100644 --- a/erpnext/stock/report/item_price_stock/item_price_stock.py +++ b/erpnext/stock/report/item_price_stock/item_price_stock.py @@ -62,22 +62,28 @@ def get_data(filters, columns): def get_item_price_qty_data(filters): - conditions = "" - if filters.get("item_code"): - conditions += "where a.item_code=%(item_code)s" + item_price = frappe.qb.DocType("Item Price") + bin = frappe.qb.DocType("Bin") - item_results = frappe.db.sql( - """select a.item_code, a.item_name, a.name as price_list_name, - a.brand as brand, b.warehouse as warehouse, b.actual_qty as actual_qty - from `tabItem Price` a left join `tabBin` b - ON a.item_code = b.item_code - {conditions}""".format( - conditions=conditions - ), - filters, - as_dict=1, + query = ( + frappe.qb.from_(item_price) + .left_join(bin) + .on(item_price.item_code == bin.item_code) + .select( + item_price.item_code, + item_price.item_name, + item_price.name.as_("price_list_name"), + item_price.brand.as_("brand"), + bin.warehouse.as_("warehouse"), + bin.actual_qty.as_("actual_qty"), + ) ) + if filters.get("item_code"): + query = query.where(item_price.item_code == filters.get("item_code")) + + item_results = query.run(as_dict=True) + price_list_names = list(set(item.price_list_name for item in item_results)) buying_price_map = get_price_map(price_list_names, buying=1) From b93331e844376fb5207acb41b4e7795ea6e1f059 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Mon, 26 Sep 2022 09:44:29 +0530 Subject: [PATCH 15/34] refactor: rewrite `Incorrect Stock Value Report` queries in `QB` --- .../incorrect_stock_value_report.py | 41 +++++++++++-------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.py b/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.py index 23e3c8a97f..df01b14d11 100644 --- a/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.py +++ b/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.py @@ -4,6 +4,8 @@ import frappe from frappe import _ +from frappe.query_builder import Field +from frappe.query_builder.functions import Min, Timestamp from frappe.utils import add_days, getdate, today import erpnext @@ -28,7 +30,7 @@ def execute(filters=None): def get_unsync_date(filters): date = filters.from_date if not date: - date = frappe.db.sql(""" SELECT min(posting_date) from `tabStock Ledger Entry`""") + date = (frappe.qb.from_("Stock Ledger Entry").select(Min(Field("posting_date")))).run() date = date[0][0] if not date: @@ -54,22 +56,27 @@ def get_data(report_filters): result = [] voucher_wise_dict = {} - data = frappe.db.sql( - """ - SELECT - name, posting_date, posting_time, voucher_type, voucher_no, - stock_value_difference, stock_value, warehouse, item_code - FROM - `tabStock Ledger Entry` - WHERE - posting_date - = %s and company = %s - and is_cancelled = 0 - ORDER BY timestamp(posting_date, posting_time) asc, creation asc - """, - (from_date, report_filters.company), - as_dict=1, - ) + sle = frappe.qb.DocType("Stock Ledger Entry") + data = ( + frappe.qb.from_(sle) + .select( + sle.name, + sle.posting_date, + sle.posting_time, + sle.voucher_type, + sle.voucher_no, + sle.stock_value_difference, + sle.stock_value, + sle.warehouse, + sle.item_code, + ) + .where( + (sle.posting_date == from_date) + & (sle.company == report_filters.company) + & (sle.is_cancelled == 0) + ) + .orderby(Timestamp(sle.posting_date, sle.posting_time), sle.creation) + ).run(as_dict=True) for d in data: voucher_wise_dict.setdefault((d.item_code, d.warehouse), []).append(d) From f41d1500b0ce024e1321df8de9aab6c4d15be9f1 Mon Sep 17 00:00:00 2001 From: hendrik Date: Mon, 26 Sep 2022 19:17:24 +0700 Subject: [PATCH 16/34] fix: report chart field type * fix: report chart field type Co-authored-by: Sagar Sharma Co-authored-by: Deepesh Garg --- .../buying/report/purchase_order_trends/purchase_order_trends.py | 1 + .../stock/report/delivery_note_trends/delivery_note_trends.py | 1 + .../report/purchase_receipt_trends/purchase_receipt_trends.py | 1 + 3 files changed, 3 insertions(+) diff --git a/erpnext/buying/report/purchase_order_trends/purchase_order_trends.py b/erpnext/buying/report/purchase_order_trends/purchase_order_trends.py index dbdc62e9ec..d089473a16 100644 --- a/erpnext/buying/report/purchase_order_trends/purchase_order_trends.py +++ b/erpnext/buying/report/purchase_order_trends/purchase_order_trends.py @@ -53,4 +53,5 @@ def get_chart_data(data, conditions, filters): }, "type": "line", "lineOptions": {"regionFill": 1}, + "fieldtype": "Currency", } diff --git a/erpnext/stock/report/delivery_note_trends/delivery_note_trends.py b/erpnext/stock/report/delivery_note_trends/delivery_note_trends.py index 7a1b8c0cee..0ec4e1ce95 100644 --- a/erpnext/stock/report/delivery_note_trends/delivery_note_trends.py +++ b/erpnext/stock/report/delivery_note_trends/delivery_note_trends.py @@ -45,4 +45,5 @@ def get_chart_data(data, filters): "datasets": [{"name": _("Total Delivered Amount"), "values": datapoints}], }, "type": "bar", + "fieldtype": "Currency", } diff --git a/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.py b/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.py index fe2d55a391..b62a6ee6fd 100644 --- a/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.py +++ b/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.py @@ -46,4 +46,5 @@ def get_chart_data(data, filters): }, "type": "bar", "colors": ["#5e64ff"], + "fieldtype": "Currency", } From e683cccf3520812b6f031ce780bf8575e5f6c8db Mon Sep 17 00:00:00 2001 From: Ernesto Ruiz Date: Mon, 26 Sep 2022 07:59:13 -0600 Subject: [PATCH 17/34] fix: add translate function to book appointment html --- erpnext/www/book_appointment/index.html | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/erpnext/www/book_appointment/index.html b/erpnext/www/book_appointment/index.html index 207175f89d..ad964d9370 100644 --- a/erpnext/www/book_appointment/index.html +++ b/erpnext/www/book_appointment/index.html @@ -12,8 +12,8 @@
-

Book an appointment

-

Select the date and your timezone

+

{{ _("Book an appointment") }}

+

{{ _("Select the date and your timezone") }}

@@ -31,7 +31,7 @@
- +
@@ -39,24 +39,24 @@
-

Add details

-

Selected date is at +

{{ _("Add details") }}

+

{{ _("Selected date is") }} {{ _("at") }}

- + - + + placeholder="{{ _('Notes') }}">
-
-
+
+
From 728ef46048d04b17a583c018d4201e868aa20605 Mon Sep 17 00:00:00 2001 From: Ernesto Ruiz Date: Mon, 26 Sep 2022 08:01:48 -0600 Subject: [PATCH 18/34] fix: add translate function to book appointment index.js --- erpnext/www/book_appointment/index.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/www/book_appointment/index.js b/erpnext/www/book_appointment/index.js index 5562cbd471..46ac15518c 100644 --- a/erpnext/www/book_appointment/index.js +++ b/erpnext/www/book_appointment/index.js @@ -69,7 +69,7 @@ function on_date_or_timezone_select() { window.selected_timezone = timezone.value; update_time_slots(date_picker.value, timezone.value); let lead_text = document.getElementById('lead-text'); - lead_text.innerHTML = "Select Time" + lead_text.innerHTML = __("Select Time") } async function get_time_slots(date, timezone) { @@ -89,7 +89,7 @@ async function update_time_slots(selected_date, selected_timezone) { clear_time_slots(); if (window.slots.length <= 0) { let message_div = document.createElement('p'); - message_div.innerHTML = "There are no slots available on this date"; + message_div.innerHTML = __("There are no slots available on this date"); timeslot_container.appendChild(message_div); return } @@ -128,7 +128,7 @@ function get_slot_layout(time) { let start_time_string = moment(time).tz(timezone).format("LT"); let end_time = moment(time).tz(timezone).add(window.appointment_settings.appointment_duration, 'minutes'); let end_time_string = end_time.format("LT"); - return `${start_time_string}
to ${end_time_string}`; + return `${start_time_string}
${__("to") } ${end_time_string}`; } function select_time() { @@ -227,9 +227,9 @@ async function submit() { }, callback: (response)=>{ if (response.message.status == "Unverified") { - frappe.show_alert("Please check your email to confirm the appointment") + frappe.show_alert(__("Please check your email to confirm the appointment")) } else { - frappe.show_alert("Appointment Created Successfully"); + frappe.show_alert(__("Appointment Created Successfully")); } setTimeout(()=>{ let redirect_url = "/"; @@ -239,7 +239,7 @@ async function submit() { window.location.href = redirect_url;},5000) }, error: (err)=>{ - frappe.show_alert("Something went wrong please try again"); + frappe.show_alert(__("Something went wrong please try again")); button.disabled = false; } }); From 499ce5139c16b7ac60eb7f062919a3298dca739a Mon Sep 17 00:00:00 2001 From: Ernesto Ruiz Date: Mon, 26 Sep 2022 08:04:14 -0600 Subject: [PATCH 19/34] fix: add translate function to book appointment verify html --- erpnext/www/book_appointment/verify/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/www/book_appointment/verify/index.html b/erpnext/www/book_appointment/verify/index.html index 9bcd3d202e..58c07e85cc 100644 --- a/erpnext/www/book_appointment/verify/index.html +++ b/erpnext/www/book_appointment/verify/index.html @@ -8,11 +8,11 @@ {% if success==True %}
- Your email has been verified and your appointment has been scheduled + {{ _("Your email has been verified and your appointment has been scheduled") }}
{% else %}
- Verification failed please check the link + {{ _("Verification failed please check the link") }}
{% endif %} {% endblock%} From 9049db41ae8793cdcb5fb770fe202c096c9ad53c Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Tue, 27 Sep 2022 12:00:50 +0530 Subject: [PATCH 20/34] fix: `For Quantity` error msg in `Stock Entry` --- erpnext/stock/doctype/stock_entry/stock_entry.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 738ac330e3..8bcd772d90 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -1073,8 +1073,8 @@ class StockEntry(StockController): # No work order could mean independent Manufacture entry, if so skip validation if self.work_order and self.fg_completed_qty > allowed_qty: frappe.throw( - _("For quantity {0} should not be greater than work order quantity {1}").format( - flt(self.fg_completed_qty), wo_qty + _("For quantity {0} should not be greater than allowed quantity {1}").format( + flt(self.fg_completed_qty), allowed_qty ) ) From a4a86ee23fb36f448a308c4f3566ba4be05697ab Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 27 Sep 2022 13:47:54 +0530 Subject: [PATCH 21/34] fix: allow to return expired batches using purchase return --- .../purchase_invoice/test_purchase_invoice.py | 31 +++++++++++++++++++ .../purchase_receipt/test_purchase_receipt.py | 31 +++++++++++++++++++ .../stock_ledger_entry/stock_ledger_entry.py | 3 ++ 3 files changed, 65 insertions(+) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 0a4f25b876..f901257ccf 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1543,6 +1543,37 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin): pi.save() self.assertEqual(pi.items[0].conversion_factor, 1000) + def test_batch_expiry_for_purchase_invoice(self): + from erpnext.controllers.sales_and_purchase_return import make_return_doc + + item = self.make_item( + "_Test Batch Item For Return Check", + { + "is_purchase_item": 1, + "is_stock_item": 1, + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "TBIRC.#####", + }, + ) + + pi = make_purchase_invoice( + qty=1, + item_code=item.name, + update_stock=True, + ) + + pi.load_from_db() + batch_no = pi.items[0].batch_no + self.assertTrue(batch_no) + + frappe.db.set_value("Batch", batch_no, "expiry_date", add_days(nowdate(), -1)) + + return_pi = make_return_doc(pi.doctype, pi.name) + return_pi.save().submit() + + self.assertTrue(return_pi.docstatus == 1) + def check_gl_entries(doc, voucher_no, expected_gle, posting_date): gl_entries = frappe.db.sql( diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index b77c3a5134..62697244ba 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -1241,6 +1241,37 @@ class TestPurchaseReceipt(FrappeTestCase): self.assertEqual(query[0].value, 0) + def test_batch_expiry_for_purchase_receipt(self): + from erpnext.controllers.sales_and_purchase_return import make_return_doc + + item = make_item( + "_Test Batch Item For Return Check", + { + "is_purchase_item": 1, + "is_stock_item": 1, + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "TBIRC.#####", + }, + ) + + pi = make_purchase_receipt( + qty=1, + item_code=item.name, + update_stock=True, + ) + + pi.load_from_db() + batch_no = pi.items[0].batch_no + self.assertTrue(batch_no) + + frappe.db.set_value("Batch", batch_no, "expiry_date", add_days(today(), -1)) + + return_pi = make_return_doc(pi.doctype, pi.name) + return_pi.save().submit() + + self.assertTrue(return_pi.docstatus == 1) + def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py index 329cd7da09..f7f8cbe4ee 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py @@ -153,6 +153,9 @@ class StockLedgerEntry(Document): def validate_batch(self): if self.batch_no and self.voucher_type != "Stock Entry": + if self.voucher_type in ["Purchase Receipt", "Purchase Invoice"] and self.actual_qty < 0: + return + expiry_date = frappe.db.get_value("Batch", self.batch_no, "expiry_date") if expiry_date: if getdate(self.posting_date) > getdate(expiry_date): From 05392e0918fce4aafffc016f246f21c8afcc5d8a Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Tue, 27 Sep 2022 15:30:08 +0530 Subject: [PATCH 22/34] fix: consider overproduction percentage for WO finish button --- .../doctype/work_order/work_order.js | 65 ++++++++++++------- 1 file changed, 40 insertions(+), 25 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index 20f15039ef..f3640b93b2 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -557,37 +557,52 @@ erpnext.work_order = { if(!frm.doc.skip_transfer){ // If "Material Consumption is check in Manufacturing Settings, allow Material Consumption - if ((flt(doc.produced_qty) < flt(doc.material_transferred_for_manufacturing)) - && frm.doc.status != 'Stopped') { - frm.has_finish_btn = true; + if (flt(doc.material_transferred_for_manufacturing) > 0 && frm.doc.status != 'Stopped') { + if ((flt(doc.produced_qty) < flt(doc.material_transferred_for_manufacturing))) { + frm.has_finish_btn = true; - if (frm.doc.__onload && frm.doc.__onload.material_consumption == 1) { - // Only show "Material Consumption" when required_qty > consumed_qty - var counter = 0; - var tbl = frm.doc.required_items || []; - var tbl_lenght = tbl.length; - for (var i = 0, len = tbl_lenght; i < len; i++) { - let wo_item_qty = frm.doc.required_items[i].transferred_qty || frm.doc.required_items[i].required_qty; - if (flt(wo_item_qty) > flt(frm.doc.required_items[i].consumed_qty)) { - counter += 1; + if (frm.doc.__onload && frm.doc.__onload.material_consumption == 1) { + // Only show "Material Consumption" when required_qty > consumed_qty + var counter = 0; + var tbl = frm.doc.required_items || []; + var tbl_lenght = tbl.length; + for (var i = 0, len = tbl_lenght; i < len; i++) { + let wo_item_qty = frm.doc.required_items[i].transferred_qty || frm.doc.required_items[i].required_qty; + if (flt(wo_item_qty) > flt(frm.doc.required_items[i].consumed_qty)) { + counter += 1; + } + } + if (counter > 0) { + var consumption_btn = frm.add_custom_button(__('Material Consumption'), function() { + const backflush_raw_materials_based_on = frm.doc.__onload.backflush_raw_materials_based_on; + erpnext.work_order.make_consumption_se(frm, backflush_raw_materials_based_on); + }); + consumption_btn.addClass('btn-primary'); } } - if (counter > 0) { - var consumption_btn = frm.add_custom_button(__('Material Consumption'), function() { - const backflush_raw_materials_based_on = frm.doc.__onload.backflush_raw_materials_based_on; - erpnext.work_order.make_consumption_se(frm, backflush_raw_materials_based_on); - }); - consumption_btn.addClass('btn-primary'); + + var finish_btn = frm.add_custom_button(__('Finish'), function() { + erpnext.work_order.make_se(frm, 'Manufacture'); + }); + + if(doc.material_transferred_for_manufacturing>=doc.qty) { + // all materials transferred for manufacturing, make this primary + finish_btn.addClass('btn-primary'); } - } + } else { + frappe.db.get_doc("Manufacturing Settings").then((doc) => { + let allowance_percentage = doc.overproduction_percentage_for_work_order; - var finish_btn = frm.add_custom_button(__('Finish'), function() { - erpnext.work_order.make_se(frm, 'Manufacture'); - }); + if (allowance_percentage > 0) { + let allowed_qty = frm.doc.qty + ((allowance_percentage / 100) * frm.doc.qty); - if(doc.material_transferred_for_manufacturing>=doc.qty) { - // all materials transferred for manufacturing, make this primary - finish_btn.addClass('btn-primary'); + if ((flt(doc.produced_qty) < allowed_qty)) { + frm.add_custom_button(__('Finish'), function() { + erpnext.work_order.make_se(frm, 'Manufacture'); + }); + } + } + }); } } } else { From c760ca232365b8e868c8420f877c9c744ec2b4e7 Mon Sep 17 00:00:00 2001 From: Maharshi Patel <39730881+maharshivpatel@users.noreply.github.com> Date: Tue, 27 Sep 2022 15:31:36 +0530 Subject: [PATCH 23/34] fix: Scrap Asset Accounting Dimensions (#31949) * fix: Scrap Asset Accounting Dimensions --- .../doctype/sales_invoice/sales_invoice.py | 4 +- erpnext/assets/doctype/asset/asset.js | 2 +- erpnext/assets/doctype/asset/depreciation.py | 110 ++++++++++++------ .../asset_capitalization.py | 6 +- 4 files changed, 83 insertions(+), 39 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index e51938b27f..94db9a9adc 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1081,7 +1081,7 @@ class SalesInvoice(SellingController): if self.is_return: fixed_asset_gl_entries = get_gl_entries_on_asset_regain( - asset, item.base_net_amount, item.finance_book + asset, item.base_net_amount, item.finance_book, self.get("doctype"), self.get("name") ) asset.db_set("disposal_date", None) @@ -1091,7 +1091,7 @@ class SalesInvoice(SellingController): else: fixed_asset_gl_entries = get_gl_entries_on_asset_disposal( - asset, item.base_net_amount, item.finance_book + asset, item.base_net_amount, item.finance_book, self.get("doctype"), self.get("name") ) asset.db_set("disposal_date", self.posting_date) diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index a43a16c9ec..5512d4159d 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -230,7 +230,7 @@ frappe.ui.form.on('Asset', { datasets: [{ color: 'green', values: asset_values, - formatted: asset_values.map(d => d.toFixed(2)) + formatted: asset_values.map(d => d?.toFixed(2)) }] }, type: 'line' diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index 74386384c5..a4bb960d8e 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -235,7 +235,9 @@ def restore_asset(asset_name): asset.set_status() -def get_gl_entries_on_asset_regain(asset, selling_amount=0, finance_book=None): +def get_gl_entries_on_asset_regain( + asset, selling_amount=0, finance_book=None, voucher_type=None, voucher_no=None +): ( fixed_asset_account, asset, @@ -247,28 +249,45 @@ def get_gl_entries_on_asset_regain(asset, selling_amount=0, finance_book=None): ) = get_asset_details(asset, finance_book) gl_entries = [ - { - "account": fixed_asset_account, - "debit_in_account_currency": asset.gross_purchase_amount, - "debit": asset.gross_purchase_amount, - "cost_center": depreciation_cost_center, - }, - { - "account": accumulated_depr_account, - "credit_in_account_currency": accumulated_depr_amount, - "credit": accumulated_depr_amount, - "cost_center": depreciation_cost_center, - }, + asset.get_gl_dict( + { + "account": fixed_asset_account, + "debit_in_account_currency": asset.gross_purchase_amount, + "debit": asset.gross_purchase_amount, + "cost_center": depreciation_cost_center, + "posting_date": getdate(), + }, + item=asset, + ), + asset.get_gl_dict( + { + "account": accumulated_depr_account, + "credit_in_account_currency": accumulated_depr_amount, + "credit": accumulated_depr_amount, + "cost_center": depreciation_cost_center, + "posting_date": getdate(), + }, + item=asset, + ), ] profit_amount = abs(flt(value_after_depreciation)) - abs(flt(selling_amount)) if profit_amount: - get_profit_gl_entries(profit_amount, gl_entries, disposal_account, depreciation_cost_center) + get_profit_gl_entries( + asset, profit_amount, gl_entries, disposal_account, depreciation_cost_center + ) + + if voucher_type and voucher_no: + for entry in gl_entries: + entry["voucher_type"] = voucher_type + entry["voucher_no"] = voucher_no return gl_entries -def get_gl_entries_on_asset_disposal(asset, selling_amount=0, finance_book=None): +def get_gl_entries_on_asset_disposal( + asset, selling_amount=0, finance_book=None, voucher_type=None, voucher_no=None +): ( fixed_asset_account, asset, @@ -280,23 +299,38 @@ def get_gl_entries_on_asset_disposal(asset, selling_amount=0, finance_book=None) ) = get_asset_details(asset, finance_book) gl_entries = [ - { - "account": fixed_asset_account, - "credit_in_account_currency": asset.gross_purchase_amount, - "credit": asset.gross_purchase_amount, - "cost_center": depreciation_cost_center, - }, - { - "account": accumulated_depr_account, - "debit_in_account_currency": accumulated_depr_amount, - "debit": accumulated_depr_amount, - "cost_center": depreciation_cost_center, - }, + asset.get_gl_dict( + { + "account": fixed_asset_account, + "credit_in_account_currency": asset.gross_purchase_amount, + "credit": asset.gross_purchase_amount, + "cost_center": depreciation_cost_center, + "posting_date": getdate(), + }, + item=asset, + ), + asset.get_gl_dict( + { + "account": accumulated_depr_account, + "debit_in_account_currency": accumulated_depr_amount, + "debit": accumulated_depr_amount, + "cost_center": depreciation_cost_center, + "posting_date": getdate(), + }, + item=asset, + ), ] profit_amount = flt(selling_amount) - flt(value_after_depreciation) if profit_amount: - get_profit_gl_entries(profit_amount, gl_entries, disposal_account, depreciation_cost_center) + get_profit_gl_entries( + asset, profit_amount, gl_entries, disposal_account, depreciation_cost_center + ) + + if voucher_type and voucher_no: + for entry in gl_entries: + entry["voucher_type"] = voucher_type + entry["voucher_no"] = voucher_no return gl_entries @@ -333,15 +367,21 @@ def get_asset_details(asset, finance_book=None): ) -def get_profit_gl_entries(profit_amount, gl_entries, disposal_account, depreciation_cost_center): +def get_profit_gl_entries( + asset, profit_amount, gl_entries, disposal_account, depreciation_cost_center +): debit_or_credit = "debit" if profit_amount < 0 else "credit" gl_entries.append( - { - "account": disposal_account, - "cost_center": depreciation_cost_center, - debit_or_credit: abs(profit_amount), - debit_or_credit + "_in_account_currency": abs(profit_amount), - } + asset.get_gl_dict( + { + "account": disposal_account, + "cost_center": depreciation_cost_center, + debit_or_credit: abs(profit_amount), + debit_or_credit + "_in_account_currency": abs(profit_amount), + "posting_date": getdate(), + }, + item=asset, + ) ) diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py index 2e6f0ad7b0..93194c0333 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py @@ -428,7 +428,11 @@ class AssetCapitalization(StockController): asset.reload() fixed_asset_gl_entries = get_gl_entries_on_asset_disposal( - asset, item.asset_value, item.get("finance_book") or self.get("finance_book") + asset, + item.asset_value, + item.get("finance_book") or self.get("finance_book"), + self.get("doctype"), + self.get("name"), ) asset.db_set("disposal_date", self.posting_date) From 1f6205e1ea569dd25ac298f10812f6e9f44409ef Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 27 Sep 2022 15:37:41 +0530 Subject: [PATCH 24/34] fix: Add return against indexes for POS Invoice --- erpnext/accounts/doctype/pos_invoice/pos_invoice.json | 2 +- erpnext/accounts/doctype/pos_invoice/pos_invoice.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json index b126d57400..6f8b3822c2 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json @@ -1553,7 +1553,7 @@ "icon": "fa fa-file-text", "is_submittable": 1, "links": [], - "modified": "2022-03-22 13:00:24.166684", + "modified": "2022-09-27 13:00:24.166684", "modified_by": "Administrator", "module": "Accounts", "name": "POS Invoice", diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index 6e3a0766f1..a5e4d9a6dd 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -740,3 +740,7 @@ def add_return_modes(doc, pos_profile): ]: payment_mode = get_mode_of_payment_info(mode_of_payment, doc.company) append_payment(payment_mode[0]) + + +def on_doctype_update(): + frappe.db.add_index("POS Invoice", ["customer", "is_return", "return_against"]) From e39e088f18177ab4a189da60c18d57b92a6f8aff Mon Sep 17 00:00:00 2001 From: Maharshi Patel Date: Tue, 27 Sep 2022 15:44:38 +0530 Subject: [PATCH 25/34] fix: POS only validate QTY if is_stock_item POS invoice raised " Item not available " validation error even though item is non_stock. --- erpnext/accounts/doctype/pos_invoice/pos_invoice.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index 6e3a0766f1..539127095e 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -239,14 +239,14 @@ class POSInvoice(SalesInvoice): frappe.bold(d.warehouse), frappe.bold(d.qty), ) - if flt(available_stock) <= 0: + if is_stock_item and flt(available_stock) <= 0: frappe.throw( _("Row #{}: Item Code: {} is not available under warehouse {}.").format( d.idx, item_code, warehouse ), title=_("Item Unavailable"), ) - elif flt(available_stock) < flt(d.qty): + elif is_stock_item and flt(available_stock) < flt(d.qty): frappe.throw( _( "Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. Available quantity {}." From cbfe28286acc9319901aeaa2c77fa50e95c0e481 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 27 Sep 2022 16:22:59 +0530 Subject: [PATCH 26/34] fix: Add return against indexes for POS Invoice --- erpnext/accounts/doctype/pos_invoice/pos_invoice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index a5e4d9a6dd..9eded8a777 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -743,4 +743,4 @@ def add_return_modes(doc, pos_profile): def on_doctype_update(): - frappe.db.add_index("POS Invoice", ["customer", "is_return", "return_against"]) + frappe.db.add_index("POS Invoice", ["return_against"]) From c6a7de0e545746556a7e80e0bd4dcf46159d3f24 Mon Sep 17 00:00:00 2001 From: anandbaburajan Date: Tue, 27 Sep 2022 20:43:08 +0530 Subject: [PATCH 27/34] chore: add blank lines --- erpnext/assets/doctype/asset/depreciation.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index b6e86cdd5a..97941706aa 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -243,6 +243,7 @@ def restore_asset(asset_name): asset.set_status() + def depreciate_asset(asset, date): asset.flags.ignore_validate_update_after_submit = True asset.prepare_depreciation_data(date_of_disposal=date) @@ -327,6 +328,7 @@ def disposal_happens_in_the_future(posting_date_of_disposal): return False + def get_gl_entries_on_asset_regain( asset, selling_amount=0, finance_book=None, voucher_type=None, voucher_no=None ): From 82a2f31ada358d896943d04bdb517ac33b9e2d7a Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 27 Sep 2022 22:12:32 +0530 Subject: [PATCH 28/34] fix: Move subscription process to hourly long quque --- erpnext/hooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index a08feb4447..6ef77f3f5b 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -391,12 +391,12 @@ scheduler_events = { "erpnext.crm.doctype.social_media_post.social_media_post.process_scheduled_social_media_posts", ], "hourly": [ - "erpnext.accounts.doctype.subscription.subscription.process_all", "erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.automatic_synchronization", "erpnext.projects.doctype.project.project.hourly_reminder", "erpnext.projects.doctype.project.project.collect_project_status", ], "hourly_long": [ + "erpnext.accounts.doctype.subscription.subscription.process_all", "erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.repost_entries", "erpnext.bulk_transaction.doctype.bulk_transaction_log.bulk_transaction_log.retry_failing_transaction", ], From e392ea1104fee5add5c893c4e092edb6ad21f486 Mon Sep 17 00:00:00 2001 From: Maharshi Patel Date: Tue, 27 Sep 2022 23:44:56 +0530 Subject: [PATCH 29/34] fix: POS properly validate stock for bundle products Stock availability was not calculated properly for Product Bundle with non stock item so i have added logic to properly calculate that as well. --- erpnext/accounts/doctype/pos_invoice/pos_invoice.py | 9 ++++++--- erpnext/selling/page/point_of_sale/pos_controller.js | 4 ++-- erpnext/selling/page/point_of_sale/pos_item_details.js | 5 +++-- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index 539127095e..54a3e934b2 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -632,11 +632,12 @@ def get_stock_availability(item_code, warehouse): pos_sales_qty = get_pos_reserved_qty(item_code, warehouse) return bin_qty - pos_sales_qty, is_stock_item else: - is_stock_item = False + is_stock_item = True if frappe.db.exists("Product Bundle", item_code): return get_bundle_availability(item_code, warehouse), is_stock_item else: - # Is a service item + is_stock_item = False + # Is a service item or non_stock item return 0, is_stock_item @@ -650,7 +651,9 @@ def get_bundle_availability(bundle_item_code, warehouse): available_qty = item_bin_qty - item_pos_reserved_qty max_available_bundles = available_qty / item.qty - if bundle_bin_qty > max_available_bundles: + if bundle_bin_qty > max_available_bundles and frappe.get_value( + "Item", item.item_code, "is_stock_item" + ): bundle_bin_qty = max_available_bundles pos_sales_qty = get_pos_reserved_qty(bundle_item_code, warehouse) diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js index da7576e08d..24375d8252 100644 --- a/erpnext/selling/page/point_of_sale/pos_controller.js +++ b/erpnext/selling/page/point_of_sale/pos_controller.js @@ -660,7 +660,7 @@ erpnext.PointOfSale.Controller = class { } else { return; } - } else if (available_qty < qty_needed) { + } else if (is_stock_item && available_qty < qty_needed) { frappe.throw({ message: __('Stock quantity not enough for Item Code: {0} under warehouse {1}. Available quantity {2}.', [bold_item_code, bold_warehouse, bold_available_qty]), indicator: 'orange' @@ -694,7 +694,7 @@ erpnext.PointOfSale.Controller = class { callback(res) { if (!me.item_stock_map[item_code]) me.item_stock_map[item_code] = {}; - me.item_stock_map[item_code][warehouse] = res.message[0]; + me.item_stock_map[item_code][warehouse] = res.message; } }); } diff --git a/erpnext/selling/page/point_of_sale/pos_item_details.js b/erpnext/selling/page/point_of_sale/pos_item_details.js index b75ffb235e..f9b5bb2e45 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_details.js +++ b/erpnext/selling/page/point_of_sale/pos_item_details.js @@ -242,13 +242,14 @@ erpnext.PointOfSale.ItemDetails = class { if (this.value) { me.events.form_updated(me.current_item, 'warehouse', this.value).then(() => { me.item_stock_map = me.events.get_item_stock_map(); - const available_qty = me.item_stock_map[me.item_row.item_code] && me.item_stock_map[me.item_row.item_code][this.value]; + const available_qty = me.item_stock_map[me.item_row.item_code][this.value][0]; + const is_stock_item = Boolean(me.item_stock_map[me.item_row.item_code][this.value][1]); if (available_qty === undefined) { me.events.get_available_stock(me.item_row.item_code, this.value).then(() => { // item stock map is updated now reset warehouse me.warehouse_control.set_value(this.value); }) - } else if (available_qty === 0) { + } else if (available_qty === 0 && is_stock_item) { me.warehouse_control.set_value(''); const bold_item_code = me.item_row.item_code.bold(); const bold_warehouse = this.value.bold(); From be623ce8e8e9c25d2327383c057b5e810b28c4a7 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 28 Sep 2022 15:36:59 +0530 Subject: [PATCH 30/34] fix: Disbursement Account in patch to update old loans --- erpnext/patches/v13_0/update_old_loans.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/patches/v13_0/update_old_loans.py b/erpnext/patches/v13_0/update_old_loans.py index a1d40b739e..0bd3fcdec4 100644 --- a/erpnext/patches/v13_0/update_old_loans.py +++ b/erpnext/patches/v13_0/update_old_loans.py @@ -100,6 +100,7 @@ def execute(): "mode_of_payment": loan.mode_of_payment, "loan_account": loan.loan_account, "payment_account": loan.payment_account, + "disbursement_account": loan.payment_account, "interest_income_account": loan.interest_income_account, "penalty_income_account": loan.penalty_income_account, }, @@ -190,6 +191,7 @@ def create_loan_type(loan, loan_type_name, penalty_account): loan_type_doc.company = loan.company loan_type_doc.mode_of_payment = loan.mode_of_payment loan_type_doc.payment_account = loan.payment_account + loan_type_doc.disbursement_account = loan.payment_account loan_type_doc.loan_account = loan.loan_account loan_type_doc.interest_income_account = loan.interest_income_account loan_type_doc.penalty_income_account = penalty_account From 80080a3d7bc2e4a69c7a18742ce89534920ab118 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Wed, 28 Sep 2022 15:40:07 +0530 Subject: [PATCH 31/34] fix: show `Make Purchase Invoice` button based on permission --- erpnext/templates/pages/order.html | 2 +- erpnext/templates/pages/order.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/templates/pages/order.html b/erpnext/templates/pages/order.html index a10870db27..ec1d49788b 100644 --- a/erpnext/templates/pages/order.html +++ b/erpnext/templates/pages/order.html @@ -18,7 +18,7 @@