From b5376ce5cb93a100376e1c5afb43a7b10917232c Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 19 Oct 2022 14:12:51 +0530 Subject: [PATCH 1/8] fix: overlap error not raised for job card in case of workstation with production capacity (cherry picked from commit 8b2165e0d16401ea24b4e571c12488e3199a48d5) --- .../doctype/job_card/job_card.py | 2 +- .../doctype/job_card/test_job_card.py | 39 +++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index c24d07d2194..f9ed007aed5 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -133,7 +133,7 @@ class JobCard(Document): (%(from_time)s <= jctl.from_time and %(to_time)s >= jctl.to_time) {0} ) and jctl.name != %(name)s and jc.name != %(parent)s and jc.docstatus < 2 {1} - order by jctl.to_time desc limit 1""".format( + order by jctl.to_time desc""".format( extra_cond, validate_overlap_for ), { diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.py b/erpnext/manufacturing/doctype/job_card/test_job_card.py index 71aa5ef0951..87ee2bdaa21 100644 --- a/erpnext/manufacturing/doctype/job_card/test_job_card.py +++ b/erpnext/manufacturing/doctype/job_card/test_job_card.py @@ -133,6 +133,45 @@ class TestJobCard(FrappeTestCase): ) self.assertRaises(OverlapError, jc2.save) + def test_job_card_overlap_with_capacity(self): + wo2 = make_wo_order_test_record(item="_Test FG Item 2", qty=2) + + workstation = make_workstation(workstation_name=random_string(5)).name + frappe.db.set_value("Workstation", workstation, "production_capacity", 1) + + jc1 = frappe.get_last_doc("Job Card", {"work_order": self.work_order.name}) + jc2 = frappe.get_last_doc("Job Card", {"work_order": wo2.name}) + + jc1.workstation = workstation + jc1.append( + "time_logs", + {"from_time": "2021-01-01 00:00:00", "to_time": "2021-01-01 08:00:00", "completed_qty": 1}, + ) + jc1.save() + + jc2.workstation = workstation + + # add a new entry in same time slice + jc2.append( + "time_logs", + {"from_time": "2021-01-01 00:01:00", "to_time": "2021-01-01 06:00:00", "completed_qty": 1}, + ) + self.assertRaises(OverlapError, jc2.save) + + frappe.db.set_value("Workstation", workstation, "production_capacity", 2) + jc2.load_from_db() + + jc2.workstation = workstation + + # add a new entry in same time slice + jc2.append( + "time_logs", + {"from_time": "2021-01-01 00:01:00", "to_time": "2021-01-01 06:00:00", "completed_qty": 1}, + ) + + jc2.save() + self.assertTrue(jc2.name) + def test_job_card_multiple_materials_transfer(self): "Test transferring RMs separately against Job Card with multiple RMs." self.transfer_material_against = "Job Card" From ea032893d379e796de118996e2fc2974248eb77c Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Wed, 19 Oct 2022 18:37:02 +0200 Subject: [PATCH 2/8] fix: allow to create Sales Order from expired Quotation (#32641) (cherry picked from commit 4ad3002861339651e85ac9ab4e532251b03590b2) --- erpnext/selling/doctype/quotation/quotation.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/erpnext/selling/doctype/quotation/quotation.js b/erpnext/selling/doctype/quotation/quotation.js index 3bb9774044a..e33fa4b1d1a 100644 --- a/erpnext/selling/doctype/quotation/quotation.js +++ b/erpnext/selling/doctype/quotation/quotation.js @@ -83,11 +83,12 @@ erpnext.selling.QuotationController = erpnext.selling.SellingController.extend({ } } - if(doc.docstatus == 1 && !(['Lost', 'Ordered']).includes(doc.status)) { - if(!doc.valid_till || frappe.datetime.get_diff(doc.valid_till, frappe.datetime.get_today()) >= 0) { - cur_frm.add_custom_button(__('Sales Order'), - cur_frm.cscript['Make Sales Order'], __('Create')); - } + if (doc.docstatus == 1 && !["Lost", "Ordered"].includes(doc.status)) { + this.frm.add_custom_button( + __("Sales Order"), + this.frm.cscript["Make Sales Order"], + __("Create") + ); if(doc.status!=="Ordered") { this.frm.add_custom_button(__('Set as Lost'), () => { From 1741501ea81424f11a859a217610873a20059afb Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 19 Oct 2022 18:38:34 +0530 Subject: [PATCH 3/8] fix: incorrect qty in material request (cherry picked from commit ad278b200769e03b52f0051c26873a9eca5d8b43) --- .../doctype/production_plan/production_plan.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 8bf633b145e..f4960f0ed50 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -27,6 +27,7 @@ from six import iteritems from erpnext.manufacturing.doctype.bom.bom import get_children, validate_bom_no from erpnext.manufacturing.doctype.work_order.work_order import get_item_details from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults +from erpnext.stock.get_item_details import get_conversion_factor from erpnext.utilities.transaction_base import validate_uom_is_integer @@ -651,13 +652,23 @@ class ProductionPlan(Document): else: material_request = material_request_map[key] + conversion_factor = 1.0 + if ( + material_request_type == "Purchase" + and item_doc.purchase_uom + and item_doc.purchase_uom != item_doc.stock_uom + ): + conversion_factor = ( + get_conversion_factor(item_doc.name, item_doc.purchase_uom).get("conversion_factor") or 1.0 + ) + # add item material_request.append( "items", { "item_code": item.item_code, "from_warehouse": item.from_warehouse, - "qty": item.quantity, + "qty": item.quantity / conversion_factor, "schedule_date": schedule_date, "warehouse": item.warehouse, "sales_order": item.sales_order, From 4f0c2909abc0c5ef924e1bb82018a808376f74fd Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 19 Oct 2022 19:08:27 +0530 Subject: [PATCH 4/8] test: validate qty and purchase uom in material request which is created from PP (cherry picked from commit 4d5ef721f72b068fc2c94c21748373762626f4b3) --- .../production_plan/test_production_plan.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index f0d4ce0ca82..142d521552d 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -732,6 +732,35 @@ class TestProductionPlan(FrappeTestCase): self.assertEqual(pln.status, "Completed") self.assertEqual(pln.po_items[0].produced_qty, 5) + def test_material_request_item_for_purchase_uom(self): + from erpnext.stock.doctype.item.test_item import make_item + + fg_item = make_item(properties={"is_stock_item": 1, "stock_uom": "_Test UOM 1"}).name + bom_item = make_item( + properties={"is_stock_item": 1, "stock_uom": "_Test UOM 1", "purchase_uom": "Nos"} + ).name + + if not frappe.db.exists("UOM Conversion Detail", {"parent": bom_item, "uom": "Nos"}): + doc = frappe.get_doc("Item", bom_item) + doc.append("uoms", {"uom": "Nos", "conversion_factor": 10}) + doc.save() + + make_bom(item=fg_item, raw_materials=[bom_item], source_warehouse="_Test Warehouse - _TC") + + pln = create_production_plan( + item_code=fg_item, planned_qty=10, ignore_existing_ordered_qty=1, stock_uom="_Test UOM 1" + ) + + pln.make_material_request() + for row in frappe.get_all( + "Material Request Item", + filters={"production_plan": pln.name}, + fields=["item_code", "uom", "qty"], + ): + self.assertEqual(row.item_code, bom_item) + self.assertEqual(row.uom, "Nos") + self.assertEqual(row.qty, 1) + def create_production_plan(**args): """ From 3a2f08fbda9b94333f7f61457f3f599061fea237 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 19 Oct 2022 23:50:39 +0530 Subject: [PATCH 5/8] fix: Billing Address for inter-company purchase docs (cherry picked from commit 796f2d3c09ae082c20ad1a2501b55db58477a085) --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 3 +++ erpnext/stock/doctype/delivery_note/delivery_note.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 1badb809341..72e9790700f 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -2187,6 +2187,9 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None): update_address( target_doc, "shipping_address", "shipping_address_display", source_doc.customer_address ) + update_address( + target_doc, "billing_address", "billing_address_display", source_doc.customer_address + ) if currency: target_doc.currency = currency diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 7205758a8e4..3efe584a793 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -832,6 +832,9 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None): update_address( target_doc, "shipping_address", "shipping_address_display", source_doc.customer_address ) + update_address( + target_doc, "billing_address", "billing_address_display", source_doc.customer_address + ) update_taxes( target_doc, From 98bcb7255d77d828f4ca56959eb888017aa5fb84 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 20 Oct 2022 13:49:46 +0530 Subject: [PATCH 6/8] fix: BOM cost update message (cherry picked from commit 9cfe527492cb740d40d290b6f8ef5da9dfbaed76) # Conflicts: # erpnext/manufacturing/doctype/bom/test_bom.py --- erpnext/manufacturing/doctype/bom/bom.py | 12 +++++++- erpnext/manufacturing/doctype/bom/test_bom.py | 30 +++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 4cf29866326..403b1475770 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -384,6 +384,7 @@ class BOM(WebsiteGenerator): if self.docstatus == 2: return + self.flags.cost_updated = False existing_bom_cost = self.total_cost if self.docstatus == 1: @@ -406,7 +407,11 @@ class BOM(WebsiteGenerator): frappe.get_doc("BOM", bom).update_cost(from_child_bom=True) if not from_child_bom: - frappe.msgprint(_("Cost Updated"), alert=True) + msg = "Cost Updated" + if not self.flags.cost_updated: + msg = "No changes in cost found" + + frappe.msgprint(_(msg), alert=True) def update_parent_cost(self): if self.total_cost: @@ -592,11 +597,16 @@ class BOM(WebsiteGenerator): # not via doc event, table is not regenerated and needs updation self.calculate_exploded_cost() + old_cost = self.total_cost + self.total_cost = self.operating_cost + self.raw_material_cost - self.scrap_material_cost self.base_total_cost = ( self.base_operating_cost + self.base_raw_material_cost - self.base_scrap_material_cost ) + if self.total_cost != old_cost: + self.flags.cost_updated = True + def calculate_op_cost(self, update_hour_rate=False): """Update workstation rate and calculates totals""" self.operating_cost = 0 diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py index 3c91c6d4d9b..0c59ca1e75e 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.py +++ b/erpnext/manufacturing/doctype/bom/test_bom.py @@ -9,8 +9,16 @@ import frappe from frappe.tests.utils import FrappeTestCase from frappe.utils import cstr, flt +<<<<<<< HEAD from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order from erpnext.manufacturing.doctype.bom.bom import BOMRecursionError, item_query +======= +from erpnext.controllers.tests.test_subcontracting_controller import ( + make_stock_in_entry, + set_backflush_based_on, +) +from erpnext.manufacturing.doctype.bom.bom import BOMRecursionError, item_query, make_variant_bom +>>>>>>> 9cfe527492 (fix: BOM cost update message) from erpnext.manufacturing.doctype.bom_update_log.test_bom_update_log import ( update_cost_in_all_boms_in_test, ) @@ -583,6 +591,28 @@ class TestBOM(FrappeTestCase): bom.submit() self.assertEqual(bom.exploded_items[0].rate, bom.items[0].base_rate) + def test_bom_cost_update_flag(self): + rm_item = make_item( + properties={"is_stock_item": 1, "valuation_rate": 99, "last_purchase_rate": 89} + ).name + fg_item = make_item(properties={"is_stock_item": 1}).name + + from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom + + bom = make_bom(item=fg_item, raw_materials=[rm_item]) + + create_stock_reconciliation( + item_code=rm_item, warehouse="_Test Warehouse - _TC", qty=100, rate=600 + ) + + bom.load_from_db() + bom.update_cost() + self.assertTrue(bom.flags.cost_updated) + + bom.load_from_db() + bom.update_cost() + self.assertFalse(bom.flags.cost_updated) + def get_default_bom(item_code="_Test FG Item 2"): return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1}) From 447485ae909df20b1c52012186e4a04056caaabc Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Thu, 20 Oct 2022 15:01:22 +0530 Subject: [PATCH 7/8] fix: conflicts --- erpnext/manufacturing/doctype/bom/test_bom.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py index 0c59ca1e75e..16280fc8106 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.py +++ b/erpnext/manufacturing/doctype/bom/test_bom.py @@ -9,16 +9,8 @@ import frappe from frappe.tests.utils import FrappeTestCase from frappe.utils import cstr, flt -<<<<<<< HEAD from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order from erpnext.manufacturing.doctype.bom.bom import BOMRecursionError, item_query -======= -from erpnext.controllers.tests.test_subcontracting_controller import ( - make_stock_in_entry, - set_backflush_based_on, -) -from erpnext.manufacturing.doctype.bom.bom import BOMRecursionError, item_query, make_variant_bom ->>>>>>> 9cfe527492 (fix: BOM cost update message) from erpnext.manufacturing.doctype.bom_update_log.test_bom_update_log import ( update_cost_in_all_boms_in_test, ) From 7afb19f9656d1f4fb70a19f237ff87c5ce225b84 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Wed, 19 Oct 2022 12:26:56 +0200 Subject: [PATCH 8/8] fix: unset contact details (cherry picked from commit 23f0bb45b01fbe7f1884bceb7a457969df79034e) --- erpnext/public/js/utils/party.js | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/erpnext/public/js/utils/party.js b/erpnext/public/js/utils/party.js index 58594b0a13d..644adff1e27 100644 --- a/erpnext/public/js/utils/party.js +++ b/erpnext/public/js/utils/party.js @@ -242,20 +242,29 @@ erpnext.utils.set_taxes = function(frm, triggered_from_field) { }); }; -erpnext.utils.get_contact_details = function(frm) { +erpnext.utils.get_contact_details = function (frm) { if (frm.updating_party_details) return; if (frm.doc["contact_person"]) { frappe.call({ method: "frappe.contacts.doctype.contact.contact.get_contact_details", - args: {contact: frm.doc.contact_person }, - callback: function(r) { - if (r.message) - frm.set_value(r.message); - } - }) + args: { contact: frm.doc.contact_person }, + callback: function (r) { + if (r.message) frm.set_value(r.message); + }, + }); + } else { + frm.set_value({ + contact_person: "", + contact_display: "", + contact_email: "", + contact_mobile: "", + contact_phone: "", + contact_designation: "", + contact_department: "", + }); } -} +}; erpnext.utils.validate_mandatory = function(frm, label, value, trigger_on) { if (!value) {