diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 3ce33a5a5f0cb9e11f4895ffe7e1cfa07dc3cfb3..57fd2be7d829e56fde809c0ba7eb6420bcb991df 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -398,6 +398,7 @@ erpnext.patches.dokos.v4_0.check_subscription_invoices erpnext.patches.dokos.v4_0.update_advance_for_down_payments #2024-05-207 erpnext.patches.dokos.v4_0.check_enable_pappers erpnext.patches.dokos.v4_0.suspense_accounts_for_vat_on_payments +erpnext.patches.dokos.v4_0.move_bundle_price_determination_to_bundle # @dokos diff --git a/erpnext/patches/dokos/v4_0/move_bundle_price_determination_to_bundle.py b/erpnext/patches/dokos/v4_0/move_bundle_price_determination_to_bundle.py new file mode 100644 index 0000000000000000000000000000000000000000..429dc7b2eb333bf7c28a9e12c6ad13ba8774ec88 --- /dev/null +++ b/erpnext/patches/dokos/v4_0/move_bundle_price_determination_to_bundle.py @@ -0,0 +1,7 @@ +import frappe + + +def execute(): + if frappe.db.get_single_value("Selling Settings", "editable_bundle_item_rates"): + for bundle in frappe.get_all("Product Bundle"): + frappe.db.set_value("Product Bundle", bundle.name, "editable_bundle_item_rates", 1) diff --git a/erpnext/selling/doctype/product_bundle/product_bundle.json b/erpnext/selling/doctype/product_bundle/product_bundle.json index 2cf3ced3d89f2882d39f9521d164d188aacc1085..14c6250f5ccd3cc2bb3c38a0bc1d185760b4b837 100644 --- a/erpnext/selling/doctype/product_bundle/product_bundle.json +++ b/erpnext/selling/doctype/product_bundle/product_bundle.json @@ -11,6 +11,7 @@ "description", "column_break_eonk", "disabled", + "editable_bundle_item_rates", "item_section", "items", "section_break_4", @@ -72,12 +73,18 @@ { "fieldname": "column_break_eonk", "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "editable_bundle_item_rates", + "fieldtype": "Check", + "label": "Calculate Product Bundle Price based on Child Items' Rates" } ], "icon": "fa fa-sitemap", "idx": 1, "links": [], - "modified": "2024-03-27 13:10:19.599302", + "modified": "2025-01-21 11:50:07.516599", "modified_by": "Administrator", "module": "Selling", "name": "Product Bundle", diff --git a/erpnext/selling/doctype/product_bundle/product_bundle.py b/erpnext/selling/doctype/product_bundle/product_bundle.py index 938869c7125da46041878ff753659051fb99de23..ef10967f1b099e97a61445b9d7f80c61ef11f164 100644 --- a/erpnext/selling/doctype/product_bundle/product_bundle.py +++ b/erpnext/selling/doctype/product_bundle/product_bundle.py @@ -10,6 +10,23 @@ from frappe.utils import get_link_to_form class ProductBundle(Document): + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from frappe.types import DF + + from erpnext.selling.doctype.product_bundle_item.product_bundle_item import ProductBundleItem + + description: DF.Data | None + disabled: DF.Check + editable_bundle_item_rates: DF.Check + items: DF.Table[ProductBundleItem] + new_item_code: DF.Link + # end: auto-generated types + def autoname(self): self.name = self.new_item_code @@ -50,7 +67,7 @@ class ProductBundle(Document): if len(invoice_links): frappe.throw( - "This Product Bundle is linked with {0}. You will have to cancel these documents in order to delete this Product Bundle".format( + "This Product Bundle is linked with {}. You will have to cancel these documents in order to delete this Product Bundle".format( ", ".join(invoice_links) ), title=_("Not Allowed"), diff --git a/erpnext/selling/doctype/quotation/test_quotation.py b/erpnext/selling/doctype/quotation/test_quotation.py index d557f563e1008c58961e4e703b04f639d8646ed5..abf8bd9670f1c410a31ab2555f62efa904aa35bf 100644 --- a/erpnext/selling/doctype/quotation/test_quotation.py +++ b/erpnext/selling/doctype/quotation/test_quotation.py @@ -84,38 +84,6 @@ class TestQuotation(IntegrationTestCase): self.assertEqual(sales_order.items[0].item_code, item.name) self.assertEqual(sales_order.items[0].qty, 5.0) - def test_do_not_add_ordered_items_in_new_sales_order(self): - from erpnext.selling.doctype.quotation.quotation import make_sales_order - from erpnext.stock.doctype.item.test_item import make_item - - item = make_item("_Test Item for Quotation for SO", {"is_stock_item": 1}) - - quotation = make_quotation(qty=5, do_not_submit=True) - quotation.append( - "items", - { - "item_code": item.name, - "qty": 5, - "rate": 100, - "conversion_factor": 1, - "uom": item.stock_uom, - "warehouse": "_Test Warehouse - _TC", - "stock_uom": item.stock_uom, - }, - ) - quotation.submit() - - sales_order = make_sales_order(quotation.name) - sales_order.delivery_date = nowdate() - self.assertEqual(len(sales_order.items), 2) - sales_order.remove(sales_order.items[1]) - sales_order.submit() - - sales_order = make_sales_order(quotation.name) - self.assertEqual(len(sales_order.items), 1) - self.assertEqual(sales_order.items[0].item_code, item.name) - self.assertEqual(sales_order.items[0].qty, 5.0) - def test_gross_profit(self): from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry @@ -451,7 +419,10 @@ class TestQuotation(IntegrationTestCase): make_product_bundle("_Test Product Bundle", ["_Test Bundle Item 1", "_Test Bundle Item 2"]) - enable_calculate_bundle_price() + # enable_calculate_bundle_price() @dokos + frappe.db.set_value( + "Product Bundle", {"new_item_code": "_Test Product Bundle"}, "editable_bundle_item_rates", 1 + ) # @dokos quotation = make_quotation(item_code="_Test Product Bundle", qty=2, rate=100, do_not_submit=1) quotation.packed_items[0].rate = 100 @@ -461,7 +432,10 @@ class TestQuotation(IntegrationTestCase): self.assertEqual(quotation.items[0].amount, 600) self.assertEqual(quotation.items[0].rate, 300) - enable_calculate_bundle_price(enable=0) + # enable_calculate_bundle_price(enable=0) @dokos + frappe.db.set_value( + "Product Bundle", {"new_item_code": "_Test Product Bundle"}, "editable_bundle_item_rates", 0 + ) # @dokos def test_product_bundle_price_calculation_for_multiple_product_bundles_when_calculate_bundle_price_is_checked( self, @@ -478,7 +452,13 @@ class TestQuotation(IntegrationTestCase): make_product_bundle("_Test Product Bundle 1", ["_Test Bundle Item 1", "_Test Bundle Item 2"]) make_product_bundle("_Test Product Bundle 2", ["_Test Bundle Item 2", "_Test Bundle Item 3"]) - enable_calculate_bundle_price() + # enable_calculate_bundle_price() + frappe.db.set_value( + "Product Bundle", {"new_item_code": "_Test Product Bundle 1"}, "editable_bundle_item_rates", 1 + ) # @dokos + frappe.db.set_value( + "Product Bundle", {"new_item_code": "_Test Product Bundle 2"}, "editable_bundle_item_rates", 1 + ) # @dokos item_list = [ { @@ -511,7 +491,13 @@ class TestQuotation(IntegrationTestCase): for item in quotation.items: self.assertEqual(item.amount, expected_values[item.idx - 1]) - enable_calculate_bundle_price(enable=0) + # enable_calculate_bundle_price(enable=0) @dokos + frappe.db.set_value( + "Product Bundle", {"new_item_code": "_Test Product Bundle 1"}, "editable_bundle_item_rates", 0 + ) # @dokos + frappe.db.set_value( + "Product Bundle", {"new_item_code": "_Test Product Bundle 2"}, "editable_bundle_item_rates", 0 + ) # @dokos def test_packed_items_indices_are_reset_when_product_bundle_is_deleted_from_items_table(self): from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle @@ -818,10 +804,11 @@ class TestQuotation(IntegrationTestCase): self.assertEqual(quotation.rounded_total, 0) -def enable_calculate_bundle_price(enable=1): - selling_settings = frappe.get_doc("Selling Settings") - selling_settings.editable_bundle_item_rates = enable - selling_settings.save() +# @dokos: This option is determined at bundle level +# def enable_calculate_bundle_price(enable=1): +# selling_settings = frappe.get_doc("Selling Settings") +# selling_settings.editable_bundle_item_rates = enable +# selling_settings.save() def get_quotation_dict(party_name=None, item_code=None): diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 25ac312bf4d00f7a2502398ca3101f81b1187779..0307c5ce8b29abd6b9b74e3f0c8a4bc059875d81 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -1919,7 +1919,7 @@ class TestSalesOrder(AccountsTestMixin, IntegrationTestCase): self.assertEqual(len(dn.packed_items), 1) self.assertEqual(dn.items[0].item_code, "_Test Product Bundle Item Partial 2") - @IntegrationTestCase.change_settings("Selling Settings", {"editable_bundle_item_rates": 1}) + # @IntegrationTestCase.change_settings("Selling Settings", {"editable_bundle_item_rates": 1}) @dokos def test_expired_rate_for_packed_item(self): bundle = "_Test Product Bundle 1" packed_item = "_Packed Item 1" @@ -1939,6 +1939,9 @@ class TestSalesOrder(AccountsTestMixin, IntegrationTestCase): make_item(product_bundle, {"is_stock_item": 0, "stock_uom": "Nos"}) make_product_bundle(bundle, [packed_item], 1) + frappe.db.set_value( + "Product Bundle", {"new_item_code": bundle}, "editable_bundle_item_rates", 1 + ) # @dokos for scenario in [ {"valid_upto": add_days(nowdate(), -1), "expected_rate": 0.0}, @@ -1975,6 +1978,10 @@ class TestSalesOrder(AccountsTestMixin, IntegrationTestCase): self.assertEqual(so.items[0].rate, scenario.get("expected_rate")) self.assertEqual(so.packed_items[0].rate, scenario.get("expected_rate")) + frappe.db.set_value( + "Product Bundle", {"new_item_code": bundle}, "editable_bundle_item_rates", 0 + ) # @dokos + @patch( # this also shadows one (1) call to _get_payment_gateway_controller "erpnext.accounts.doctype.payment_request.payment_request.PaymentRequest.get_payment_url", diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.json b/erpnext/selling/doctype/selling_settings/selling_settings.json index 16c69cff37b3d45a056be5d59c132b03bc68b39f..c44a09d5347dc0bd8374c985b63474f9542fc8e3 100644 --- a/erpnext/selling/doctype/selling_settings/selling_settings.json +++ b/erpnext/selling/doctype/selling_settings/selling_settings.json @@ -172,8 +172,10 @@ }, { "default": "0", + "description": "In Dokos, this option is determined at the bundle level", "fieldname": "editable_bundle_item_rates", "fieldtype": "Check", + "hidden": 1, "label": "Calculate Product Bundle Price based on Child Items' Rates" }, { @@ -250,7 +252,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2024-12-06 11:41:54.722337", + "modified": "2025-01-21 11:58:36.375345", "modified_by": "Administrator", "module": "Selling", "name": "Selling Settings", diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.py b/erpnext/selling/doctype/selling_settings/selling_settings.py index 5c6c63693c869c7ae204ad4181da06de3ee00046..4996cf71826434fe80ee3ac62ed8046dbd3f088d 100644 --- a/erpnext/selling/doctype/selling_settings/selling_settings.py +++ b/erpnext/selling/doctype/selling_settings/selling_settings.py @@ -39,7 +39,6 @@ class SellingSettings(Document): enable_cutoff_date_on_bulk_delivery_note_creation: DF.Check enable_discount_accounting: DF.Check hide_tax_id: DF.Check - validate_project_in_so_si: DF.Check maintain_same_rate_action: DF.Literal["Stop", "Warn"] maintain_same_sales_rate: DF.Check role_to_override_stop_action: DF.Link | None @@ -48,12 +47,13 @@ class SellingSettings(Document): so_required: DF.Literal["No", "Yes"] territory: DF.Link | None use_server_side_reactivity: DF.Check + validate_project_in_so_si: DF.Check validate_selling_price: DF.Check # end: auto-generated types def on_update(self): self.toggle_hide_tax_id() - self.toggle_editable_rate_for_bundle_items() + # self.toggle_editable_rate_for_bundle_items() @dokos self.toggle_discount_accounting_fields() self.toggle_item_price_calculation_rule() @@ -89,17 +89,18 @@ class SellingSettings(Document): doctype, "tax_id", "print_hide", _hide_tax_id, "Check", validate_fields_for_doctype=False ) - def toggle_editable_rate_for_bundle_items(self): - editable_bundle_item_rates = cint(self.editable_bundle_item_rates) - - make_property_setter( - "Packed Item", - "rate", - "read_only", - not (editable_bundle_item_rates), - "Check", - validate_fields_for_doctype=False, - ) + # @dokos + # def toggle_editable_rate_for_bundle_items(self): + # editable_bundle_item_rates = cint(self.editable_bundle_item_rates) + + # make_property_setter( + # "Packed Item", + # "rate", + # "read_only", + # not (editable_bundle_item_rates), + # "Check", + # validate_fields_for_doctype=False, + # ) def toggle_discount_accounting_fields(self): enable_discount_accounting = cint(self.enable_discount_accounting) diff --git a/erpnext/stock/doctype/packed_item/packed_item.py b/erpnext/stock/doctype/packed_item/packed_item.py index b7c8a4efaf891621abd0c9599fe310a7632c8d1f..69e1622f6bc1cc0666f4137d5f766d1bb55cf9fe 100644 --- a/erpnext/stock/doctype/packed_item/packed_item.py +++ b/erpnext/stock/doctype/packed_item/packed_item.py @@ -69,7 +69,7 @@ def make_packing_list(doc): return parent_items_price, reset = {}, False - set_price_from_children = frappe.db.get_single_value("Selling Settings", "editable_bundle_item_rates") + # set_price_from_children = frappe.db.get_single_value("Selling Settings", "editable_bundle_item_rates") @dokos stale_packed_items_table = get_indexed_packed_items_table(doc) @@ -91,7 +91,7 @@ def make_packing_list(doc): update_packed_item_price_data(pi_row, item_data, doc) update_packed_item_from_cancelled_doc(item_row, bundle_item, pi_row, doc) - if set_price_from_children: # create/update bundle item wise price dict + if bundle_item.editable_bundle_item_rates: # create/update bundle item wise price dict update_product_bundle_rate(parent_items_price, pi_row, item_row) if parent_items_price: @@ -155,6 +155,7 @@ def get_product_bundle_items(item_code): product_bundle_item.qty, product_bundle_item.uom, product_bundle_item.description, + product_bundle.editable_bundle_item_rates, ) .where((product_bundle.new_item_code == item_code) & (product_bundle.disabled == 0)) .orderby(product_bundle_item.idx) diff --git a/erpnext/stock/doctype/packed_item/test_packed_item.py b/erpnext/stock/doctype/packed_item/test_packed_item.py index 4f422a202709cf4e991d25283438adc98b691439..30408aac45c0f1dc2f4e8829eeb808022d3e13a1 100644 --- a/erpnext/stock/doctype/packed_item/test_packed_item.py +++ b/erpnext/stock/doctype/packed_item/test_packed_item.py @@ -122,9 +122,13 @@ class TestPackedItem(IntegrationTestCase): self.assertEqual(so.packed_items[1].qty, 4) self.assertEqual(so.packed_items[3].qty, 12) - @IntegrationTestCase.change_settings("Selling Settings", {"editable_bundle_item_rates": 1}) + # @IntegrationTestCase.change_settings("Selling Settings", {"editable_bundle_item_rates": 1}) @dokos def test_bundle_item_cumulative_price(self): "Test if Bundle Item rate is cumulative from packed items." + frappe.db.set_value( + "Product Bundle", {"new_item_code": self.bundle}, "editable_bundle_item_rates", 1 + ) # @dokos + so = make_sales_order(item_code=self.bundle, qty=2, do_not_submit=True) so.packed_items[0].rate = 150 @@ -133,6 +137,9 @@ class TestPackedItem(IntegrationTestCase): self.assertEqual(so.items[0].rate, 700) self.assertEqual(so.items[0].amount, 1400) + frappe.db.set_value( + "Product Bundle", {"new_item_code": self.bundle}, "editable_bundle_item_rates", 0 + ) # @dokos def test_newly_mapped_doc_packed_items(self): "Test impact on packed items in newly mapped DN from SO."