From f81d4a79eab95148a4a6591bb0920cd2db13afed Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 18 Jan 2023 23:21:29 +0530 Subject: [PATCH 01/23] fix: don't add template item in sales/purchase transaction (cherry picked from commit 2c83fff1a1a4aa7053159f58c68d9ed697a73742) # Conflicts: # erpnext/buying/doctype/purchase_order/test_purchase_order.py --- .../purchase_order/test_purchase_order.py | 123 +++++++++++++++++- erpnext/stock/get_item_details.py | 6 +- 2 files changed, 126 insertions(+), 3 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 590370e808c..06274af5945 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -1239,6 +1239,127 @@ class TestPurchaseOrder(FrappeTestCase): automatically_fetch_payment_terms(enable=0) +<<<<<<< HEAD +======= + def test_internal_transfer_flow(self): + from erpnext.accounts.doctype.sales_invoice.sales_invoice import ( + make_inter_company_purchase_invoice, + ) + from erpnext.selling.doctype.sales_order.sales_order import ( + make_delivery_note, + make_sales_invoice, + ) + from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt + + frappe.db.set_value("Selling Settings", None, "maintain_same_sales_rate", 1) + frappe.db.set_value("Buying Settings", None, "maintain_same_rate", 1) + + prepare_data_for_internal_transfer() + supplier = "_Test Internal Supplier 2" + + mr = make_material_request( + qty=2, company="_Test Company with perpetual inventory", warehouse="Stores - TCP1" + ) + + po = create_purchase_order( + company="_Test Company with perpetual inventory", + supplier=supplier, + warehouse="Stores - TCP1", + from_warehouse="_Test Internal Warehouse New 1 - TCP1", + qty=2, + rate=1, + material_request=mr.name, + material_request_item=mr.items[0].name, + ) + + so = make_inter_company_sales_order(po.name) + so.items[0].delivery_date = today() + self.assertEqual(so.items[0].warehouse, "_Test Internal Warehouse New 1 - TCP1") + self.assertTrue(so.items[0].purchase_order) + self.assertTrue(so.items[0].purchase_order_item) + so.submit() + + dn = make_delivery_note(so.name) + dn.items[0].target_warehouse = "_Test Internal Warehouse GIT - TCP1" + self.assertEqual(dn.items[0].warehouse, "_Test Internal Warehouse New 1 - TCP1") + self.assertTrue(dn.items[0].purchase_order) + self.assertTrue(dn.items[0].purchase_order_item) + + self.assertEqual(po.items[0].name, dn.items[0].purchase_order_item) + dn.submit() + + pr = make_inter_company_purchase_receipt(dn.name) + self.assertEqual(pr.items[0].warehouse, "Stores - TCP1") + self.assertTrue(pr.items[0].purchase_order) + self.assertTrue(pr.items[0].purchase_order_item) + self.assertEqual(po.items[0].name, pr.items[0].purchase_order_item) + pr.submit() + + si = make_sales_invoice(so.name) + self.assertEqual(si.items[0].warehouse, "_Test Internal Warehouse New 1 - TCP1") + self.assertTrue(si.items[0].purchase_order) + self.assertTrue(si.items[0].purchase_order_item) + si.submit() + + pi = make_inter_company_purchase_invoice(si.name) + self.assertTrue(pi.items[0].purchase_order) + self.assertTrue(pi.items[0].po_detail) + pi.submit() + mr.reload() + + po.load_from_db() + self.assertEqual(po.status, "Completed") + self.assertEqual(mr.status, "Received") + + def test_variant_item_po(self): + po = create_purchase_order(item_code="_Test Variant Item", qty=1, rate=100, do_not_save=1) + + self.assertRaises(frappe.ValidationError, po.save) + + +def prepare_data_for_internal_transfer(): + from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier + from erpnext.selling.doctype.customer.test_customer import create_internal_customer + from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt + from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse + + company = "_Test Company with perpetual inventory" + + create_internal_customer( + "_Test Internal Customer 2", + company, + company, + ) + + create_internal_supplier( + "_Test Internal Supplier 2", + company, + company, + ) + + warehouse = create_warehouse("_Test Internal Warehouse New 1", company=company) + + create_warehouse("_Test Internal Warehouse GIT", company=company) + + make_purchase_receipt(company=company, warehouse=warehouse, qty=2, rate=100) + + if not frappe.db.get_value("Company", company, "unrealized_profit_loss_account"): + account = "Unrealized Profit and Loss - TCP1" + if not frappe.db.exists("Account", account): + frappe.get_doc( + { + "doctype": "Account", + "account_name": "Unrealized Profit and Loss", + "parent_account": "Direct Income - TCP1", + "company": company, + "is_group": 0, + "account_type": "Income Account", + } + ).insert() + + frappe.db.set_value("Company", company, "unrealized_profit_loss_account", account) + +>>>>>>> 2c83fff1a1 (fix: don't add template item in sales/purchase transaction) def make_pr_against_po(po, received_qty=0): pr = make_purchase_receipt(po) @@ -1342,8 +1463,8 @@ def create_purchase_order(**args): }, ) - po.set_missing_values() if not args.do_not_save: + po.set_missing_values() po.insert() if not args.do_not_submit: if po.is_subcontracted == "Yes": diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 6fb9205d4b9..15d77544236 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -226,8 +226,10 @@ def validate_item_details(args, item): validate_end_of_life(item.name, item.end_of_life, item.disabled) - if args.transaction_type == "selling" and cint(item.has_variants): - throw(_("Item {0} is a template, please select one of its variants").format(item.name)) + if cint(item.has_variants): + msg = f"Item {item.name} is a template, please select one of its variants" + + throw(_(msg), title=_("Template Item Selected")) elif args.transaction_type == "buying" and args.doctype != "Material Request": if args.get("is_subcontracted") == "Yes" and item.is_sub_contracted_item != 1: From 4741ce13c630967e7eb06112c096c2f2349d5027 Mon Sep 17 00:00:00 2001 From: unknown <57280279+SvbZ3r0@users.noreply.github.com> Date: Thu, 12 Jan 2023 07:44:57 +0530 Subject: [PATCH 02/23] fix: rewrite logic for duplicate check in Item Attribute Previously, Item Attribute values were not checked for case-insensitive duplicates, and Item tttribute abbreviations were forced to be uppercase. This commit fixes both problems. (cherry picked from commit 974e12c8378c729647965bdfd65f52f89154609b) --- .../stock/doctype/item_attribute/item_attribute.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/erpnext/stock/doctype/item_attribute/item_attribute.py b/erpnext/stock/doctype/item_attribute/item_attribute.py index 391ff06918a..018d5257e1d 100644 --- a/erpnext/stock/doctype/item_attribute/item_attribute.py +++ b/erpnext/stock/doctype/item_attribute/item_attribute.py @@ -74,11 +74,12 @@ class ItemAttribute(Document): def validate_duplication(self): values, abbrs = [], [] for d in self.item_attribute_values: - d.abbr = d.abbr.upper() - if d.attribute_value in values: - frappe.throw(_("{0} must appear only once").format(d.attribute_value)) + if d.attribute_value.lower() in map(str.lower, values): + frappe.throw( + _("Attribute value: {0} must appear only once").format(d.attribute_value.title())) values.append(d.attribute_value) - if d.abbr in abbrs: - frappe.throw(_("{0} must appear only once").format(d.abbr)) + if d.abbr.lower() in map(str.lower, abbrs): + frappe.throw( + _("Abbreviation: {0} must appear only once").format(d.abbr.title())) abbrs.append(d.abbr) From 13906cba9a412a3cc0afd698be932616346f58d7 Mon Sep 17 00:00:00 2001 From: unknown <57280279+SvbZ3r0@users.noreply.github.com> Date: Thu, 12 Jan 2023 20:53:12 +0530 Subject: [PATCH 03/23] fix: linting (cherry picked from commit 2ca4d3fb71587bae26f23f2c748884cc1b27b744) --- erpnext/stock/doctype/item_attribute/item_attribute.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/item_attribute/item_attribute.py b/erpnext/stock/doctype/item_attribute/item_attribute.py index 018d5257e1d..ac4c313e28a 100644 --- a/erpnext/stock/doctype/item_attribute/item_attribute.py +++ b/erpnext/stock/doctype/item_attribute/item_attribute.py @@ -75,11 +75,9 @@ class ItemAttribute(Document): values, abbrs = [], [] for d in self.item_attribute_values: if d.attribute_value.lower() in map(str.lower, values): - frappe.throw( - _("Attribute value: {0} must appear only once").format(d.attribute_value.title())) + frappe.throw(_("Attribute value: {0} must appear only once").format(d.attribute_value.title())) values.append(d.attribute_value) if d.abbr.lower() in map(str.lower, abbrs): - frappe.throw( - _("Abbreviation: {0} must appear only once").format(d.abbr.title())) + frappe.throw(_("Abbreviation: {0} must appear only once").format(d.abbr.title())) abbrs.append(d.abbr) From 055f8536c304e750926b6b44c91b83c013dfae24 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Thu, 19 Jan 2023 16:57:17 +0530 Subject: [PATCH 04/23] fix: conflicts --- .../purchase_order/test_purchase_order.py | 116 ------------------ 1 file changed, 116 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 06274af5945..2e6df8525cc 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -1239,128 +1239,12 @@ class TestPurchaseOrder(FrappeTestCase): automatically_fetch_payment_terms(enable=0) -<<<<<<< HEAD -======= - def test_internal_transfer_flow(self): - from erpnext.accounts.doctype.sales_invoice.sales_invoice import ( - make_inter_company_purchase_invoice, - ) - from erpnext.selling.doctype.sales_order.sales_order import ( - make_delivery_note, - make_sales_invoice, - ) - from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt - - frappe.db.set_value("Selling Settings", None, "maintain_same_sales_rate", 1) - frappe.db.set_value("Buying Settings", None, "maintain_same_rate", 1) - - prepare_data_for_internal_transfer() - supplier = "_Test Internal Supplier 2" - - mr = make_material_request( - qty=2, company="_Test Company with perpetual inventory", warehouse="Stores - TCP1" - ) - - po = create_purchase_order( - company="_Test Company with perpetual inventory", - supplier=supplier, - warehouse="Stores - TCP1", - from_warehouse="_Test Internal Warehouse New 1 - TCP1", - qty=2, - rate=1, - material_request=mr.name, - material_request_item=mr.items[0].name, - ) - - so = make_inter_company_sales_order(po.name) - so.items[0].delivery_date = today() - self.assertEqual(so.items[0].warehouse, "_Test Internal Warehouse New 1 - TCP1") - self.assertTrue(so.items[0].purchase_order) - self.assertTrue(so.items[0].purchase_order_item) - so.submit() - - dn = make_delivery_note(so.name) - dn.items[0].target_warehouse = "_Test Internal Warehouse GIT - TCP1" - self.assertEqual(dn.items[0].warehouse, "_Test Internal Warehouse New 1 - TCP1") - self.assertTrue(dn.items[0].purchase_order) - self.assertTrue(dn.items[0].purchase_order_item) - - self.assertEqual(po.items[0].name, dn.items[0].purchase_order_item) - dn.submit() - - pr = make_inter_company_purchase_receipt(dn.name) - self.assertEqual(pr.items[0].warehouse, "Stores - TCP1") - self.assertTrue(pr.items[0].purchase_order) - self.assertTrue(pr.items[0].purchase_order_item) - self.assertEqual(po.items[0].name, pr.items[0].purchase_order_item) - pr.submit() - - si = make_sales_invoice(so.name) - self.assertEqual(si.items[0].warehouse, "_Test Internal Warehouse New 1 - TCP1") - self.assertTrue(si.items[0].purchase_order) - self.assertTrue(si.items[0].purchase_order_item) - si.submit() - - pi = make_inter_company_purchase_invoice(si.name) - self.assertTrue(pi.items[0].purchase_order) - self.assertTrue(pi.items[0].po_detail) - pi.submit() - mr.reload() - - po.load_from_db() - self.assertEqual(po.status, "Completed") - self.assertEqual(mr.status, "Received") - def test_variant_item_po(self): po = create_purchase_order(item_code="_Test Variant Item", qty=1, rate=100, do_not_save=1) self.assertRaises(frappe.ValidationError, po.save) -def prepare_data_for_internal_transfer(): - from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier - from erpnext.selling.doctype.customer.test_customer import create_internal_customer - from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt - from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse - - company = "_Test Company with perpetual inventory" - - create_internal_customer( - "_Test Internal Customer 2", - company, - company, - ) - - create_internal_supplier( - "_Test Internal Supplier 2", - company, - company, - ) - - warehouse = create_warehouse("_Test Internal Warehouse New 1", company=company) - - create_warehouse("_Test Internal Warehouse GIT", company=company) - - make_purchase_receipt(company=company, warehouse=warehouse, qty=2, rate=100) - - if not frappe.db.get_value("Company", company, "unrealized_profit_loss_account"): - account = "Unrealized Profit and Loss - TCP1" - if not frappe.db.exists("Account", account): - frappe.get_doc( - { - "doctype": "Account", - "account_name": "Unrealized Profit and Loss", - "parent_account": "Direct Income - TCP1", - "company": company, - "is_group": 0, - "account_type": "Income Account", - } - ).insert() - - frappe.db.set_value("Company", company, "unrealized_profit_loss_account", account) - ->>>>>>> 2c83fff1a1 (fix: don't add template item in sales/purchase transaction) - def make_pr_against_po(po, received_qty=0): pr = make_purchase_receipt(po) pr.get("items")[0].qty = received_qty or 5 From 1c5c06716b63bbfe9a7e0d56d63473074405c2b6 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 17 Jan 2023 11:32:06 +0530 Subject: [PATCH 05/23] fix: patch item_reposting_for_incorrect_sl_and_gl (cherry picked from commit dbde3a34210bbf63ea5647d46e3fc74c8f69ca97) # Conflicts: # erpnext/patches.txt --- erpnext/patches.txt | 11 +++++++++++ .../v13_0/item_reposting_for_incorrect_sl_and_gl.py | 1 + 2 files changed, 12 insertions(+) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 47277cc48ce..5123b912f5e 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -259,8 +259,11 @@ erpnext.patches.v13_0.convert_qi_parameter_to_link_field erpnext.patches.v13_0.setup_patient_history_settings_for_standard_doctypes erpnext.patches.v13_0.add_naming_series_to_old_projects # 1-02-2021 erpnext.patches.v13_0.update_payment_terms_outstanding +<<<<<<< HEAD erpnext.patches.v12_0.add_state_code_for_ladakh erpnext.patches.v13_0.item_reposting_for_incorrect_sl_and_gl +======= +>>>>>>> dbde3a3421 (fix: patch item_reposting_for_incorrect_sl_and_gl) erpnext.patches.v13_0.delete_old_bank_reconciliation_doctypes erpnext.patches.v13_0.update_vehicle_no_reqd_condition erpnext.patches.v12_0.add_einvoice_status_field #2021-03-17 @@ -352,6 +355,14 @@ erpnext.patches.v13_0.amazon_mws_deprecation_warning erpnext.patches.v13_0.datev_deprecation_warning erpnext.patches.v13_0.set_work_order_qty_in_so_from_mr erpnext.patches.v13_0.update_accounts_in_loan_docs +<<<<<<< HEAD +======= +erpnext.patches.v13_0.item_reposting_for_incorrect_sl_and_gl +erpnext.patches.v14_0.update_batch_valuation_flag +erpnext.patches.v14_0.delete_non_profit_doctypes +erpnext.patches.v13_0.add_cost_center_in_loans +erpnext.patches.v13_0.set_return_against_in_pos_invoice_references +>>>>>>> dbde3a3421 (fix: patch item_reposting_for_incorrect_sl_and_gl) erpnext.patches.v13_0.remove_unknown_links_to_prod_plan_items # 24-03-2022 erpnext.patches.v13_0.rename_non_profit_fields erpnext.patches.v13_0.enable_ksa_vat_docs #1 diff --git a/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py b/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py index f6427ca55a6..bf48d3ab04a 100644 --- a/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py +++ b/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py @@ -7,6 +7,7 @@ from erpnext.stock.stock_ledger import update_entries_after def execute(): doctypes_to_reload = [ + ("setup", "company"), ("stock", "repost_item_valuation"), ("stock", "stock_entry_detail"), ("stock", "purchase_receipt_item"), From d717ca0325a09780212fd1fe63dad5c41a3f456f Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Fri, 20 Jan 2023 14:05:14 +0530 Subject: [PATCH 06/23] fix: conflicts --- erpnext/patches.txt | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 5123b912f5e..f49e06675dd 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -259,11 +259,8 @@ erpnext.patches.v13_0.convert_qi_parameter_to_link_field erpnext.patches.v13_0.setup_patient_history_settings_for_standard_doctypes erpnext.patches.v13_0.add_naming_series_to_old_projects # 1-02-2021 erpnext.patches.v13_0.update_payment_terms_outstanding -<<<<<<< HEAD erpnext.patches.v12_0.add_state_code_for_ladakh erpnext.patches.v13_0.item_reposting_for_incorrect_sl_and_gl -======= ->>>>>>> dbde3a3421 (fix: patch item_reposting_for_incorrect_sl_and_gl) erpnext.patches.v13_0.delete_old_bank_reconciliation_doctypes erpnext.patches.v13_0.update_vehicle_no_reqd_condition erpnext.patches.v12_0.add_einvoice_status_field #2021-03-17 @@ -355,14 +352,6 @@ erpnext.patches.v13_0.amazon_mws_deprecation_warning erpnext.patches.v13_0.datev_deprecation_warning erpnext.patches.v13_0.set_work_order_qty_in_so_from_mr erpnext.patches.v13_0.update_accounts_in_loan_docs -<<<<<<< HEAD -======= -erpnext.patches.v13_0.item_reposting_for_incorrect_sl_and_gl -erpnext.patches.v14_0.update_batch_valuation_flag -erpnext.patches.v14_0.delete_non_profit_doctypes -erpnext.patches.v13_0.add_cost_center_in_loans -erpnext.patches.v13_0.set_return_against_in_pos_invoice_references ->>>>>>> dbde3a3421 (fix: patch item_reposting_for_incorrect_sl_and_gl) erpnext.patches.v13_0.remove_unknown_links_to_prod_plan_items # 24-03-2022 erpnext.patches.v13_0.rename_non_profit_fields erpnext.patches.v13_0.enable_ksa_vat_docs #1 @@ -385,4 +374,4 @@ erpnext.patches.v13_0.reset_corrupt_defaults erpnext.patches.v13_0.show_hr_payroll_deprecation_warning erpnext.patches.v13_0.create_accounting_dimensions_for_asset_repair execute:frappe.db.set_value("Naming Series", "Naming Series", {"select_doc_for_series": "", "set_options": "", "prefix": "", "current_value": 0, "user_must_always_select": 0}) -erpnext.patches.v13_0.update_schedule_type_in_loans \ No newline at end of file +erpnext.patches.v13_0.update_schedule_type_in_loans From ae031cea6313628a404cf5314cf60d91a9be9803 Mon Sep 17 00:00:00 2001 From: anandbaburajan Date: Fri, 20 Jan 2023 15:39:01 +0530 Subject: [PATCH 07/23] fix: fb issue in asset chart --- erpnext/assets/doctype/asset/asset.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index 1473b79bea5..587cb147b79 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -202,6 +202,10 @@ frappe.ui.form.on('Asset', { }, setup_chart: function(frm) { + if(frm.doc.finance_books.length > 1) { + return + } + var x_intervals = [frm.doc.purchase_date]; var asset_values = [frm.doc.gross_purchase_amount]; var last_depreciation_date = frm.doc.purchase_date; From 2da543ebd4a15ef48d9bdb1c81f456b50bda0137 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 20 Jan 2023 19:26:05 +0530 Subject: [PATCH 08/23] fix(ecommerce): breadcrumb: fallback to `/all-products` (#33718) fix(ecommerce): breadcrumb: fallback to `/all-products` (#33718) * fix(ecommerce): breadcrumb: fallback to `/all-products` * fix(item_group): use `==` instead of `is` * test(ecommerce): breadcrumb (cherry picked from commit a94aa7a79f3569ecb925f0d2d346dd3aa0ede8df) Co-authored-by: Sabu Siyad --- .../e_commerce/doctype/website_item/test_website_item.py | 7 +++++-- erpnext/setup/doctype/item_group/item_group.py | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/erpnext/e_commerce/doctype/website_item/test_website_item.py b/erpnext/e_commerce/doctype/website_item/test_website_item.py index 18e18dd31a9..ebf01bf43fb 100644 --- a/erpnext/e_commerce/doctype/website_item/test_website_item.py +++ b/erpnext/e_commerce/doctype/website_item/test_website_item.py @@ -173,7 +173,10 @@ class TestWebsiteItem(unittest.TestCase): # Website Item Portal Tests Begin def test_website_item_breadcrumbs(self): - "Check if breadcrumbs include homepage, product listing navigation page, parent item group(s) and item group." + """ + Check if breadcrumbs include homepage, product listing navigation page, + parent item group(s) and item group + """ from erpnext.setup.doctype.item_group.item_group import get_parent_item_groups item_code = "Test Breadcrumb Item" @@ -196,7 +199,7 @@ class TestWebsiteItem(unittest.TestCase): breadcrumbs = get_parent_item_groups(item.item_group) self.assertEqual(breadcrumbs[0]["name"], "Home") - self.assertEqual(breadcrumbs[1]["name"], "Shop by Category") + self.assertEqual(breadcrumbs[1]["name"], "All Products") self.assertEqual(breadcrumbs[2]["name"], "_Test Item Group B") # parent item group self.assertEqual(breadcrumbs[3]["name"], "_Test Item Group B - 1") diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py index b0ff1ccfcd7..a11cfc3407a 100644 --- a/erpnext/setup/doctype/item_group/item_group.py +++ b/erpnext/setup/doctype/item_group/item_group.py @@ -149,12 +149,12 @@ def get_item_for_list_in_html(context): def get_parent_item_groups(item_group_name, from_item=False): - base_nav_page = {"name": _("Shop by Category"), "route": "/shop-by-category"} + base_nav_page = {"name": _("All Products"), "route": "/all-products"} if from_item and frappe.request.environ.get("HTTP_REFERER"): # base page after 'Home' will vary on Item page last_page = frappe.request.environ["HTTP_REFERER"].split("/")[-1].split("?")[0] - if last_page and last_page in ("shop-by-category", "all-products"): + if last_page and last_page == "shop-by-category": base_nav_page_title = " ".join(last_page.split("-")).title() base_nav_page = {"name": _(base_nav_page_title), "route": "/" + last_page} From af3a0e56f6147e8ef816a135f1dcd6a54ebd3d0d Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Fri, 20 Jan 2023 18:22:33 +0100 Subject: [PATCH 09/23] chore: bump python version for docs-checker (#33756) --- .github/workflows/docs-checker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs-checker.yml b/.github/workflows/docs-checker.yml index db46c5621b2..722c1252ed9 100644 --- a/.github/workflows/docs-checker.yml +++ b/.github/workflows/docs-checker.yml @@ -12,7 +12,7 @@ jobs: - name: 'Setup Environment' uses: actions/setup-python@v2 with: - python-version: 3.6 + python-version: '3.10' - name: 'Clone repo' uses: actions/checkout@v2 From d6504320b152629c22b63488a29fb019e78014ea Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 20 Jan 2023 23:38:37 +0530 Subject: [PATCH 10/23] fix: calculate correct amount for qty == 0 (backport #33739) (#33752) fix: calculate correct amount for qty == 0 (#33739) (cherry picked from commit 327b6fdb32c07491058002abe4d06f2d45060061) Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> --- .../public/js/controllers/taxes_and_totals.js | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index c75e7b77f70..a9f1ff684f8 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -115,24 +115,16 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ calculate_item_values: function() { let me = this; if (!this.discount_amount_applied) { - $.each(this.frm.doc["items"] || [], function(i, item) { + for (item of this.frm.doc.items || []) { frappe.model.round_floats_in(item); item.net_rate = item.rate; - - if ((!item.qty) && me.frm.doc.is_return) { - item.amount = flt(item.rate * -1, precision("amount", item)); - } else if ((!item.qty) && me.frm.doc.is_debit_note) { - item.amount = flt(item.rate, precision("amount", item)); - } else { - item.amount = flt(item.rate * item.qty, precision("amount", item)); - } - - item.net_amount = item.amount; + item.qty = item.qty === undefined ? (me.frm.doc.is_return ? -1 : 1) : item.qty; + item.net_amount = item.amount = flt(item.rate * item.qty, precision("amount", item)); item.item_tax_amount = 0.0; item.total_weight = flt(item.weight_per_unit * item.stock_qty); me.set_in_company_currency(item, ["price_list_rate", "rate", "amount", "net_rate", "net_amount"]); - }); + } } }, From 09e13d279c1a4dbaab5924bb4e877c33bfa06984 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 20 Jan 2023 22:38:56 +0530 Subject: [PATCH 11/23] fix: incorrect actual qty for the packed item (cherry picked from commit 02566a02a8a9768d7a48846a9ea49c32daf467a7) --- .../sales_invoice/test_sales_invoice.py | 40 +++++++++++++++++++ erpnext/controllers/selling_controller.py | 2 +- .../doctype/sales_order/test_sales_order.py | 36 +++++++++++++++++ .../delivery_note/test_delivery_note.py | 40 +++++++++++++++++++ 4 files changed, 117 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 79a67b83631..6035e86d067 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1117,6 +1117,46 @@ class TestSalesInvoice(unittest.TestCase): frappe.db.sql("delete from `tabPOS Profile`") + def test_bin_details_of_packed_item(self): + from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle + from erpnext.stock.doctype.item.test_item import make_item + + # test Update Items with product bundle + if not frappe.db.exists("Item", "_Test Product Bundle Item New"): + bundle_item = make_item("_Test Product Bundle Item New", {"is_stock_item": 0}) + bundle_item.append( + "item_defaults", {"company": "_Test Company", "default_warehouse": "_Test Warehouse - _TC"} + ) + bundle_item.save(ignore_permissions=True) + + make_item("_Packed Item New 1", {"is_stock_item": 1}) + make_product_bundle("_Test Product Bundle Item New", ["_Packed Item New 1"], 2) + + si = create_sales_invoice( + item_code="_Test Product Bundle Item New", + update_stock=1, + warehouse="_Test Warehouse - _TC", + transaction_date=add_days(nowdate(), -1), + do_not_submit=1, + ) + + make_stock_entry(item="_Packed Item New 1", target="_Test Warehouse - _TC", qty=120, rate=100) + + bin_details = frappe.db.get_value( + "Bin", + {"item_code": "_Packed Item New 1", "warehouse": "_Test Warehouse - _TC"}, + ["actual_qty", "projected_qty", "ordered_qty"], + as_dict=1, + ) + + si.transaction_date = nowdate() + si.save() + + packed_item = si.packed_items[0] + self.assertEqual(flt(bin_details.actual_qty), flt(packed_item.actual_qty)) + self.assertEqual(flt(bin_details.projected_qty), flt(packed_item.projected_qty)) + self.assertEqual(flt(bin_details.ordered_qty), flt(packed_item.ordered_qty)) + def test_pos_si_without_payment(self): make_pos_profile() diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 57f8a3e1513..c52a2dfa95b 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -25,7 +25,7 @@ class SellingController(StockController): def onload(self): super(SellingController, self).onload() if self.doctype in ("Sales Order", "Delivery Note", "Sales Invoice"): - for item in self.get("items"): + for item in self.get("items") + (self.get("packed_items") or []): item.update(get_bin_details(item.item_code, item.warehouse, include_child_warehouses=True)) def validate(self): diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 8bb25d4538c..a1c6cb5cd6c 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -546,6 +546,42 @@ class TestSalesOrder(FrappeTestCase): workflow.is_active = 0 workflow.save() + def test_bin_details_of_packed_item(self): + # test Update Items with product bundle + if not frappe.db.exists("Item", "_Test Product Bundle Item New"): + bundle_item = make_item("_Test Product Bundle Item New", {"is_stock_item": 0}) + bundle_item.append( + "item_defaults", {"company": "_Test Company", "default_warehouse": "_Test Warehouse - _TC"} + ) + bundle_item.save(ignore_permissions=True) + + make_item("_Packed Item New 1", {"is_stock_item": 1}) + make_product_bundle("_Test Product Bundle Item New", ["_Packed Item New 1"], 2) + + so = make_sales_order( + item_code="_Test Product Bundle Item New", + warehouse="_Test Warehouse - _TC", + transaction_date=add_days(nowdate(), -1), + do_not_submit=1, + ) + + make_stock_entry(item="_Packed Item New 1", target="_Test Warehouse - _TC", qty=120, rate=100) + + bin_details = frappe.db.get_value( + "Bin", + {"item_code": "_Packed Item New 1", "warehouse": "_Test Warehouse - _TC"}, + ["actual_qty", "projected_qty", "ordered_qty"], + as_dict=1, + ) + + so.transaction_date = nowdate() + so.save() + + packed_item = so.packed_items[0] + self.assertEqual(flt(bin_details.actual_qty), flt(packed_item.actual_qty)) + self.assertEqual(flt(bin_details.projected_qty), flt(packed_item.projected_qty)) + self.assertEqual(flt(bin_details.ordered_qty), flt(packed_item.ordered_qty)) + def test_update_child_product_bundle(self): # test Update Items with product bundle if not frappe.db.exists("Item", "_Product Bundle Item"): diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index d747383d6a5..6847c78d7c8 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -490,6 +490,46 @@ class TestDeliveryNote(FrappeTestCase): self.assertEqual(gle_warehouse_amount, 1400) + def test_bin_details_of_packed_item(self): + from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle + from erpnext.stock.doctype.item.test_item import make_item + + # test Update Items with product bundle + if not frappe.db.exists("Item", "_Test Product Bundle Item New"): + bundle_item = make_item("_Test Product Bundle Item New", {"is_stock_item": 0}) + bundle_item.append( + "item_defaults", {"company": "_Test Company", "default_warehouse": "_Test Warehouse - _TC"} + ) + bundle_item.save(ignore_permissions=True) + + make_item("_Packed Item New 1", {"is_stock_item": 1}) + make_product_bundle("_Test Product Bundle Item New", ["_Packed Item New 1"], 2) + + si = create_delivery_note( + item_code="_Test Product Bundle Item New", + update_stock=1, + warehouse="_Test Warehouse - _TC", + transaction_date=add_days(nowdate(), -1), + do_not_submit=1, + ) + + make_stock_entry(item="_Packed Item New 1", target="_Test Warehouse - _TC", qty=120, rate=100) + + bin_details = frappe.db.get_value( + "Bin", + {"item_code": "_Packed Item New 1", "warehouse": "_Test Warehouse - _TC"}, + ["actual_qty", "projected_qty", "ordered_qty"], + as_dict=1, + ) + + si.transaction_date = nowdate() + si.save() + + packed_item = si.packed_items[0] + self.assertEqual(flt(bin_details.actual_qty), flt(packed_item.actual_qty)) + self.assertEqual(flt(bin_details.projected_qty), flt(packed_item.projected_qty)) + self.assertEqual(flt(bin_details.ordered_qty), flt(packed_item.ordered_qty)) + def test_return_for_serialized_items(self): se = make_serialized_item() serial_no = get_serial_nos(se.get("items")[0].serial_no)[0] From 3daaa021eb618333f51398c44366747c2d8e9904 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 20 Jan 2023 23:41:14 +0530 Subject: [PATCH 12/23] fix: Short closed order, receipt and delivery note status on cancellation (#33743) fix: Short closed order, receipt, and delivery note status on cancellation (#33743) --- erpnext/controllers/status_updater.py | 8 ++++---- erpnext/stock/doctype/delivery_note/test_delivery_note.py | 5 +++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index 62e90ae747d..23dad95fa0d 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -58,7 +58,7 @@ status_map = { "eval:(self.per_delivered == 100 or self.skip_delivery_note) and self.per_billed == 100 and self.docstatus == 1", ], ["Cancelled", "eval:self.docstatus==2"], - ["Closed", "eval:self.status=='Closed'"], + ["Closed", "eval:self.status=='Closed' and self.docstatus != 2"], ["On Hold", "eval:self.status=='On Hold'"], ], "Purchase Order": [ @@ -79,7 +79,7 @@ status_map = { ["Delivered", "eval:self.status=='Delivered'"], ["Cancelled", "eval:self.docstatus==2"], ["On Hold", "eval:self.status=='On Hold'"], - ["Closed", "eval:self.status=='Closed'"], + ["Closed", "eval:self.status=='Closed' and self.docstatus != 2"], ], "Delivery Note": [ ["Draft", None], @@ -87,7 +87,7 @@ status_map = { ["Return Issued", "eval:self.per_returned == 100 and self.docstatus == 1"], ["Completed", "eval:self.per_billed == 100 and self.docstatus == 1"], ["Cancelled", "eval:self.docstatus==2"], - ["Closed", "eval:self.status=='Closed'"], + ["Closed", "eval:self.status=='Closed' and self.docstatus != 2"], ], "Purchase Receipt": [ ["Draft", None], @@ -95,7 +95,7 @@ status_map = { ["Return Issued", "eval:self.per_returned == 100 and self.docstatus == 1"], ["Completed", "eval:self.per_billed == 100 and self.docstatus == 1"], ["Cancelled", "eval:self.docstatus==2"], - ["Closed", "eval:self.status=='Closed'"], + ["Closed", "eval:self.status=='Closed' and self.docstatus != 2"], ], "Material Request": [ ["Draft", None], diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index d747383d6a5..b2ea0831758 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -650,6 +650,11 @@ class TestDeliveryNote(FrappeTestCase): update_delivery_note_status(dn.name, "Closed") self.assertEqual(frappe.db.get_value("Delivery Note", dn.name, "Status"), "Closed") + # Check cancelling closed delivery note + dn.load_from_db() + dn.cancel() + self.assertEqual(dn.status, "Cancelled") + def test_dn_billing_status_case1(self): # SO -> DN -> SI so = make_sales_order() From 4511d413298738d3d89896bd67350831150f2294 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sat, 21 Jan 2023 12:03:33 +0530 Subject: [PATCH 13/23] Removed an unnecessary check in code which always evaluates to true (#33710) fix: removed an unnecessary check which always evaluates to true --- .../deferred_revenue_and_expense.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py index 1eb257ac853..7d2db26c25f 100644 --- a/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py +++ b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py @@ -378,15 +378,14 @@ class Deferred_Revenue_and_Expense_Report(object): ret += [{}] # add total row - if ret is not []: - if self.filters.type == "Revenue": - total_row = frappe._dict({"name": "Total Deferred Income"}) - elif self.filters.type == "Expense": - total_row = frappe._dict({"name": "Total Deferred Expense"}) + if self.filters.type == "Revenue": + total_row = frappe._dict({"name": "Total Deferred Income"}) + elif self.filters.type == "Expense": + total_row = frappe._dict({"name": "Total Deferred Expense"}) - for idx, period in enumerate(self.period_list, 0): - total_row[period.key] = self.period_total[idx].total - ret.append(total_row) + for idx, period in enumerate(self.period_list, 0): + total_row[period.key] = self.period_total[idx].total + ret.append(total_row) return ret From 47e500c2ebf7dd7185e7b4dfc607971d9622e2da Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sat, 21 Jan 2023 15:44:27 +0530 Subject: [PATCH 14/23] fix(minor): Label updates in Statement of Accounts (#33639) fix(minor): Label updates in Statement of Accounts (#33639) --- .../process_statement_of_accounts.html | 1 - erpnext/accounts/report/general_ledger/general_ledger.html | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html index 0da44a464e7..3920d4cf096 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html @@ -49,7 +49,6 @@
{% endif %} - {{ _("Against") }}: {{ row.against }}
{{ _("Remarks") }}: {{ row.remarks }} {% if row.bill_no %}
{{ _("Supplier Invoice No") }}: {{ row.bill_no }} diff --git a/erpnext/accounts/report/general_ledger/general_ledger.html b/erpnext/accounts/report/general_ledger/general_ledger.html index 378fa3791c1..1aad36ca909 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.html +++ b/erpnext/accounts/report/general_ledger/general_ledger.html @@ -25,8 +25,8 @@ {%= __("Date") %} - {%= __("Ref") %} - {%= __("Party") %} + {%= __("Reference") %} + {%= __("Remarks") %} {%= __("Debit") %} {%= __("Credit") %} {%= __("Balance (Dr - Cr)") %} @@ -45,7 +45,6 @@
{% } %} - {{ __("Against") }}: {%= data[i].against %}
{%= __("Remarks") %}: {%= data[i].remarks %} {% if(data[i].bill_no) { %}
{%= __("Supplier Invoice No") %}: {%= data[i].bill_no %} From 7174a2cd933e177fb2903fd96b861bae21212318 Mon Sep 17 00:00:00 2001 From: anandbaburajan Date: Sat, 21 Jan 2023 18:41:19 +0530 Subject: [PATCH 15/23] fix: incorrect row order and accumulated_depreciation when schedule with multiple FBs is scrapped --- erpnext/assets/doctype/asset/asset.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 59187d36865..7a0ff9ce8b8 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -364,6 +364,9 @@ class Asset(AccountsController): }, ) + if len(self.get("finance_books")) > 1 and any(start): + self.sort_depreciation_schedule() + # depreciation schedules need to be cleared before modification due to increase in asset life/asset sales # JE: Journal Entry, FB: Finance Book def clear_depreciation_schedule(self): @@ -399,6 +402,14 @@ class Asset(AccountsController): return start + def sort_depreciation_schedule(self): + self.schedules = sorted( + self.schedules, key=lambda s: (int(s.finance_book_id), getdate(s.schedule_date)) + ) + + for idx, s in enumerate(self.schedules, 1): + s.idx = idx + def get_from_date(self, finance_book): if not self.get("schedules"): return self.available_for_use_date From d6913fffe65c139273b75b36f095557227bfa223 Mon Sep 17 00:00:00 2001 From: anandbaburajan Date: Sat, 21 Jan 2023 19:29:39 +0530 Subject: [PATCH 16/23] fix: backport of #32226 --- .../doctype/sales_invoice/sales_invoice.py | 117 ++--------------- erpnext/assets/doctype/asset/depreciation.py | 100 ++++++++++++++- erpnext/assets/doctype/asset/test_asset.py | 118 ++++++++++++++---- 3 files changed, 199 insertions(+), 136 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 72e9790700f..dde3947bd9d 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -7,17 +7,7 @@ from frappe import _, msgprint, throw from frappe.contacts.doctype.address.address import get_address_display from frappe.model.mapper import get_mapped_doc from frappe.model.utils import get_fetch_values -from frappe.utils import ( - add_days, - add_months, - cint, - cstr, - flt, - formatdate, - get_link_to_form, - getdate, - nowdate, -) +from frappe.utils import add_days, cint, cstr, flt, formatdate, get_link_to_form, getdate, nowdate from six import iteritems import erpnext @@ -33,10 +23,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, - make_depreciation_entry, + 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 @@ -1114,18 +1106,20 @@ class SalesInvoice(SellingController): asset = self.get_asset(item) if self.is_return: - if asset.calculate_depreciation: - self.reverse_depreciation_entry_made_after_sale(asset) - self.reset_depreciation_schedule(asset) - fixed_asset_gl_entries = get_gl_entries_on_asset_regain( asset, item.base_net_amount, item.finance_book ) asset.db_set("disposal_date", None) + if asset.calculate_depreciation: + 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: - self.depreciate_asset(asset) + 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 @@ -1193,95 +1187,6 @@ class SalesInvoice(SellingController): _("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_sale=self.posting_date) - asset.save() - - make_depreciation_entry(asset.name, self.posting_date) - asset.load_from_db() - - 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() - asset.load_from_db() - - 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_sale(self, asset): - from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry - - posting_date_of_original_invoice = self.get_posting_date_of_sales_invoice() - - 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_invoice: - if not self.sale_was_made_on_original_schedule_date( - asset, schedule, row, posting_date_of_original_invoice - ) or self.sale_happens_in_the_future(posting_date_of_original_invoice): - - 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 - depreciation_amount = self.get_depreciation_amount_in_je(reverse_journal_entry) - asset.finance_books[0].value_after_depreciation += depreciation_amount - asset.save() - - def get_posting_date_of_sales_invoice(self): - return frappe.db.get_value("Sales Invoice", self.return_against, "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 sale_was_made_on_original_schedule_date( - self, asset, schedule, row, posting_date_of_original_invoice - ): - 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_original_invoice: - return True - return False - - def sale_happens_in_the_future(self, posting_date_of_original_invoice): - if posting_date_of_original_invoice > getdate(): - return True - - return False - - def get_depreciation_amount_in_je(self, 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 - @property def enable_discount_accounting(self): if not hasattr(self, "_enable_discount_accounting"): diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index f8f581d8ae2..efe780278a1 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -4,12 +4,13 @@ import frappe from frappe import _ -from frappe.utils import cint, flt, get_link_to_form, getdate, today +from frappe.utils import add_months, cint, flt, get_link_to_form, getdate, nowdate, today from frappe.utils.user import get_users_with_role 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): @@ -247,6 +248,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" ) @@ -254,7 +260,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) @@ -265,7 +271,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") @@ -276,6 +282,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) @@ -286,6 +295,91 @@ 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 + 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"): + 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/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 26db6396df3..d133cebedb8 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, update_maintenance_status @@ -153,28 +162,59 @@ 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)) + 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() + + 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, get_last_day(add_months(purchase_date, 1)), date + ) + pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount")) + self.assertEquals( + accumulated_depr_amount, + flt(18000.0 + pro_rata_amount, asset.precision("gross_purchase_amount")), + ) + self.assertEqual(asset.status, "Scrapped") self.assertTrue(asset.journal_entry_for_scrap) expected_gle = ( - ("_Test Accumulated Depreciations - _TC", 36000.0, 0.0), + ( + "_Test Accumulated Depreciations - _TC", + flt(18000.0 + pro_rata_amount, asset.precision("gross_purchase_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", + flt(82000.0 - pro_rata_amount, asset.precision("gross_purchase_amount")), + 0.0, + ), ) gle = frappe.db.sql( @@ -183,7 +223,7 @@ class TestAsset(AssetSetup): order by account""", asset.journal_entry_for_scrap, ) - self.assertEqual(gle, expected_gle) + self.assertSequenceEqual(gle, expected_gle) restore_asset(asset.name) @@ -191,34 +231,57 @@ 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"), + ) + this_month_depr_amount = 9000.0 if is_last_day_of_the_month(date) else 0 + + self.assertEquals(accumulated_depr_amount, 18000.0 + this_month_depr_amount) + 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" - si.set_posting_time = 1 - si.posting_date = "2021-10-31" - si.due_date = "2021-10-31" - si.get("items")[0].rate = 75000 + si.due_date = nowdate() + si.get("items")[0].rate = 25000 + si.insert() si.submit() self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold") + pro_rata_amount, _, _ = asset.get_pro_rata_amt( + asset.finance_books[0], 9000, get_last_day(add_months(purchase_date, 1)), date + ) + pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount")) + expected_gle = ( - ("_Test Accumulated Depreciations - _TC", 50490.2, 0.0), + ( + "_Test Accumulated Depreciations - _TC", + flt(18000.0 + pro_rata_amount, asset.precision("gross_purchase_amount")), + 0.0, + ), ("_Test Fixed Asset - _TC", 0.0, 100000.0), - ("_Test Gain/Loss on Asset Disposal - _TC", 0.0, 25490.2), - ("Debtors - _TC", 75000.0, 0.0), + ( + "_Test Gain/Loss on Asset Disposal - _TC", + flt(57000.0 - pro_rata_amount, asset.precision("gross_purchase_amount")), + 0.0, + ), + ("Debtors - _TC", 25000.0, 0.0), ) gle = frappe.db.sql( @@ -228,14 +291,9 @@ class TestAsset(AssetSetup): si.name, ) - for i, gle_entry in enumerate(gle): - self.assertEqual(gle_entry[0], expected_gle[i][0]) - self.assertEqual(flt(gle_entry[1], 1), flt(expected_gle[i][1], 1)) - self.assertEqual(flt(gle_entry[2], 1), flt(expected_gle[i][2], 1)) + self.assertSequenceEqual(gle, expected_gle) - si.load_from_db() si.cancel() - self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated") def test_asset_with_maintenance_required_status_after_sale(self): @@ -1471,3 +1529,9 @@ def set_depreciation_settings_in_company(): def enable_cwip_accounting(asset_category, enable=1): frappe.db.set_value("Asset Category", asset_category, "enable_cwip_accounting", enable) + + +def is_last_day_of_the_month(dt): + last_day_of_the_month = get_last_day(dt) + + return getdate(dt) == getdate(last_day_of_the_month) From 7959e41a81b554779c0933b59fe2d2ec7a11c5ed Mon Sep 17 00:00:00 2001 From: anandbaburajan Date: Sat, 21 Jan 2023 19:58:26 +0530 Subject: [PATCH 17/23] chore: fix circular import issue and rename date_of_sale to date_of_disposal --- erpnext/assets/doctype/asset/asset.py | 18 +++++++++--------- erpnext/assets/doctype/asset/depreciation.py | 3 ++- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 7a0ff9ce8b8..af32f93cf05 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -79,12 +79,12 @@ class Asset(AccountsController): _("Purchase Invoice cannot be made against an existing asset {0}").format(self.name) ) - def prepare_depreciation_data(self, date_of_sale=None, date_of_return=None): + def prepare_depreciation_data(self, date_of_disposal=None, date_of_return=None): if self.calculate_depreciation: self.value_after_depreciation = 0 self.set_depreciation_rate() - self.make_depreciation_schedule(date_of_sale) - self.set_accumulated_depreciation(date_of_sale, date_of_return) + self.make_depreciation_schedule(date_of_disposal) + self.set_accumulated_depreciation(date_of_disposal, date_of_return) else: self.finance_books = [] self.value_after_depreciation = flt(self.gross_purchase_amount) - flt( @@ -223,7 +223,7 @@ class Asset(AccountsController): self.get_depreciation_rate(d, on_validate=True), d.precision("rate_of_depreciation") ) - def make_depreciation_schedule(self, date_of_sale): + def make_depreciation_schedule(self, date_of_disposal): if "Manual" not in [d.depreciation_method for d in self.finance_books] and not self.get( "schedules" ): @@ -279,17 +279,17 @@ class Asset(AccountsController): monthly_schedule_date = add_months(schedule_date, -finance_book.frequency_of_depreciation + 1) # if asset is being sold - if date_of_sale: + if date_of_disposal: from_date = self.get_from_date(finance_book.finance_book) depreciation_amount, days, months = self.get_pro_rata_amt( - finance_book, depreciation_amount, from_date, date_of_sale + finance_book, depreciation_amount, from_date, date_of_disposal ) if depreciation_amount > 0: self.append( "schedules", { - "schedule_date": date_of_sale, + "schedule_date": date_of_disposal, "depreciation_amount": depreciation_amount, "depreciation_method": finance_book.depreciation_method, "finance_book": finance_book.finance_book, @@ -542,7 +542,7 @@ class Asset(AccountsController): return True def set_accumulated_depreciation( - self, date_of_sale=None, date_of_return=None, ignore_booked_entry=False + self, date_of_disposal=None, date_of_return=None, ignore_booked_entry=False ): straight_line_idx = [ d.idx for d in self.get("schedules") if d.depreciation_method == "Straight Line" @@ -565,7 +565,7 @@ class Asset(AccountsController): if ( straight_line_idx and i == max(straight_line_idx) - 1 - and not date_of_sale + and not date_of_disposal and not date_of_return ): book = self.get("finance_books")[cint(d.finance_book_id) - 1] diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index efe780278a1..91c24d16327 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -10,7 +10,6 @@ from frappe.utils.user import get_users_with_role 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): @@ -326,6 +325,8 @@ def modify_depreciation_schedule_for_asset_repairs(asset): def reverse_depreciation_entry_made_after_disposal(asset, date): + from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry + row = -1 finance_book = asset.get("schedules")[0].get("finance_book") for schedule in asset.get("schedules"): From fc4be1b337426c727eee6e5ad9a3e28562ff177c Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Sat, 21 Jan 2023 17:29:43 +0100 Subject: [PATCH 18/23] fix: missing constant definition (cherry picked from commit 547d37b1db03a625bb2d4577f895c0f731b679d8) --- erpnext/public/js/controllers/taxes_and_totals.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index a9f1ff684f8..92796073be7 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -115,7 +115,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ calculate_item_values: function() { let me = this; if (!this.discount_amount_applied) { - for (item of this.frm.doc.items || []) { + for (const item of this.frm.doc.items || []) { frappe.model.round_floats_in(item); item.net_rate = item.rate; item.qty = item.qty === undefined ? (me.frm.doc.is_return ? -1 : 1) : item.qty; From b7e9e4a7c5155b5f6b100ca29427eb81f68d5cf4 Mon Sep 17 00:00:00 2001 From: anandbaburajan Date: Sun, 22 Jan 2023 13:56:18 +0530 Subject: [PATCH 19/23] fix: accumulated_depreciation in reverse_depreciation_entry_made_after_disposal --- erpnext/assets/doctype/asset/depreciation.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index 91c24d16327..66dd2e98dc5 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -350,7 +350,10 @@ def reverse_depreciation_entry_made_after_disposal(asset, date): 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 + + idx = cint(schedule.finance_book_id) + asset.finance_books[idx - 1].value_after_depreciation += depreciation_amount + asset.save() From 5ed6a74fc419b66f96609ac794c74477837d6f73 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 16 Jan 2023 23:29:13 +0530 Subject: [PATCH 20/23] feat: provision to select date type based on filter (cherry picked from commit 20c88732086771ca9f54f237fbe19c004d8cf584) --- .../work_order_summary/work_order_summary.js | 28 +++++-------------- .../work_order_summary/work_order_summary.py | 19 +++++++++++-- 2 files changed, 23 insertions(+), 24 deletions(-) diff --git a/erpnext/manufacturing/report/work_order_summary/work_order_summary.js b/erpnext/manufacturing/report/work_order_summary/work_order_summary.js index 832be2301c1..67bd24dd805 100644 --- a/erpnext/manufacturing/report/work_order_summary/work_order_summary.js +++ b/erpnext/manufacturing/report/work_order_summary/work_order_summary.js @@ -13,38 +13,24 @@ frappe.query_reports["Work Order Summary"] = { reqd: 1 }, { - fieldname: "fiscal_year", - label: __("Fiscal Year"), - fieldtype: "Link", - options: "Fiscal Year", - default: frappe.defaults.get_user_default("fiscal_year"), - reqd: 1, - on_change: function(query_report) { - var fiscal_year = query_report.get_values().fiscal_year; - if (!fiscal_year) { - return; - } - frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) { - var fy = frappe.model.get_doc("Fiscal Year", fiscal_year); - frappe.query_report.set_filter_value({ - from_date: fy.year_start_date, - to_date: fy.year_end_date - }); - }); - } + label: __("Based On"), + fieldname:"based_on", + fieldtype: "Select", + options: "Creation Date\nPlanned Date\nActual Date", + default: "Creation Date" }, { label: __("From Posting Date"), fieldname:"from_date", fieldtype: "Date", - default: frappe.defaults.get_user_default("year_start_date"), + default: frappe.datetime.add_months(frappe.datetime.get_today(), -3), reqd: 1 }, { label: __("To Posting Date"), fieldname:"to_date", fieldtype: "Date", - default: frappe.defaults.get_user_default("year_end_date"), + default: frappe.datetime.get_today(), reqd: 1, }, { 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 08a7e0ccd39..f3ab6abcdd5 100644 --- a/erpnext/manufacturing/report/work_order_summary/work_order_summary.py +++ b/erpnext/manufacturing/report/work_order_summary/work_order_summary.py @@ -31,6 +31,7 @@ def get_data(filters): "sales_order", "production_item", "qty", + "creation", "produced_qty", "planned_start_date", "planned_end_date", @@ -47,11 +48,17 @@ def get_data(filters): if filters.get(field): query_filters[field] = filters.get(field) - query_filters["planned_start_date"] = (">=", filters.get("from_date")) - query_filters["planned_end_date"] = ("<=", filters.get("to_date")) + if filters.get("based_on") == "Planned Date": + query_filters["planned_start_date"] = (">=", filters.get("from_date")) + query_filters["planned_end_date"] = ("<=", filters.get("to_date")) + elif filters.get("based_on") == "Actual Date": + query_filters["actual_start_date"] = (">=", filters.get("from_date")) + query_filters["actual_end_date"] = ("<=", filters.get("to_date")) + else: + query_filters["creation"] = ("between", [filters.get("from_date"), filters.get("to_date")]) data = frappe.get_all( - "Work Order", fields=fields, filters=query_filters, order_by="planned_start_date asc" + "Work Order", fields=fields, filters=query_filters, order_by="planned_start_date asc", debug=1 ) res = [] @@ -212,6 +219,12 @@ def get_columns(filters): "options": "Sales Order", "width": 90, }, + { + "label": _("Created On"), + "fieldname": "creation", + "fieldtype": "Date", + "width": 150, + }, { "label": _("Planned Start Date"), "fieldname": "planned_start_date", From 593d7f3dd673ec8da83242313f923af0a1048ce2 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Mon, 23 Jan 2023 13:22:24 +0530 Subject: [PATCH 21/23] fix: linter issue --- .../report/work_order_summary/work_order_summary.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 f3ab6abcdd5..6544c75958e 100644 --- a/erpnext/manufacturing/report/work_order_summary/work_order_summary.py +++ b/erpnext/manufacturing/report/work_order_summary/work_order_summary.py @@ -58,7 +58,7 @@ def get_data(filters): query_filters["creation"] = ("between", [filters.get("from_date"), filters.get("to_date")]) data = frappe.get_all( - "Work Order", fields=fields, filters=query_filters, order_by="planned_start_date asc", debug=1 + "Work Order", fields=fields, filters=query_filters, order_by="planned_start_date asc" ) res = [] From 28f5d28201070ef1963f1173c929a5e5a56ffb56 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 24 Jan 2023 16:47:13 +0530 Subject: [PATCH 22/23] ci: documentation helper (backport #33757) (#33799) ci: documentation helper (#33757) refactor: documentation helper (cherry picked from commit d155042edd0ff1cb0df97313d317678d1c81f0a9) Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> --- .github/helper/documentation.py | 99 ++++++++++++++++++++------------- 1 file changed, 59 insertions(+), 40 deletions(-) diff --git a/.github/helper/documentation.py b/.github/helper/documentation.py index b8909483a6f..258035e4cf6 100644 --- a/.github/helper/documentation.py +++ b/.github/helper/documentation.py @@ -3,52 +3,71 @@ from urllib.parse import urlparse import requests -docs_repos = [ - "frappe_docs", - "erpnext_documentation", +WEBSITE_REPOS = [ "erpnext_com", "frappe_io", ] +DOCUMENTATION_DOMAINS = [ + "docs.erpnext.com", + "frappeframework.com", +] -def uri_validator(x): - result = urlparse(x) - return all([result.scheme, result.netloc, result.path]) -def docs_link_exists(body): - for line in body.splitlines(): - for word in line.split(): - if word.startswith('http') and uri_validator(word): - parsed_url = urlparse(word) - if parsed_url.netloc == "github.com": - parts = parsed_url.path.split('/') - if len(parts) == 5 and parts[1] == "frappe" and parts[2] in docs_repos: - return True - elif parsed_url.netloc == "docs.erpnext.com": - return True +def is_valid_url(url: str) -> bool: + parts = urlparse(url) + return all((parts.scheme, parts.netloc, parts.path)) + + +def is_documentation_link(word: str) -> bool: + if not word.startswith("http") or not is_valid_url(word): + return False + + parsed_url = urlparse(word) + if parsed_url.netloc in DOCUMENTATION_DOMAINS: + return True + + if parsed_url.netloc == "github.com": + parts = parsed_url.path.split("/") + if len(parts) == 5 and parts[1] == "frappe" and parts[2] in WEBSITE_REPOS: + return True + + return False + + +def contains_documentation_link(body: str) -> bool: + return any( + is_documentation_link(word) + for line in body.splitlines() + for word in line.split() + ) + + +def check_pull_request(number: str) -> "tuple[int, str]": + response = requests.get(f"https://api.github.com/repos/frappe/erpnext/pulls/{number}") + if not response.ok: + return 1, "Pull Request Not Found! ⚠️" + + payload = response.json() + title = (payload.get("title") or "").lower().strip() + head_sha = (payload.get("head") or {}).get("sha") + body = (payload.get("body") or "").lower() + + if ( + not title.startswith("feat") + or not head_sha + or "no-docs" in body + or "backport" in body + ): + return 0, "Skipping documentation checks... 🏃" + + if contains_documentation_link(body): + return 0, "Documentation Link Found. You're Awesome! 🎉" + + return 1, "Documentation Link Not Found! ⚠️" if __name__ == "__main__": - pr = sys.argv[1] - response = requests.get("https://api.github.com/repos/frappe/erpnext/pulls/{}".format(pr)) - - if response.ok: - payload = response.json() - title = (payload.get("title") or "").lower().strip() - head_sha = (payload.get("head") or {}).get("sha") - body = (payload.get("body") or "").lower() - - if (title.startswith("feat") - and head_sha - and "no-docs" not in body - and "backport" not in body - ): - if docs_link_exists(body): - print("Documentation Link Found. You're Awesome! 🎉") - - else: - print("Documentation Link Not Found! ⚠️") - sys.exit(1) - - else: - print("Skipping documentation checks... 🏃") + exit_code, message = check_pull_request(sys.argv[1]) + print(message) + sys.exit(exit_code) From 1b115664859016ae849922ecdb782937d8c51200 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 25 Jan 2023 08:57:52 +0530 Subject: [PATCH 23/23] fix: e-Invoicing for SEZ Customer(v13) (#33796) --- erpnext/regional/india/e_invoice/utils.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 45ceb1566c6..251404a46a1 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -71,7 +71,11 @@ def validate_eligibility(doc): # if export invoice, then taxes can be empty # invoice can only be ineligible if no taxes applied and is not an export invoice - no_taxes_applied = not doc.get("taxes") and not doc.get("gst_category") == "Overseas" + no_taxes_applied = ( + not doc.get("taxes") + and not doc.get("gst_category") == "Overseas" + and not doc.get("gst_category") == "SEZ" + ) has_non_gst_item = any(d for d in doc.get("items", []) if d.get("is_non_gst")) if (