diff --git a/erpnext/patches.txt b/erpnext/patches.txt index cc6cef53ade141fcfbcd3374a72afd14a3b03272..b28d0f23b765159ede784d600e717f41c876fec2 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -390,6 +390,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 358f2ceaeaf8bc69a2c553a831a4ff392c527638..811ebb7d004ee498bfba58b150871adab8d13852 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-01-30 13:57:04.951788", + "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 4306a5e4a34275bce7eaa99dc948f1462f17ffe4..eba763fa96638b84b074f206ad0e6403cf21f87d 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 a888998ee3cedd2a7966538a3b5871fcd8ecb1cf..1030cf313b6e5d01c87831fc391ffe386e9ec786 100644 --- a/erpnext/selling/doctype/quotation/test_quotation.py +++ b/erpnext/selling/doctype/quotation/test_quotation.py @@ -63,38 +63,6 @@ class TestQuotation(FrappeTestCase): 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 @@ -428,7 +396,10 @@ class TestQuotation(FrappeTestCase): 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 @@ -438,7 +409,10 @@ class TestQuotation(FrappeTestCase): 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, @@ -455,7 +429,13 @@ class TestQuotation(FrappeTestCase): 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 = [ { @@ -488,7 +468,13 @@ class TestQuotation(FrappeTestCase): 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 @@ -798,10 +784,11 @@ class TestQuotation(FrappeTestCase): test_records = frappe.get_test_records("Quotation") -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 a588846d8bb2430bb505e9ebb5f19883e17036d5..1f60a7be8229f58c8dda197a0915a02e9044b688 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -1928,7 +1928,7 @@ class TestSalesOrder(AccountsTestMixin, FrappeTestCase): self.assertEqual(len(dn.packed_items), 1) self.assertEqual(dn.items[0].item_code, "_Test Product Bundle Item Partial 2") - @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" @@ -1947,6 +1947,9 @@ class TestSalesOrder(AccountsTestMixin, FrappeTestCase): 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}, @@ -1983,6 +1986,11 @@ class TestSalesOrder(AccountsTestMixin, FrappeTestCase): 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 + + def test_pick_list_without_rejected_materials(self): serial_and_batch_item = make_item( "_Test Serial and Batch Item for Rejected Materials", diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.json b/erpnext/selling/doctype/selling_settings/selling_settings.json index 9be4b47c09d5eb8d91566117bebcd02fcaf72171..a2050beca19444b1e6fb606ab429bd13dc4f4e28 100644 --- a/erpnext/selling/doctype/selling_settings/selling_settings.json +++ b/erpnext/selling/doctype/selling_settings/selling_settings.json @@ -170,8 +170,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" }, { @@ -237,7 +239,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2024-07-05 18:39:50.173353", + "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 0f1538cdfcd686487af6bcd014c3c7927c21d2e1..315510f63f15c2a9ac69657e2a866b2abdcbd8cf 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 @@ -47,12 +46,14 @@ class SellingSettings(Document): selling_price_list: DF.Link | None 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() @@ -88,17 +89,18 @@ class SellingSettings(Document): doctype, "tax_id", "print_hide", self.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 a59ab8466b8e5dc8f746be90493f6752b4a73428..af24f0215a3babbad4479fc6c1ee76510df4cd95 100644 --- a/erpnext/stock/doctype/packed_item/packed_item.py +++ b/erpnext/stock/doctype/packed_item/packed_item.py @@ -60,9 +60,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) @@ -84,7 +82,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: @@ -148,6 +146,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 7de79c4a8babbbd94b71965a3b58b7dbe4f30d9f..fe3bf9cabf2436c9c81b4b17c52b9dee1e8efdf3 100644 --- a/erpnext/stock/doctype/packed_item/test_packed_item.py +++ b/erpnext/stock/doctype/packed_item/test_packed_item.py @@ -115,9 +115,13 @@ class TestPackedItem(FrappeTestCase): self.assertEqual(so.packed_items[1].qty, 4) self.assertEqual(so.packed_items[3].qty, 12) - @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 @@ -126,6 +130,9 @@ class TestPackedItem(FrappeTestCase): 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."