From 26fe2388e1dbbd3b663a33059cecbc41d1694c0e Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 24 Mar 2022 13:01:06 +0530 Subject: [PATCH 1/8] fix: (ux) Add is_group=0 filter on website warehouse (#30396) (#30397) - It does not support group warehouses right now and it is misleading (cherry picked from commit d24458ab774571c7c1cdcf5e0b974f0b55eafbfa) Co-authored-by: Marica --- erpnext/e_commerce/doctype/website_item/website_item.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/e_commerce/doctype/website_item/website_item.js b/erpnext/e_commerce/doctype/website_item/website_item.js index 741e78f4a55..7108cabfb3f 100644 --- a/erpnext/e_commerce/doctype/website_item/website_item.js +++ b/erpnext/e_commerce/doctype/website_item/website_item.js @@ -5,6 +5,12 @@ frappe.ui.form.on('Website Item', { onload: function(frm) { // should never check Private frm.fields_dict["website_image"].df.is_private = 0; + + frm.set_query("website_warehouse", () => { + return { + filters: {"is_group": 0} + }; + }); }, image: function() { From 03d7fc1570a92bd462bc6c1a57464041614e4957 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 24 Mar 2022 17:30:58 +0530 Subject: [PATCH 2/8] fix: broken production item links on production plan (backport #30399) (#30400) * fix: only validate qty for main non-subassy items (cherry picked from commit 3d43c437adb596f840f34fe89d4cbcb67957c6fb) * fix: subassembly items linked to temporary name Production Plan tables for po_items and sub_assembly_items are prepared client side so both dont exist at time of first save or modifying and hence any "links" created are invalid. This change retains temporary name so it can be relinked server side after naming is performed. Co-Authored-By: Marica (cherry picked from commit 5b1d6055e6eba72a092ae99a9e49cc671c2e8b08) * test: dont resubmit WO [skip ci] Co-authored-by: Ankush Menat --- .../production_plan/production_plan.js | 7 ++++ .../production_plan/production_plan.py | 13 ++++++++ .../production_plan/test_production_plan.py | 33 +++++++++++++++++++ .../production_plan_item.json | 15 +++++++-- .../doctype/work_order/test_work_order.py | 1 - .../doctype/work_order/work_order.py | 9 +++-- erpnext/patches.txt | 2 +- 7 files changed, 73 insertions(+), 7 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index f3ded994814..5653e1be75d 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -2,6 +2,13 @@ // For license information, please see license.txt frappe.ui.form.on('Production Plan', { + + before_save: function(frm) { + // preserve temporary names on production plan item to re-link sub-assembly items + frm.doc.po_items.forEach(item => { + item.temporary_name = item.name; + }); + }, setup: function(frm) { frm.custom_make_buttons = { 'Work Order': 'Work Order / Subcontract PO', diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 93e9c94da53..35deeb6e65b 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -32,6 +32,7 @@ class ProductionPlan(Document): self.set_pending_qty_in_row_without_reference() self.calculate_total_planned_qty() self.set_status() + self._rename_temporary_references() def set_pending_qty_in_row_without_reference(self): "Set Pending Qty in independent rows (not from SO or MR)." @@ -57,6 +58,18 @@ class ProductionPlan(Document): if not flt(d.planned_qty): frappe.throw(_("Please enter Planned Qty for Item {0} at row {1}").format(d.item_code, d.idx)) + def _rename_temporary_references(self): + """ po_items and sub_assembly_items items are both constructed client side without saving. + + Attempt to fix linkages by using temporary names to map final row names. + """ + new_name_map = {d.temporary_name: d.name for d in self.po_items if d.temporary_name} + actual_names = set(new_name_map.values()) + + for sub_assy in self.sub_assembly_items: + if sub_assy.production_plan_item not in actual_names: + sub_assy.production_plan_item = new_name_map.get(sub_assy.production_plan_item) + @frappe.whitelist() def get_open_sales_orders(self): """ Pull sales orders which are pending to deliver based on criteria selected""" diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index d8e43db62cf..dae16e4bd30 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -617,6 +617,39 @@ class TestProductionPlan(FrappeTestCase): wo_doc.submit() self.assertEqual(wo_doc.qty, 0.55) + def test_temporary_name_relinking(self): + + pp = frappe.new_doc("Production Plan") + + # this can not be unittested so mocking data that would be expected + # from client side. + for _ in range(10): + po_item = pp.append("po_items", { + "name": frappe.generate_hash(length=10), + "temporary_name": frappe.generate_hash(length=10), + }) + pp.append("sub_assembly_items", { + "production_plan_item": po_item.temporary_name + }) + pp._rename_temporary_references() + + for po_item, subassy_item in zip(pp.po_items, pp.sub_assembly_items): + self.assertEqual(po_item.name, subassy_item.production_plan_item) + + # bad links should be erased + pp.append("sub_assembly_items", { + "production_plan_item": frappe.generate_hash(length=16) + }) + pp._rename_temporary_references() + self.assertIsNone(pp.sub_assembly_items[-1].production_plan_item) + pp.sub_assembly_items.pop() + + # reattempting on same doc shouldn't change anything + pp._rename_temporary_references() + for po_item, subassy_item in zip(pp.po_items, pp.sub_assembly_items): + self.assertEqual(po_item.name, subassy_item.production_plan_item) + + def create_production_plan(**args): """ sales_order (obj): Sales Order Doc Object diff --git a/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json b/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json index f829d57475a..df5862fcac8 100644 --- a/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json +++ b/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json @@ -27,7 +27,8 @@ "material_request", "material_request_item", "product_bundle_item", - "item_reference" + "item_reference", + "temporary_name" ], "fields": [ { @@ -204,17 +205,25 @@ "fieldtype": "Data", "hidden": 1, "label": "Item Reference" + }, + { + "fieldname": "temporary_name", + "fieldtype": "Data", + "hidden": 1, + "label": "temporary name" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2021-06-28 18:31:06.822168", + "modified": "2022-03-24 04:54:09.940224", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Plan Item", + "naming_rule": "Random", "owner": "Administrator", "permissions": [], "sort_field": "modified", - "sort_order": "ASC" + "sort_order": "ASC", + "states": [] } \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 28226290e2f..ed7f843271b 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -352,7 +352,6 @@ class TestWorkOrder(FrappeTestCase): wo_order = make_wo_order_test_record(planned_start_date=now(), sales_order=so.name, qty=3) - wo_order.submit() self.assertEqual(wo_order.docstatus, 1) allow_overproduction("overproduction_percentage_for_sales_order", 0) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 484264613fe..ee12597d24f 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -457,7 +457,8 @@ class WorkOrder(Document): mr_obj.update_requested_qty([self.material_request_item]) def update_ordered_qty(self): - if self.production_plan and self.production_plan_item: + if self.production_plan and self.production_plan_item \ + and not self.production_plan_sub_assembly_item: qty = frappe.get_value("Production Plan Item", self.production_plan_item, "ordered_qty") or 0.0 if self.docstatus == 1: @@ -640,9 +641,13 @@ class WorkOrder(Document): if not self.qty > 0: frappe.throw(_("Quantity to Manufacture must be greater than 0.")) - if self.production_plan and self.production_plan_item: + if self.production_plan and self.production_plan_item \ + and not self.production_plan_sub_assembly_item: qty_dict = frappe.db.get_value("Production Plan Item", self.production_plan_item, ["planned_qty", "ordered_qty"], as_dict=1) + if not qty_dict: + return + allowance_qty = flt(frappe.db.get_single_value("Manufacturing Settings", "overproduction_percentage_for_work_order"))/100 * qty_dict.get("planned_qty", 0) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 9e3c82bc5d3..a8fa414208a 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -352,6 +352,6 @@ erpnext.patches.v13_0.update_reserved_qty_closed_wo erpnext.patches.v13_0.amazon_mws_deprecation_warning erpnext.patches.v13_0.set_work_order_qty_in_so_from_mr erpnext.patches.v13_0.update_accounts_in_loan_docs -erpnext.patches.v13_0.remove_unknown_links_to_prod_plan_items +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 From 20794ac9ce3d8e45122f8041ac84b950510646a4 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 24 Mar 2022 17:58:22 +0530 Subject: [PATCH 3/8] fix: consider all existing PO items When this is sent from API/client side doesn't send temporary_name it can be flaky. Hence, use all available names for validation. --- .../manufacturing/doctype/production_plan/production_plan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 35deeb6e65b..a262b91cb5f 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -64,7 +64,7 @@ class ProductionPlan(Document): Attempt to fix linkages by using temporary names to map final row names. """ new_name_map = {d.temporary_name: d.name for d in self.po_items if d.temporary_name} - actual_names = set(new_name_map.values()) + actual_names = {d.name for d in self.po_items} for sub_assy in self.sub_assembly_items: if sub_assy.production_plan_item not in actual_names: From 13fcda57767c456dfca4c064c41ca4f80a1898b2 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 25 Mar 2022 10:46:09 +0530 Subject: [PATCH 4/8] fix: Rate change issue on save and mapping from other doc --- .../controllers/sales_and_purchase_return.py | 2 ++ erpnext/public/js/controllers/transaction.js | 33 +++---------------- 2 files changed, 6 insertions(+), 29 deletions(-) diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index 7d4ef587526..5b72d14dda5 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -399,6 +399,8 @@ def make_return_doc(doctype, source_name, target_doc=None): } }, target_doc, set_missing_values) + doclist.set_onload('ignore_price_list', True) + return doclist def get_rate_for_return(voucher_type, voucher_no, item_code, return_against=None, diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 8964b14f553..8188f324f6f 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -14,31 +14,6 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ frappe.model.round_floats_in(item, ["rate", "price_list_rate"]); - if(item.price_list_rate) { - if(item.rate > item.price_list_rate && has_margin_field) { - // if rate is greater than price_list_rate, set margin - // or set discount - item.discount_percentage = 0; - item.margin_type = 'Amount'; - item.margin_rate_or_amount = flt(item.rate - item.price_list_rate, - precision("margin_rate_or_amount", item)); - item.rate_with_margin = item.rate; - } else { - item.discount_percentage = flt((1 - item.rate / item.price_list_rate) * 100.0, - precision("discount_percentage", item)); - item.discount_amount = flt(item.price_list_rate) - flt(item.rate); - item.margin_type = ''; - item.margin_rate_or_amount = 0; - item.rate_with_margin = 0; - } - } else { - item.discount_percentage = 0.0; - item.margin_type = ''; - item.margin_rate_or_amount = 0; - item.rate_with_margin = 0; - } - item.base_rate_with_margin = item.rate_with_margin * flt(frm.doc.conversion_rate); - cur_frm.cscript.set_gross_profit(item); cur_frm.cscript.calculate_taxes_and_totals(); cur_frm.cscript.calculate_stock_uom_rate(frm, cdt, cdn); @@ -1041,9 +1016,9 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ var me = this; this.set_dynamic_labels(); var company_currency = this.get_company_currency(); - // Added `ignore_pricing_rule` to determine if document is loading after mapping from another doc + // Added `ignore_price_list` to determine if document is loading after mapping from another doc if(this.frm.doc.currency && this.frm.doc.currency !== company_currency - && !this.frm.doc.ignore_pricing_rule) { + && !this.frm.doc.__onload.ignore_price_list) { this.get_exchange_rate(transaction_date, this.frm.doc.currency, company_currency, function(exchange_rate) { @@ -1143,8 +1118,8 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ this.set_dynamic_labels(); var company_currency = this.get_company_currency(); - // Added `ignore_pricing_rule` to determine if document is loading after mapping from another doc - if(this.frm.doc.price_list_currency !== company_currency && !this.frm.doc.ignore_pricing_rule) { + // Added `ignore_price_list` to determine if document is loading after mapping from another doc + if(this.frm.doc.price_list_currency !== company_currency && !this.frm.doc.__onload.ignore_price_list) { this.get_exchange_rate(this.frm.doc.posting_date, this.frm.doc.price_list_currency, company_currency, function(exchange_rate) { me.frm.set_value("plc_conversion_rate", exchange_rate); From 067564bd260437290b7cb190fb1cdc02eb658d0a Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 25 Mar 2022 12:17:51 +0530 Subject: [PATCH 5/8] fix: Add back calculation for discount --- erpnext/public/js/controllers/transaction.js | 25 ++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 8188f324f6f..0973a426e98 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -14,6 +14,31 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ frappe.model.round_floats_in(item, ["rate", "price_list_rate"]); + if(item.price_list_rate) { + if(item.rate > item.price_list_rate && has_margin_field) { + // if rate is greater than price_list_rate, set margin + // or set discount + item.discount_percentage = 0; + item.margin_type = 'Amount'; + item.margin_rate_or_amount = flt(item.rate - item.price_list_rate, + precision("margin_rate_or_amount", item)); + item.rate_with_margin = item.rate; + } else { + item.discount_percentage = flt((1 - item.rate / item.price_list_rate) * 100.0, + precision("discount_percentage", item)); + item.discount_amount = flt(item.price_list_rate) - flt(item.rate); + item.margin_type = ''; + item.margin_rate_or_amount = 0; + item.rate_with_margin = 0; + } + } else { + item.discount_percentage = 0.0; + item.margin_type = ''; + item.margin_rate_or_amount = 0; + item.rate_with_margin = 0; + } + item.base_rate_with_margin = item.rate_with_margin * flt(frm.doc.conversion_rate); + cur_frm.cscript.set_gross_profit(item); cur_frm.cscript.calculate_taxes_and_totals(); cur_frm.cscript.calculate_stock_uom_rate(frm, cdt, cdn); From 6937a498e71cffc56cad65141f561b4c8785d185 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 25 Mar 2022 12:28:55 +0530 Subject: [PATCH 6/8] fix: Revert rate calculation --- erpnext/controllers/taxes_and_totals.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 40833b9300f..35e03ac051d 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -114,18 +114,14 @@ class calculate_taxes_and_totals(object): for item in self.doc.get("items"): self.doc.round_floats_in(item) - if not item.rate: - item.rate = item.price_list_rate - if item.discount_percentage == 100: item.rate = 0.0 elif item.price_list_rate: - if item.pricing_rules or abs(item.discount_percentage) > 0: + if not item.rate or (item.pricing_rules and item.discount_percentage > 0): item.rate = flt(item.price_list_rate * (1.0 - (item.discount_percentage / 100.0)), item.precision("rate")) - if abs(item.discount_percentage) > 0: - item.discount_amount = item.price_list_rate * (item.discount_percentage / 100.0) + item.discount_amount = item.price_list_rate * (item.discount_percentage / 100.0) elif item.discount_amount or item.pricing_rules: item.rate = item.price_list_rate - item.discount_amount From 2e0e6ca6b177043f673c05472ac3069f060d1563 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 25 Mar 2022 12:39:59 +0530 Subject: [PATCH 7/8] fix: Condition --- erpnext/controllers/taxes_and_totals.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 35e03ac051d..585577c99f8 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -123,7 +123,7 @@ class calculate_taxes_and_totals(object): item.discount_amount = item.price_list_rate * (item.discount_percentage / 100.0) - elif item.discount_amount or item.pricing_rules: + elif item.discount_amount and item.pricing_rules: item.rate = item.price_list_rate - item.discount_amount if item.doctype in ['Quotation Item', 'Sales Order Item', 'Delivery Note Item', 'Sales Invoice Item', From 29fde6ee8b19931fce7a68d6c2b82aee16ca3317 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 25 Mar 2022 12:57:05 +0530 Subject: [PATCH 8/8] fix: failing broken patches (#30409) (#30412) * fix: failing broken patches * refactor: simpler conditions [skip ci] (cherry picked from commit c4854bb1b15f00db74a92302ae5426d45c981157) Co-authored-by: Shadrak Gurupnor <30501401+shadrak98@users.noreply.github.com> --- erpnext/patches.txt | 2 +- erpnext/patches/v13_0/rename_issue_doctype_fields.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index a8fa414208a..3faab2eb1d1 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -1,6 +1,6 @@ erpnext.patches.v12_0.update_is_cancelled_field -erpnext.patches.v13_0.add_bin_unique_constraint erpnext.patches.v11_0.rename_production_order_to_work_order +erpnext.patches.v13_0.add_bin_unique_constraint erpnext.patches.v11_0.refactor_naming_series erpnext.patches.v11_0.refactor_autoname_naming execute:frappe.reload_doc("accounts", "doctype", "POS Payment Method") #2020-05-28 diff --git a/erpnext/patches/v13_0/rename_issue_doctype_fields.py b/erpnext/patches/v13_0/rename_issue_doctype_fields.py index bf5438c4d2e..80d51652abe 100644 --- a/erpnext/patches/v13_0/rename_issue_doctype_fields.py +++ b/erpnext/patches/v13_0/rename_issue_doctype_fields.py @@ -60,7 +60,7 @@ def execute(): def convert_to_seconds(value, unit): seconds = 0 - if value == 0: + if not value: return seconds if unit == 'Hours': seconds = value * 3600