From 8010a157b185d3ce299bc4c9a9c3b44dd360acae Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Thu, 24 Aug 2023 11:06:19 +0530 Subject: [PATCH 01/20] fix: use `flt` for qty and rate fields --- .../subcontracting_receipt/subcontracting_receipt.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py index d2bf7e8f1d..53c567af6b 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -183,9 +183,9 @@ class SubcontractingReceipt(SubcontractingController): flt(item.rm_cost_per_qty) + flt(item.service_cost_per_qty) + flt(item.additional_cost_per_qty) ) - item.received_qty = item.qty + flt(item.rejected_qty) - item.amount = item.qty * item.rate - total_qty += item.qty + item.received_qty = flt(item.qty) + flt(item.rejected_qty) + item.amount = flt(item.qty) * flt(item.rate) + total_qty += flt(item.qty) total_amount += item.amount else: self.total_qty = total_qty From 4e7cccbdf08c9be6df0c63bad47797351a53c54b Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Thu, 24 Aug 2023 11:47:11 +0530 Subject: [PATCH 02/20] feat: `Is Scrap Item` field in Subcontracting Receipt Item --- .../subcontracting_receipt_item.json | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json index d72878061c..fc09422306 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json +++ b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json @@ -10,6 +10,7 @@ "item_code", "column_break_2", "item_name", + "is_scrap_item", "section_break_4", "description", "brand", @@ -345,7 +346,8 @@ "label": "BOM", "no_copy": 1, "options": "BOM", - "print_hide": 1 + "print_hide": 1, + "read_only_depends_on": "eval: doc.is_scrap_item" }, { "fetch_from": "item_code.brand", @@ -490,12 +492,22 @@ "no_copy": 1, "options": "Serial and Batch Bundle", "print_hide": 1 + }, + { + "default": "0", + "depends_on": "eval: !doc.bom", + "fieldname": "is_scrap_item", + "fieldtype": "Check", + "label": "Is Scrap Item", + "no_copy": 1, + "print_hide": 1, + "read_only_depends_on": "eval: doc.bom" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2023-07-06 18:43:45.599761", + "modified": "2023-08-24 11:52:22.666506", "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Receipt Item", From d1e877b6f085399ac24192d5d0513800e580efd3 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Thu, 24 Aug 2023 12:16:12 +0530 Subject: [PATCH 03/20] refactor: Subcontracting Receipt Item form view --- .../subcontracting_receipt_item.json | 36 ++++++++----------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json index fc09422306..b6f1155ce3 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json +++ b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json @@ -25,8 +25,6 @@ "col_break2", "stock_uom", "conversion_factor", - "tracking_section", - "col_break_tracking_section", "rate_and_amount", "rate", "amount", @@ -38,15 +36,15 @@ "rm_supp_cost", "warehouse_and_reference", "warehouse", - "rejected_warehouse", "subcontracting_order", - "column_break_40", - "schedule_date", - "quality_inspection", "subcontracting_order_item", "subcontracting_receipt_item", - "section_break_45", + "column_break_40", + "rejected_warehouse", "bom", + "quality_inspection", + "schedule_date", + "section_break_45", "serial_and_batch_bundle", "serial_no", "col_break5", @@ -86,12 +84,13 @@ "fieldtype": "Column Break" }, { + "fetch_from": "item_code.item_name", + "fetch_if_empty": 1, "fieldname": "item_name", "fieldtype": "Data", "in_global_search": 1, "label": "Item Name", - "print_hide": 1, - "reqd": 1 + "print_hide": 1 }, { "collapsible": 1, @@ -100,11 +99,12 @@ "label": "Description" }, { + "fetch_from": "item_code.description", + "fetch_if_empty": 1, "fieldname": "description", "fieldtype": "Text Editor", "label": "Description", "print_width": "300px", - "reqd": 1, "width": "300px" }, { @@ -296,7 +296,8 @@ }, { "fieldname": "section_break_45", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "label": "Serial and Batch Details" }, { "depends_on": "eval:!doc.is_fixed_asset", @@ -322,7 +323,8 @@ "fieldtype": "Small Text", "label": "Rejected Serial No", "no_copy": 1, - "print_hide": 1 + "print_hide": 1, + "read_only": 1 }, { "fieldname": "subcontracting_order_item", @@ -412,14 +414,6 @@ "fieldname": "image_column", "fieldtype": "Column Break" }, - { - "fieldname": "tracking_section", - "fieldtype": "Section Break" - }, - { - "fieldname": "col_break_tracking_section", - "fieldtype": "Column Break" - }, { "fieldname": "accounting_dimensions_section", "fieldtype": "Section Break", @@ -507,7 +501,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2023-08-24 11:52:22.666506", + "modified": "2023-08-24 12:14:25.123857", "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Receipt Item", From a33b75f2cd7ee4512361d8e1b7bb54eaadde551b Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Thu, 24 Aug 2023 16:37:57 +0530 Subject: [PATCH 04/20] feat: allow manually entry for scrap items in SCR --- erpnext/controllers/buying_controller.py | 18 ------- .../controllers/subcontracting_controller.py | 51 +++++++++++++------ .../subcontracting_receipt.py | 36 ++++++------- 3 files changed, 54 insertions(+), 51 deletions(-) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index b396b27da7..b1ce539bc3 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -436,24 +436,6 @@ class BuyingController(SubcontractingController): # validate rate with ref PR - def validate_rejected_warehouse(self): - for item in self.get("items"): - if flt(item.rejected_qty) and not item.rejected_warehouse: - if self.rejected_warehouse: - item.rejected_warehouse = self.rejected_warehouse - - if not item.rejected_warehouse: - frappe.throw( - _("Row #{0}: Rejected Warehouse is mandatory for the rejected Item {1}").format( - item.idx, item.item_code - ) - ) - - if item.get("rejected_warehouse") and (item.get("rejected_warehouse") == item.get("warehouse")): - frappe.throw( - _("Row #{0}: Accepted Warehouse and Rejected Warehouse cannot be same").format(item.idx) - ) - # validate accepted and rejected qty def validate_accepted_rejected_qty(self): for d in self.get("items"): diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py index 6633f4f6eb..837969728b 100644 --- a/erpnext/controllers/subcontracting_controller.py +++ b/erpnext/controllers/subcontracting_controller.py @@ -55,6 +55,23 @@ class SubcontractingController(StockController): else: super(SubcontractingController, self).validate() + def validate_rejected_warehouse(self): + for item in self.get("items"): + if flt(item.rejected_qty) and not item.rejected_warehouse: + if self.rejected_warehouse: + item.rejected_warehouse = self.rejected_warehouse + else: + frappe.throw( + _("Row #{0}: Rejected Warehouse is mandatory for the rejected Item {1}").format( + item.idx, item.item_code + ) + ) + + if item.get("rejected_warehouse") and (item.get("rejected_warehouse") == item.get("warehouse")): + frappe.throw( + _("Row #{0}: Accepted Warehouse and Rejected Warehouse cannot be same").format(item.idx) + ) + def remove_empty_rows(self): for key in ["service_items", "items", "supplied_items"]: if self.get(key): @@ -80,23 +97,27 @@ class SubcontractingController(StockController): if not is_stock_item: frappe.throw(_("Row {0}: Item {1} must be a stock item.").format(item.idx, item.item_name)) - if not is_sub_contracted_item: - frappe.throw( - _("Row {0}: Item {1} must be a subcontracted item.").format(item.idx, item.item_name) - ) + if not item.is_scrap_item: + if not is_sub_contracted_item: + frappe.throw( + _("Row {0}: Item {1} must be a subcontracted item.").format(item.idx, item.item_name) + ) - if item.bom: - bom = frappe.get_doc("BOM", item.bom) - if not bom.is_active: - frappe.throw( - _("Row {0}: Please select an active BOM for Item {1}.").format(item.idx, item.item_name) - ) - if bom.item != item.item_code: - frappe.throw( - _("Row {0}: Please select an valid BOM for Item {1}.").format(item.idx, item.item_name) - ) + if item.bom: + is_active, bom_item = frappe.get_value("BOM", item.bom, ["is_active", "item"]) + + if not is_active: + frappe.throw( + _("Row {0}: Please select an active BOM for Item {1}.").format(item.idx, item.item_name) + ) + if bom_item != item.item_code: + frappe.throw( + _("Row {0}: Please select an valid BOM for Item {1}.").format(item.idx, item.item_name) + ) + else: + frappe.throw(_("Row {0}: Please select a BOM for Item {1}.").format(item.idx, item.item_name)) else: - frappe.throw(_("Row {0}: Please select a BOM for Item {1}.").format(item.idx, item.item_name)) + item.bom = None def __get_data_before_save(self): item_dict = {} diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py index 53c567af6b..b1a585d147 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -79,6 +79,7 @@ class SubcontractingReceipt(SubcontractingController): super(SubcontractingReceipt, self).validate() self.set_missing_values() self.validate_posting_time() + self.validate_accepted_warehouse() self.validate_rejected_warehouse() if getdate(self.posting_date) > getdate(nowdate()): @@ -127,6 +128,23 @@ class SubcontractingReceipt(SubcontractingController): self.calculate_supplied_items_qty_and_amount() self.calculate_items_qty_and_amount() + def validate_accepted_warehouse(self): + for item in self.get("items"): + if flt(item.qty) and not item.warehouse: + if self.set_warehouse: + item.warehouse = self.set_warehouse + else: + frappe.throw( + _("Row #{0}: Accepted Warehouse is mandatory for the accepted Item {1}").format( + item.idx, item.item_code + ) + ) + + if item.get("warehouse") and (item.get("warehouse") == item.get("rejected_warehouse")): + frappe.throw( + _("Row #{0}: Accepted Warehouse and Rejected Warehouse cannot be same").format(item.idx) + ) + def set_available_qty_for_consumption(self): supplied_items_details = {} @@ -191,24 +209,6 @@ class SubcontractingReceipt(SubcontractingController): self.total_qty = total_qty self.total = total_amount - def validate_rejected_warehouse(self): - for item in self.items: - if flt(item.rejected_qty) and not item.rejected_warehouse: - if self.rejected_warehouse: - item.rejected_warehouse = self.rejected_warehouse - - if not item.rejected_warehouse: - frappe.throw( - _("Row #{0}: Rejected Warehouse is mandatory for the rejected Item {1}").format( - item.idx, item.item_code - ) - ) - - if item.get("rejected_warehouse") and (item.get("rejected_warehouse") == item.get("warehouse")): - frappe.throw( - _("Row #{0}: Accepted Warehouse and Rejected Warehouse cannot be same").format(item.idx) - ) - def validate_available_qty_for_consumption(self): for item in self.get("supplied_items"): precision = item.precision("consumed_qty") From f78b6d00817e9f73ffb343e1b2363db510639466 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Thu, 24 Aug 2023 18:56:16 +0530 Subject: [PATCH 05/20] refactor: code cleanup --- .../subcontracting_receipt.py | 315 ++++++++++-------- 1 file changed, 175 insertions(+), 140 deletions(-) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py index b1a585d147..2c7b2eb99e 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -36,33 +36,6 @@ class SubcontractingReceipt(SubcontractingController): ), ) - def update_status_updater_args(self): - if cint(self.is_return): - self.status_updater.extend( - [ - { - "source_dt": "Subcontracting Receipt Item", - "target_dt": "Subcontracting Order Item", - "join_field": "subcontracting_order_item", - "target_field": "returned_qty", - "source_field": "-1 * qty", - "extra_cond": """ and exists (select name from `tabSubcontracting Receipt` - where name=`tabSubcontracting Receipt Item`.parent and is_return=1)""", - }, - { - "source_dt": "Subcontracting Receipt Item", - "target_dt": "Subcontracting Receipt Item", - "join_field": "subcontracting_receipt_item", - "target_field": "returned_qty", - "target_parent_dt": "Subcontracting Receipt", - "target_parent_field": "per_returned", - "target_ref_field": "received_qty", - "source_field": "-1 * received_qty", - "percent_join_field_parent": "return_against", - }, - ] - ) - def before_validate(self): super(SubcontractingReceipt, self).before_validate() self.validate_items_qty() @@ -71,16 +44,18 @@ class SubcontractingReceipt(SubcontractingController): self.set_items_expense_account() def validate(self): - if ( - frappe.db.get_single_value("Buying Settings", "backflush_raw_materials_of_subcontract_based_on") - == "BOM" - ): - self.supplied_items = [] + self.reset_supplied_items() super(SubcontractingReceipt, self).validate() + + if self.is_new() and self.get("_action") == "save": + self.get_scrap_items() + self.set_missing_values() self.validate_posting_time() - self.validate_accepted_warehouse() - self.validate_rejected_warehouse() + + if self.get("_action") == "submit": + self.validate_accepted_warehouse() + self.validate_rejected_warehouse() if getdate(self.posting_date) > getdate(nowdate()): frappe.throw(_("Posting Date cannot be future date")) @@ -89,11 +64,6 @@ class SubcontractingReceipt(SubcontractingController): self.reset_default_field_value("rejected_warehouse", "items", "rejected_warehouse") self.get_current_stock() - def on_update(self): - for table_field in ["items", "supplied_items"]: - if self.get(table_field): - self.set_serial_and_batch_bundle(table_field) - def on_submit(self): self.validate_available_qty_for_consumption() self.update_status_updater_args() @@ -105,6 +75,11 @@ class SubcontractingReceipt(SubcontractingController): self.repost_future_sle_and_gle() self.update_status() + def on_update(self): + for table_field in ["items", "supplied_items"]: + if self.get(table_field): + self.set_serial_and_batch_bundle(table_field) + def on_cancel(self): self.ignore_linked_doctypes = ( "GL Entry", @@ -122,107 +97,6 @@ class SubcontractingReceipt(SubcontractingController): self.set_subcontracting_order_status() self.update_status() - @frappe.whitelist() - def set_missing_values(self): - self.calculate_additional_costs() - self.calculate_supplied_items_qty_and_amount() - self.calculate_items_qty_and_amount() - - def validate_accepted_warehouse(self): - for item in self.get("items"): - if flt(item.qty) and not item.warehouse: - if self.set_warehouse: - item.warehouse = self.set_warehouse - else: - frappe.throw( - _("Row #{0}: Accepted Warehouse is mandatory for the accepted Item {1}").format( - item.idx, item.item_code - ) - ) - - if item.get("warehouse") and (item.get("warehouse") == item.get("rejected_warehouse")): - frappe.throw( - _("Row #{0}: Accepted Warehouse and Rejected Warehouse cannot be same").format(item.idx) - ) - - def set_available_qty_for_consumption(self): - supplied_items_details = {} - - sco_supplied_item = frappe.qb.DocType("Subcontracting Order Supplied Item") - for item in self.get("items"): - supplied_items = ( - frappe.qb.from_(sco_supplied_item) - .select( - sco_supplied_item.rm_item_code, - sco_supplied_item.reference_name, - (sco_supplied_item.total_supplied_qty - sco_supplied_item.consumed_qty).as_("available_qty"), - ) - .where( - (sco_supplied_item.parent == item.subcontracting_order) - & (sco_supplied_item.main_item_code == item.item_code) - & (sco_supplied_item.reference_name == item.subcontracting_order_item) - ) - ).run(as_dict=True) - - if supplied_items: - supplied_items_details[item.name] = {} - - for supplied_item in supplied_items: - supplied_items_details[item.name][supplied_item.rm_item_code] = supplied_item.available_qty - else: - for item in self.get("supplied_items"): - item.available_qty_for_consumption = supplied_items_details.get(item.reference_name, {}).get( - item.rm_item_code, 0 - ) - - def calculate_supplied_items_qty_and_amount(self): - for item in self.get("supplied_items") or []: - item.amount = item.rate * item.consumed_qty - - self.set_available_qty_for_consumption() - - def calculate_items_qty_and_amount(self): - rm_supp_cost = {} - for item in self.get("supplied_items") or []: - if item.reference_name in rm_supp_cost: - rm_supp_cost[item.reference_name] += item.amount - else: - rm_supp_cost[item.reference_name] = item.amount - - total_qty = total_amount = 0 - for item in self.items: - if item.qty and item.name in rm_supp_cost: - item.rm_supp_cost = rm_supp_cost[item.name] - item.rm_cost_per_qty = item.rm_supp_cost / item.qty - rm_supp_cost.pop(item.name) - - if item.recalculate_rate: - item.rate = ( - flt(item.rm_cost_per_qty) + flt(item.service_cost_per_qty) + flt(item.additional_cost_per_qty) - ) - - item.received_qty = flt(item.qty) + flt(item.rejected_qty) - item.amount = flt(item.qty) * flt(item.rate) - total_qty += flt(item.qty) - total_amount += item.amount - else: - self.total_qty = total_qty - self.total = total_amount - - def validate_available_qty_for_consumption(self): - for item in self.get("supplied_items"): - precision = item.precision("consumed_qty") - if ( - item.available_qty_for_consumption - and flt(item.available_qty_for_consumption, precision) - flt(item.consumed_qty, precision) < 0 - ): - msg = f"""Row {item.idx}: Consumed Qty {flt(item.consumed_qty, precision)} - must be less than or equal to Available Qty For Consumption - {flt(item.available_qty_for_consumption, precision)} - in Consumed Items Table.""" - - frappe.throw(_(msg)) - def validate_items_qty(self): for item in self.items: if not (item.qty or item.rejected_qty): @@ -264,6 +138,167 @@ class SubcontractingReceipt(SubcontractingController): if not item.expense_account: item.expense_account = expense_account + def reset_supplied_items(self): + if ( + frappe.db.get_single_value("Buying Settings", "backflush_raw_materials_of_subcontract_based_on") + == "BOM" + ): + self.supplied_items = [] + + def get_scrap_items(self): + if ( + frappe.db.get_single_value("Buying Settings", "backflush_raw_materials_of_subcontract_based_on") + == "BOM" + ): + for item in list(self.items): + if item.bom and not item.is_scrap_item: + bom = frappe.get_doc("BOM", item.bom) + for scrap_item in bom.scrap_items: + qty = flt(item.received_qty) * (flt(scrap_item.stock_qty) / flt(bom.quantity)) + self.append( + "items", + { + "item_code": scrap_item.item_code, + "item_name": scrap_item.item_name, + "qty": qty, + "stock_uom": scrap_item.stock_uom, + "rate": scrap_item.rate, + "amount": qty * scrap_item.rate, + "is_scrap_item": 1, + "warehouse": self.set_warehouse, + "rejected_warehouse": self.rejected_warehouse, + "service_cost_per_qty": 0, + }, + ) + + @frappe.whitelist() + def set_missing_values(self): + self.calculate_additional_costs() + self.calculate_supplied_items_qty_and_amount() + self.calculate_items_qty_and_amount() + + def calculate_supplied_items_qty_and_amount(self): + for item in self.get("supplied_items") or []: + item.amount = item.rate * item.consumed_qty + + self.set_available_qty_for_consumption() + + def set_available_qty_for_consumption(self): + supplied_items_details = {} + + sco_supplied_item = frappe.qb.DocType("Subcontracting Order Supplied Item") + for item in self.get("items"): + supplied_items = ( + frappe.qb.from_(sco_supplied_item) + .select( + sco_supplied_item.rm_item_code, + sco_supplied_item.reference_name, + (sco_supplied_item.total_supplied_qty - sco_supplied_item.consumed_qty).as_("available_qty"), + ) + .where( + (sco_supplied_item.parent == item.subcontracting_order) + & (sco_supplied_item.main_item_code == item.item_code) + & (sco_supplied_item.reference_name == item.subcontracting_order_item) + ) + ).run(as_dict=True) + + if supplied_items: + supplied_items_details[item.name] = {} + + for supplied_item in supplied_items: + supplied_items_details[item.name][supplied_item.rm_item_code] = supplied_item.available_qty + else: + for item in self.get("supplied_items"): + item.available_qty_for_consumption = supplied_items_details.get(item.reference_name, {}).get( + item.rm_item_code, 0 + ) + + def calculate_items_qty_and_amount(self): + rm_supp_cost = {} + for item in self.get("supplied_items") or []: + if item.reference_name in rm_supp_cost: + rm_supp_cost[item.reference_name] += item.amount + else: + rm_supp_cost[item.reference_name] = item.amount + + total_qty = total_amount = 0 + for item in self.items: + if item.qty and item.name in rm_supp_cost: + item.rm_supp_cost = rm_supp_cost[item.name] + item.rm_cost_per_qty = item.rm_supp_cost / item.qty + rm_supp_cost.pop(item.name) + + if item.recalculate_rate: + item.rate = ( + flt(item.rm_cost_per_qty) + flt(item.service_cost_per_qty) + flt(item.additional_cost_per_qty) + ) + + item.received_qty = flt(item.qty) + flt(item.rejected_qty) + item.amount = flt(item.qty) * flt(item.rate) + total_qty += flt(item.qty) + total_amount += item.amount + else: + self.total_qty = total_qty + self.total = total_amount + + def validate_accepted_warehouse(self): + for item in self.get("items"): + if flt(item.qty) and not item.warehouse: + if self.set_warehouse: + item.warehouse = self.set_warehouse + else: + frappe.throw( + _("Row #{0}: Accepted Warehouse is mandatory for the accepted Item {1}").format( + item.idx, item.item_code + ) + ) + + if item.get("warehouse") and (item.get("warehouse") == item.get("rejected_warehouse")): + frappe.throw( + _("Row #{0}: Accepted Warehouse and Rejected Warehouse cannot be same").format(item.idx) + ) + + def validate_available_qty_for_consumption(self): + for item in self.get("supplied_items"): + precision = item.precision("consumed_qty") + if ( + item.available_qty_for_consumption + and flt(item.available_qty_for_consumption, precision) - flt(item.consumed_qty, precision) < 0 + ): + msg = f"""Row {item.idx}: Consumed Qty {flt(item.consumed_qty, precision)} + must be less than or equal to Available Qty For Consumption + {flt(item.available_qty_for_consumption, precision)} + in Consumed Items Table.""" + + frappe.throw(_(msg)) + + def update_status_updater_args(self): + if cint(self.is_return): + self.status_updater.extend( + [ + { + "source_dt": "Subcontracting Receipt Item", + "target_dt": "Subcontracting Order Item", + "join_field": "subcontracting_order_item", + "target_field": "returned_qty", + "source_field": "-1 * qty", + "extra_cond": """ and exists (select name from `tabSubcontracting Receipt` + where name=`tabSubcontracting Receipt Item`.parent and is_return=1)""", + }, + { + "source_dt": "Subcontracting Receipt Item", + "target_dt": "Subcontracting Receipt Item", + "join_field": "subcontracting_receipt_item", + "target_field": "returned_qty", + "target_parent_dt": "Subcontracting Receipt", + "target_parent_field": "per_returned", + "target_ref_field": "received_qty", + "source_field": "-1 * received_qty", + "percent_join_field_parent": "return_against", + }, + ] + ) + def update_status(self, status=None, update_modified=False): if not status: if self.docstatus == 0: From 794edbb334660287d0264aa3a3c4aaa1d1033df7 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Thu, 24 Aug 2023 22:02:29 +0530 Subject: [PATCH 06/20] feat: add `Scrap Cost Per Qty` field in SCR Item --- .../subcontracting_receipt_item.json | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json index b6f1155ce3..1ac5bcb480 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json +++ b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json @@ -33,6 +33,7 @@ "rm_cost_per_qty", "service_cost_per_qty", "additional_cost_per_qty", + "scrap_cost_per_qty", "rm_supp_cost", "warehouse_and_reference", "warehouse", @@ -44,6 +45,7 @@ "bom", "quality_inspection", "schedule_date", + "reference_name", "section_break_45", "serial_and_batch_bundle", "serial_no", @@ -158,6 +160,7 @@ "no_copy": 1, "print_hide": 1, "print_width": "100px", + "read_only_depends_on": "eval: doc.is_scrap_item", "width": "100px" }, { @@ -215,6 +218,8 @@ "fieldtype": "Column Break" }, { + "default": "0", + "depends_on": "eval: !doc.is_scrap_item", "fieldname": "rm_cost_per_qty", "fieldtype": "Currency", "label": "Raw Material Cost Per Qty", @@ -222,6 +227,8 @@ "read_only": 1 }, { + "default": "0", + "depends_on": "eval: !doc.is_scrap_item", "fieldname": "service_cost_per_qty", "fieldtype": "Currency", "label": "Service Cost Per Qty", @@ -261,6 +268,7 @@ "options": "Warehouse", "print_hide": 1, "print_width": "100px", + "read_only_depends_on": "eval: doc.is_scrap_item", "width": "100px" }, { @@ -452,6 +460,7 @@ "print_hide": 1 }, { + "default": "0", "depends_on": "returned_qty", "fieldname": "returned_qty", "fieldtype": "Float", @@ -496,12 +505,29 @@ "no_copy": 1, "print_hide": 1, "read_only_depends_on": "eval: doc.bom" + }, + { + "default": "0", + "depends_on": "eval: !doc.is_scrap_item", + "fieldname": "scrap_cost_per_qty", + "fieldtype": "Float", + "label": "Scrap Cost Per Qty", + "no_copy": 1, + "non_negative": 1, + "read_only": 1 + }, + { + "fieldname": "reference_name", + "fieldtype": "Data", + "label": "Reference Name", + "no_copy": 1, + "read_only": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2023-08-24 12:14:25.123857", + "modified": "2023-08-24 21:57:15.897010", "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Receipt Item", From a6b2cf3acdb9e83ca41bc01b0796d531faf59d1b Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Thu, 24 Aug 2023 22:07:24 +0530 Subject: [PATCH 07/20] fix: reduce scrap cost from FG total cost --- .../subcontracting_receipt.py | 47 ++++++++++++++----- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py index 2c7b2eb99e..07f70d1188 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -150,11 +150,13 @@ class SubcontractingReceipt(SubcontractingController): frappe.db.get_single_value("Buying Settings", "backflush_raw_materials_of_subcontract_based_on") == "BOM" ): + self.remove_scrap_items() + for item in list(self.items): - if item.bom and not item.is_scrap_item: + if item.bom: bom = frappe.get_doc("BOM", item.bom) for scrap_item in bom.scrap_items: - qty = flt(item.received_qty) * (flt(scrap_item.stock_qty) / flt(bom.quantity)) + qty = flt(item.qty) * (flt(scrap_item.stock_qty) / flt(bom.quantity)) self.append( "items", { @@ -168,9 +170,15 @@ class SubcontractingReceipt(SubcontractingController): "warehouse": self.set_warehouse, "rejected_warehouse": self.rejected_warehouse, "service_cost_per_qty": 0, + "reference_name": item.name, }, ) + def remove_scrap_items(self): + for item in list(self.items): + if item.is_scrap_item: + self.remove(item) + @frappe.whitelist() def set_missing_values(self): self.calculate_additional_costs() @@ -214,27 +222,44 @@ class SubcontractingReceipt(SubcontractingController): ) def calculate_items_qty_and_amount(self): - rm_supp_cost = {} + rm_cost_map = {} for item in self.get("supplied_items") or []: - if item.reference_name in rm_supp_cost: - rm_supp_cost[item.reference_name] += item.amount + if item.reference_name in rm_cost_map: + rm_cost_map[item.reference_name] += item.amount else: - rm_supp_cost[item.reference_name] = item.amount + rm_cost_map[item.reference_name] = item.amount + + scrap_cost_map = {} + for item in self.get("items") or []: + if item.is_scrap_item: + if item.reference_name in scrap_cost_map: + scrap_cost_map[item.reference_name] += item.amount + else: + scrap_cost_map[item.reference_name] = item.amount total_qty = total_amount = 0 for item in self.items: - if item.qty and item.name in rm_supp_cost: - item.rm_supp_cost = rm_supp_cost[item.name] - item.rm_cost_per_qty = item.rm_supp_cost / item.qty - rm_supp_cost.pop(item.name) + if item.qty and not item.is_scrap_item: + if item.name in rm_cost_map: + item.rm_supp_cost = rm_cost_map[item.name] + item.rm_cost_per_qty = item.rm_supp_cost / item.qty + rm_cost_map.pop(item.name) + + if item.name in scrap_cost_map: + item.scrap_cost_per_qty = scrap_cost_map[item.name] / item.qty + scrap_cost_map.pop(item.name) if item.recalculate_rate: item.rate = ( - flt(item.rm_cost_per_qty) + flt(item.service_cost_per_qty) + flt(item.additional_cost_per_qty) + flt(item.rm_cost_per_qty) + + flt(item.service_cost_per_qty) + + flt(item.additional_cost_per_qty) + - flt(item.scrap_cost_per_qty) ) item.received_qty = flt(item.qty) + flt(item.rejected_qty) item.amount = flt(item.qty) * flt(item.rate) + total_qty += flt(item.qty) total_amount += item.amount else: From 40a6b5cefef6abba086f06fff78e919f5f779bbc Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Fri, 25 Aug 2023 09:35:29 +0530 Subject: [PATCH 08/20] fix: don't recalculate rate for scrap items --- .../subcontracting_receipt.py | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py index 07f70d1188..9bf21030b4 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -171,6 +171,7 @@ class SubcontractingReceipt(SubcontractingController): "rejected_warehouse": self.rejected_warehouse, "service_cost_per_qty": 0, "reference_name": item.name, + "recalculate_rate": 0, }, ) @@ -239,23 +240,24 @@ class SubcontractingReceipt(SubcontractingController): total_qty = total_amount = 0 for item in self.items: - if item.qty and not item.is_scrap_item: - if item.name in rm_cost_map: - item.rm_supp_cost = rm_cost_map[item.name] - item.rm_cost_per_qty = item.rm_supp_cost / item.qty - rm_cost_map.pop(item.name) + if not item.is_scrap_item: + if item.qty: + if item.name in rm_cost_map: + item.rm_supp_cost = rm_cost_map[item.name] + item.rm_cost_per_qty = item.rm_supp_cost / item.qty + rm_cost_map.pop(item.name) - if item.name in scrap_cost_map: - item.scrap_cost_per_qty = scrap_cost_map[item.name] / item.qty - scrap_cost_map.pop(item.name) + if item.name in scrap_cost_map: + item.scrap_cost_per_qty = scrap_cost_map[item.name] / item.qty + scrap_cost_map.pop(item.name) - if item.recalculate_rate: - item.rate = ( - flt(item.rm_cost_per_qty) - + flt(item.service_cost_per_qty) - + flt(item.additional_cost_per_qty) - - flt(item.scrap_cost_per_qty) - ) + if item.recalculate_rate: + item.rate = ( + flt(item.rm_cost_per_qty) + + flt(item.service_cost_per_qty) + + flt(item.additional_cost_per_qty) + - flt(item.scrap_cost_per_qty) + ) item.received_qty = flt(item.qty) + flt(item.rejected_qty) item.amount = flt(item.qty) * flt(item.rate) From 199071b7731d729ddb1248dace1ecda6bd6743bb Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Fri, 25 Aug 2023 09:36:12 +0530 Subject: [PATCH 09/20] fix(ux): make `Recalculate Rate` field hidden for scrap items --- .../subcontracting_receipt_item.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json index 1ac5bcb480..5442dfddd7 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json +++ b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json @@ -476,9 +476,11 @@ }, { "default": "1", + "depends_on": "eval: !doc.is_scrap_item", "fieldname": "recalculate_rate", "fieldtype": "Check", - "label": "Recalculate Rate" + "label": "Recalculate Rate", + "read_only_depends_on": "eval: doc.is_scrap_item" }, { "fieldname": "serial_and_batch_bundle", @@ -519,6 +521,7 @@ { "fieldname": "reference_name", "fieldtype": "Data", + "hidden": 1, "label": "Reference Name", "no_copy": 1, "read_only": 1 @@ -527,7 +530,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2023-08-24 21:57:15.897010", + "modified": "2023-08-25 09:33:47.232140", "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Receipt Item", From 7a6db924d524ebaec9101c64087db69d06a232ef Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Fri, 25 Aug 2023 09:56:48 +0530 Subject: [PATCH 10/20] fix: validate scrap items --- .../subcontracting_receipt.py | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py index 9bf21030b4..36be4d1035 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -45,21 +45,23 @@ class SubcontractingReceipt(SubcontractingController): def validate(self): self.reset_supplied_items() + self.validate_posting_time() + + if getdate(self.posting_date) > getdate(nowdate()): + frappe.throw(_("Posting Date cannot be future date")) + super(SubcontractingReceipt, self).validate() if self.is_new() and self.get("_action") == "save": self.get_scrap_items() self.set_missing_values() - self.validate_posting_time() if self.get("_action") == "submit": + self.validate_scrap_items() self.validate_accepted_warehouse() self.validate_rejected_warehouse() - if getdate(self.posting_date) > getdate(nowdate()): - frappe.throw(_("Posting Date cannot be future date")) - self.reset_default_field_value("set_warehouse", "items", "warehouse") self.reset_default_field_value("rejected_warehouse", "items", "rejected_warehouse") self.get_current_stock() @@ -268,6 +270,28 @@ class SubcontractingReceipt(SubcontractingController): self.total_qty = total_qty self.total = total_amount + def validate_scrap_items(self): + for item in self.items: + if item.is_scrap_item: + if not item.qty: + frappe.throw( + _("Row #{0}: Scrap Item Qty cannot be zero").format(item.idx), + ) + + if item.rejected_qty: + frappe.throw( + _("Row #{0}: Rejected Qty cannot be set for Scrap Item {1}.").format( + item.idx, frappe.bold(item.item_code) + ), + ) + + if not item.reference_name: + frappe.throw( + _("Row #{0}: Finished Good reference is mandatory for Scrap Item {1}.").format( + item.idx, frappe.bold(item.item_code) + ), + ) + def validate_accepted_warehouse(self): for item in self.get("items"): if flt(item.qty) and not item.warehouse: From 9b47617117988a73a5f9c3bc36693d14809e896f Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Fri, 25 Aug 2023 10:53:13 +0530 Subject: [PATCH 11/20] feat: button to get Scrap Items --- .../subcontracting_receipt/subcontracting_receipt.js | 4 ++++ .../subcontracting_receipt/subcontracting_receipt.json | 9 ++++++++- .../subcontracting_receipt/subcontracting_receipt.py | 8 ++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js index 94a2589b98..3824853255 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js @@ -204,6 +204,10 @@ frappe.ui.form.on('Subcontracting Receipt Item', { rate(frm) { set_missing_values(frm); }, + + items_remove: function(frm) { + set_missing_values(frm); + } }); frappe.ui.form.on('Subcontracting Receipt Supplied Item', { diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json index 4b3cc8365c..95fe087d9a 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json @@ -40,6 +40,7 @@ "col_break_warehouse", "supplier_warehouse", "items_section", + "get_scrap_items", "items", "section_break0", "total_qty", @@ -626,12 +627,18 @@ "fieldtype": "Check", "label": "Edit Posting Date and Time", "print_hide": 1 + }, + { + "fieldname": "get_scrap_items", + "fieldtype": "Button", + "label": "Get Scrap Items", + "options": "get_scrap_items" } ], "in_create": 1, "is_submittable": 1, "links": [], - "modified": "2023-07-06 18:43:16.171842", + "modified": "2023-08-25 10:02:11.546559", "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Receipt", diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py index 36be4d1035..5f64ab1323 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -147,6 +147,7 @@ class SubcontractingReceipt(SubcontractingController): ): self.supplied_items = [] + @frappe.whitelist() def get_scrap_items(self): if ( frappe.db.get_single_value("Buying Settings", "backflush_raw_materials_of_subcontract_based_on") @@ -176,11 +177,16 @@ class SubcontractingReceipt(SubcontractingController): "recalculate_rate": 0, }, ) + else: + self.calculate_additional_costs() + self.calculate_items_qty_and_amount() def remove_scrap_items(self): for item in list(self.items): if item.is_scrap_item: self.remove(item) + else: + item.scrap_cost_per_qty = 0 @frappe.whitelist() def set_missing_values(self): @@ -239,6 +245,8 @@ class SubcontractingReceipt(SubcontractingController): scrap_cost_map[item.reference_name] += item.amount else: scrap_cost_map[item.reference_name] = item.amount + else: + item.scrap_cost_per_qty = 0 total_qty = total_amount = 0 for item in self.items: From 879d31a5881490962a0f6e253887076582edbf64 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Fri, 25 Aug 2023 16:13:08 +0530 Subject: [PATCH 12/20] fix: recalculate rate while getting scrap items --- .../subcontracting_receipt.js | 29 ++++++++++-- .../subcontracting_receipt.py | 44 +++++++++++-------- .../subcontracting_receipt_item.json | 7 +-- 3 files changed, 54 insertions(+), 26 deletions(-) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js index 3824853255..1a20f0ccf5 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js @@ -3,7 +3,7 @@ frappe.provide('erpnext.buying'); -erpnext.landed_cost_taxes_and_charges.setup_triggers("Subcontracting Receipt"); +erpnext.landed_cost_taxes_and_charges.setup_triggers('Subcontracting Receipt'); frappe.ui.form.on('Subcontracting Receipt', { setup: (frm) => { @@ -77,13 +77,13 @@ frappe.ui.form.on('Subcontracting Receipt', { } }); - frm.set_query("serial_and_batch_bundle", "supplied_items", (doc, cdt, cdn) => { + frm.set_query('serial_and_batch_bundle', 'supplied_items', (doc, cdt, cdn) => { let row = locals[cdt][cdn]; return { filters: { 'item_code': row.rm_item_code, 'voucher_type': doc.doctype, - 'voucher_no': ["in", [doc.name, ""]], + 'voucher_no': ['in', [doc.name, '']], 'is_cancelled': 0, } } @@ -180,6 +180,23 @@ frappe.ui.form.on('Subcontracting Receipt', { rejected_warehouse: (frm) => { set_warehouse_in_children(frm.doc.items, 'rejected_warehouse', frm.doc.rejected_warehouse); }, + + get_scrap_items: (frm) => { + frappe.call({ + doc: frm.doc, + method: 'get_scrap_items', + args: { + recalculate_rate: true + }, + freeze: true, + freeze_message: __('Getting Scrap Items'), + callback: (r) => { + if (!r.exc) { + frm.refresh(); + } + } + }); + }, }); frappe.ui.form.on('Landed Cost Taxes and Charges', { @@ -205,6 +222,12 @@ frappe.ui.form.on('Subcontracting Receipt Item', { set_missing_values(frm); }, + recalculate_rate(frm) { + if (frm.doc.recalculate_rate) { + set_missing_values(frm); + } + }, + items_remove: function(frm) { set_missing_values(frm); } diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py index 5f64ab1323..09db66f97a 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -148,7 +148,7 @@ class SubcontractingReceipt(SubcontractingController): self.supplied_items = [] @frappe.whitelist() - def get_scrap_items(self): + def get_scrap_items(self, recalculate_rate=False): if ( frappe.db.get_single_value("Buying Settings", "backflush_raw_materials_of_subcontract_based_on") == "BOM" @@ -163,42 +163,43 @@ class SubcontractingReceipt(SubcontractingController): self.append( "items", { + "is_scrap_item": 1, + "reference_name": item.name, "item_code": scrap_item.item_code, "item_name": scrap_item.item_name, "qty": qty, "stock_uom": scrap_item.stock_uom, + "recalculate_rate": 1, "rate": scrap_item.rate, + "rm_cost_per_qty": 0, + "service_cost_per_qty": 0, + "additional_cost_per_qty": 0, + "scrap_cost_per_qty": scrap_item.rate, "amount": qty * scrap_item.rate, - "is_scrap_item": 1, "warehouse": self.set_warehouse, "rejected_warehouse": self.rejected_warehouse, - "service_cost_per_qty": 0, - "reference_name": item.name, - "recalculate_rate": 0, }, ) - else: + + if recalculate_rate: self.calculate_additional_costs() self.calculate_items_qty_and_amount() - def remove_scrap_items(self): + def remove_scrap_items(self, recalculate_rate=False): for item in list(self.items): if item.is_scrap_item: self.remove(item) else: item.scrap_cost_per_qty = 0 + if recalculate_rate: + self.calculate_items_qty_and_amount() + @frappe.whitelist() def set_missing_values(self): - self.calculate_additional_costs() - self.calculate_supplied_items_qty_and_amount() - self.calculate_items_qty_and_amount() - - def calculate_supplied_items_qty_and_amount(self): - for item in self.get("supplied_items") or []: - item.amount = item.rate * item.consumed_qty - self.set_available_qty_for_consumption() + self.calculate_additional_costs() + self.calculate_items_qty_and_amount() def set_available_qty_for_consumption(self): supplied_items_details = {} @@ -233,6 +234,8 @@ class SubcontractingReceipt(SubcontractingController): def calculate_items_qty_and_amount(self): rm_cost_map = {} for item in self.get("supplied_items") or []: + item.amount = flt(item.consumed_qty) * flt(item.rate) + if item.reference_name in rm_cost_map: rm_cost_map[item.reference_name] += item.amount else: @@ -241,15 +244,18 @@ class SubcontractingReceipt(SubcontractingController): scrap_cost_map = {} for item in self.get("items") or []: if item.is_scrap_item: + if item.recalculate_rate: + item.rate = flt(item.scrap_cost_per_qty) + flt(item.additional_cost_per_qty) + + item.amount = flt(item.qty) * flt(item.rate) + if item.reference_name in scrap_cost_map: scrap_cost_map[item.reference_name] += item.amount else: scrap_cost_map[item.reference_name] = item.amount - else: - item.scrap_cost_per_qty = 0 total_qty = total_amount = 0 - for item in self.items: + for item in self.get("items") or []: if not item.is_scrap_item: if item.qty: if item.name in rm_cost_map: @@ -260,6 +266,8 @@ class SubcontractingReceipt(SubcontractingController): if item.name in scrap_cost_map: item.scrap_cost_per_qty = scrap_cost_map[item.name] / item.qty scrap_cost_map.pop(item.name) + else: + item.scrap_cost_per_qty = 0 if item.recalculate_rate: item.rate = ( diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json index 5442dfddd7..b333627b4a 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json +++ b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json @@ -476,11 +476,9 @@ }, { "default": "1", - "depends_on": "eval: !doc.is_scrap_item", "fieldname": "recalculate_rate", "fieldtype": "Check", - "label": "Recalculate Rate", - "read_only_depends_on": "eval: doc.is_scrap_item" + "label": "Recalculate Rate" }, { "fieldname": "serial_and_batch_bundle", @@ -510,7 +508,6 @@ }, { "default": "0", - "depends_on": "eval: !doc.is_scrap_item", "fieldname": "scrap_cost_per_qty", "fieldtype": "Float", "label": "Scrap Cost Per Qty", @@ -530,7 +527,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2023-08-25 09:33:47.232140", + "modified": "2023-08-25 15:42:36.923833", "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Receipt Item", From 1504ff8b6c299a10ca7e4af263561a83bb6d3d9c Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Fri, 25 Aug 2023 20:18:30 +0530 Subject: [PATCH 13/20] fix: ignore scrap items while distribute additional cost --- .../controllers/subcontracting_controller.py | 19 ++++++++++++------- .../subcontracting_receipt.js | 7 ++++++- .../subcontracting_receipt.py | 7 ++----- .../subcontracting_receipt_item.json | 8 ++++++-- 4 files changed, 26 insertions(+), 15 deletions(-) diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py index 837969728b..913c80b26a 100644 --- a/erpnext/controllers/subcontracting_controller.py +++ b/erpnext/controllers/subcontracting_controller.py @@ -895,19 +895,24 @@ class SubcontractingController(StockController): if self.total_additional_costs: if self.distribute_additional_costs_based_on == "Amount": - total_amt = sum(flt(item.amount) for item in self.get("items")) + total_amt = sum( + flt(item.amount) for item in self.get("items") if not item.get("is_scrap_item") + ) for item in self.items: - item.additional_cost_per_qty = ( - (item.amount * self.total_additional_costs) / total_amt - ) / item.qty + if not item.get("is_scrap_item"): + item.additional_cost_per_qty = ( + (item.amount * self.total_additional_costs) / total_amt + ) / item.qty else: - total_qty = sum(flt(item.qty) for item in self.get("items")) + total_qty = sum(flt(item.qty) for item in self.get("items") if not item.get("is_scrap_item")) additional_cost_per_qty = self.total_additional_costs / total_qty for item in self.items: - item.additional_cost_per_qty = additional_cost_per_qty + if not item.get("is_scrap_item"): + item.additional_cost_per_qty = additional_cost_per_qty else: for item in self.items: - item.additional_cost_per_qty = 0 + if not item.get("is_scrap_item"): + item.additional_cost_per_qty = 0 @frappe.whitelist() def get_current_stock(self): diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js index 1a20f0ccf5..8edbd4af08 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js @@ -201,12 +201,17 @@ frappe.ui.form.on('Subcontracting Receipt', { frappe.ui.form.on('Landed Cost Taxes and Charges', { amount: function (frm, cdt, cdn) { + set_missing_values(frm); frm.events.set_base_amount(frm, cdt, cdn); }, expense_account: function (frm, cdt, cdn) { frm.events.set_account_currency(frm, cdt, cdn); - } + }, + + additional_costs_remove: function(frm) { + set_missing_values(frm); + } }); frappe.ui.form.on('Subcontracting Receipt Item', { diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py index 09db66f97a..b28292f0a4 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -169,12 +169,12 @@ class SubcontractingReceipt(SubcontractingController): "item_name": scrap_item.item_name, "qty": qty, "stock_uom": scrap_item.stock_uom, - "recalculate_rate": 1, + "recalculate_rate": 0, "rate": scrap_item.rate, "rm_cost_per_qty": 0, "service_cost_per_qty": 0, "additional_cost_per_qty": 0, - "scrap_cost_per_qty": scrap_item.rate, + "scrap_cost_per_qty": 0, "amount": qty * scrap_item.rate, "warehouse": self.set_warehouse, "rejected_warehouse": self.rejected_warehouse, @@ -244,9 +244,6 @@ class SubcontractingReceipt(SubcontractingController): scrap_cost_map = {} for item in self.get("items") or []: if item.is_scrap_item: - if item.recalculate_rate: - item.rate = flt(item.scrap_cost_per_qty) + flt(item.additional_cost_per_qty) - item.amount = flt(item.qty) * flt(item.rate) if item.reference_name in scrap_cost_map: diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json index b333627b4a..c036390ba3 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json +++ b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json @@ -237,6 +237,7 @@ }, { "default": "0", + "depends_on": "eval: !doc.is_scrap_item", "fieldname": "additional_cost_per_qty", "fieldtype": "Currency", "label": "Additional Cost Per Qty", @@ -476,9 +477,11 @@ }, { "default": "1", + "depends_on": "eval: !doc.is_scrap_item", "fieldname": "recalculate_rate", "fieldtype": "Check", - "label": "Recalculate Rate" + "label": "Recalculate Rate", + "read_only_depends_on": "eval: doc.is_scrap_item" }, { "fieldname": "serial_and_batch_bundle", @@ -508,6 +511,7 @@ }, { "default": "0", + "depends_on": "eval: !doc.is_scrap_item", "fieldname": "scrap_cost_per_qty", "fieldtype": "Float", "label": "Scrap Cost Per Qty", @@ -527,7 +531,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2023-08-25 15:42:36.923833", + "modified": "2023-08-25 20:09:03.069417", "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Receipt Item", From c8d7433e30e91e6b99c75ebe791827989906d9f8 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Fri, 25 Aug 2023 20:23:39 +0530 Subject: [PATCH 14/20] refactor(minor): `subcontracting_receipt.js` --- .../subcontracting_receipt.js | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js index 8edbd4af08..a890437752 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js @@ -52,14 +52,14 @@ frappe.ui.form.on('Subcontracting Receipt', { } })); - frm.set_query('expense_account', 'items', function () { + frm.set_query('expense_account', 'items', () => { return { query: 'erpnext.controllers.queries.get_expense_account', filters: { 'company': frm.doc.company } }; }); - frm.set_query('batch_no', 'items', function(doc, cdt, cdn) { + frm.set_query('batch_no', 'items', (doc, cdt, cdn) => { var row = locals[cdt][cdn]; return { filters: { @@ -68,7 +68,7 @@ frappe.ui.form.on('Subcontracting Receipt', { } }); - frm.set_query('batch_no', 'supplied_items', function(doc, cdt, cdn) { + frm.set_query('batch_no', 'supplied_items', (doc, cdt, cdn) => { var row = locals[cdt][cdn]; return { filters: { @@ -101,7 +101,7 @@ frappe.ui.form.on('Subcontracting Receipt', { let batch_no_field = frm.get_docfield('items', 'batch_no'); if (batch_no_field) { - batch_no_field.get_route_options_for_new_doc = function(row) { + batch_no_field.get_route_options_for_new_doc = (row) => { return { 'item': row.doc.item_code } @@ -111,7 +111,7 @@ frappe.ui.form.on('Subcontracting Receipt', { refresh: (frm) => { if (frm.doc.docstatus > 0) { - frm.add_custom_button(__('Stock Ledger'), function () { + frm.add_custom_button(__('Stock Ledger'), () => { frappe.route_options = { voucher_no: frm.doc.name, from_date: frm.doc.posting_date, @@ -122,7 +122,7 @@ frappe.ui.form.on('Subcontracting Receipt', { frappe.set_route('query-report', 'Stock Ledger'); }, __('View')); - frm.add_custom_button(__('Accounting Ledger'), function () { + frm.add_custom_button(__('Accounting Ledger'), () => { frappe.route_options = { voucher_no: frm.doc.name, from_date: frm.doc.posting_date, @@ -136,7 +136,7 @@ frappe.ui.form.on('Subcontracting Receipt', { } if (!frm.doc.is_return && frm.doc.docstatus == 1 && frm.doc.per_returned < 100) { - frm.add_custom_button(__('Subcontract Return'), function () { + frm.add_custom_button(__('Subcontract Return'), () => { frappe.model.open_mapped_doc({ method: 'erpnext.subcontracting.doctype.subcontracting_receipt.subcontracting_receipt.make_subcontract_return', frm: frm @@ -146,7 +146,7 @@ frappe.ui.form.on('Subcontracting Receipt', { } if (frm.doc.docstatus == 0) { - frm.add_custom_button(__('Subcontracting Order'), function () { + frm.add_custom_button(__('Subcontracting Order'), () => { if (!frm.doc.supplier) { frappe.throw({ title: __('Mandatory'), @@ -200,18 +200,18 @@ frappe.ui.form.on('Subcontracting Receipt', { }); frappe.ui.form.on('Landed Cost Taxes and Charges', { - amount: function (frm, cdt, cdn) { + amount: (frm, cdt, cdn) => { set_missing_values(frm); frm.events.set_base_amount(frm, cdt, cdn); }, - expense_account: function (frm, cdt, cdn) { + expense_account: (frm, cdt, cdn) => { frm.events.set_account_currency(frm, cdt, cdn); }, - additional_costs_remove: function(frm) { - set_missing_values(frm); - } + additional_costs_remove: (frm) => { + set_missing_values(frm); + } }); frappe.ui.form.on('Subcontracting Receipt Item', { @@ -233,9 +233,9 @@ frappe.ui.form.on('Subcontracting Receipt Item', { } }, - items_remove: function(frm) { - set_missing_values(frm); - } + items_remove: (frm) => { + set_missing_values(frm); + } }); frappe.ui.form.on('Subcontracting Receipt Supplied Item', { From 08bc33689c8d8125f1c80bb621e46a5a83bfd45f Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Sat, 26 Aug 2023 10:57:49 +0530 Subject: [PATCH 15/20] fix(ux): make `Get Scrap Items` button hidden for submitted and cancelled doc --- .../subcontracting_receipt/subcontracting_receipt.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json index 95fe087d9a..8be1c1ba97 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json @@ -286,7 +286,7 @@ "reqd": 1 }, { - "depends_on": "supplied_items", + "depends_on": "eval: (!doc.__islocal && doc.docstatus == 0 && doc.supplied_items)", "fieldname": "get_current_stock", "fieldtype": "Button", "label": "Get Current Stock", @@ -629,6 +629,7 @@ "print_hide": 1 }, { + "depends_on": "eval: (!doc.__islocal && doc.docstatus == 0)", "fieldname": "get_scrap_items", "fieldtype": "Button", "label": "Get Scrap Items", @@ -638,7 +639,7 @@ "in_create": 1, "is_submittable": 1, "links": [], - "modified": "2023-08-25 10:02:11.546559", + "modified": "2023-08-26 10:52:04.050829", "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Receipt", From ed4f498704486664d9810193c5bc01bbfcf4b23e Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Sat, 26 Aug 2023 11:38:45 +0530 Subject: [PATCH 16/20] feat: allow scrap items when backflush based on is Material Transfer --- .../subcontracting_receipt.py | 62 +++++++++---------- 1 file changed, 29 insertions(+), 33 deletions(-) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py index b28292f0a4..4ab0c666c7 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -149,41 +149,37 @@ class SubcontractingReceipt(SubcontractingController): @frappe.whitelist() def get_scrap_items(self, recalculate_rate=False): - if ( - frappe.db.get_single_value("Buying Settings", "backflush_raw_materials_of_subcontract_based_on") - == "BOM" - ): - self.remove_scrap_items() + self.remove_scrap_items() - for item in list(self.items): - if item.bom: - bom = frappe.get_doc("BOM", item.bom) - for scrap_item in bom.scrap_items: - qty = flt(item.qty) * (flt(scrap_item.stock_qty) / flt(bom.quantity)) - self.append( - "items", - { - "is_scrap_item": 1, - "reference_name": item.name, - "item_code": scrap_item.item_code, - "item_name": scrap_item.item_name, - "qty": qty, - "stock_uom": scrap_item.stock_uom, - "recalculate_rate": 0, - "rate": scrap_item.rate, - "rm_cost_per_qty": 0, - "service_cost_per_qty": 0, - "additional_cost_per_qty": 0, - "scrap_cost_per_qty": 0, - "amount": qty * scrap_item.rate, - "warehouse": self.set_warehouse, - "rejected_warehouse": self.rejected_warehouse, - }, - ) + for item in list(self.items): + if item.bom: + bom = frappe.get_doc("BOM", item.bom) + for scrap_item in bom.scrap_items: + qty = flt(item.qty) * (flt(scrap_item.stock_qty) / flt(bom.quantity)) + self.append( + "items", + { + "is_scrap_item": 1, + "reference_name": item.name, + "item_code": scrap_item.item_code, + "item_name": scrap_item.item_name, + "qty": qty, + "stock_uom": scrap_item.stock_uom, + "recalculate_rate": 0, + "rate": scrap_item.rate, + "rm_cost_per_qty": 0, + "service_cost_per_qty": 0, + "additional_cost_per_qty": 0, + "scrap_cost_per_qty": 0, + "amount": qty * scrap_item.rate, + "warehouse": self.set_warehouse, + "rejected_warehouse": self.rejected_warehouse, + }, + ) - if recalculate_rate: - self.calculate_additional_costs() - self.calculate_items_qty_and_amount() + if recalculate_rate: + self.calculate_additional_costs() + self.calculate_items_qty_and_amount() def remove_scrap_items(self, recalculate_rate=False): for item in list(self.items): From afd7d59c733ff581ba4b7eb1aaf252abc8792984 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Sat, 26 Aug 2023 13:10:47 +0530 Subject: [PATCH 17/20] fix: `AttributeError` while saving subcontracting order --- erpnext/controllers/subcontracting_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py index 913c80b26a..d4270a76d4 100644 --- a/erpnext/controllers/subcontracting_controller.py +++ b/erpnext/controllers/subcontracting_controller.py @@ -97,7 +97,7 @@ class SubcontractingController(StockController): if not is_stock_item: frappe.throw(_("Row {0}: Item {1} must be a stock item.").format(item.idx, item.item_name)) - if not item.is_scrap_item: + if not item.get("is_scrap_item"): if not is_sub_contracted_item: frappe.throw( _("Row {0}: Item {1} must be a subcontracted item.").format(item.idx, item.item_name) From ffcbcd7397626c13e9d72120e73e325e0cba8c06 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Sun, 27 Aug 2023 23:35:59 +0530 Subject: [PATCH 18/20] test: add test case for SCR scrap items --- .../test_subcontracting_receipt.py | 73 ++++++++++++++++++- 1 file changed, 71 insertions(+), 2 deletions(-) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py index a170527e2d..0c163916b3 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py @@ -23,6 +23,7 @@ from erpnext.controllers.tests.test_subcontracting_controller import ( make_subcontracted_items, set_backflush_based_on, ) +from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import get_gl_entries from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry @@ -507,8 +508,6 @@ class TestSubcontractingReceipt(FrappeTestCase): self.assertEqual(scr.supplied_items[0].rate, sr.items[0].valuation_rate) def test_subcontracting_receipt_raw_material_rate(self): - from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom - # Step - 1: Set Backflush Based On as "BOM" set_backflush_based_on("BOM") @@ -625,6 +624,76 @@ class TestSubcontractingReceipt(FrappeTestCase): # ValidationError should not be raised as `Inspection Required before Purchase` is disabled scr2.submit() + def test_scrap_items_for_subcontracting_receipt(self): + set_backflush_based_on("BOM") + + fg_item = "Subcontracted Item SA1" + + # Create Raw Materials + raw_materials = [ + make_item(properties={"is_stock_item": 1, "valuation_rate": 100}).name, + make_item(properties={"is_stock_item": 1, "valuation_rate": 200}).name, + ] + + # Create Scrap Items + scrap_item_1 = make_item(properties={"is_stock_item": 1, "valuation_rate": 10}).name + scrap_item_2 = make_item(properties={"is_stock_item": 1, "valuation_rate": 20}).name + scrap_items = [scrap_item_1, scrap_item_2] + + service_items = [ + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 1", + "qty": 10, + "rate": 100, + "fg_item": fg_item, + "fg_item_qty": 10, + }, + ] + + # Create BOM with Scrap Items + bom = make_bom( + item=fg_item, raw_materials=raw_materials, rate=100, currency="INR", do_not_submit=True + ) + for idx, item in enumerate(bom.items): + item.qty = 1 * (idx + 1) + for idx, item in enumerate(scrap_items): + bom.append( + "scrap_items", + { + "item_code": item, + "stock_qty": 1 * (idx + 1), + "rate": 10 * (idx + 1), + }, + ) + bom.save() + bom.submit() + + # Create PO and SCO + sco = get_subcontracting_order(service_items=service_items) + + # Inward Raw Materials + rm_items = get_rm_items(sco.supplied_items) + itemwise_details = make_stock_in_entry(rm_items=rm_items) + + # Transfer RM's to Subcontractor + make_stock_transfer_entry( + sco_no=sco.name, + rm_items=rm_items, + itemwise_details=copy.deepcopy(itemwise_details), + ) + + # Create Subcontracting Receipt + scr = make_subcontracting_receipt(sco.name) + scr.save() + + # Test - 1: Scrap Items should be fetched from BOM in items table with `is_scrap_item` = 1 + scr_scrap_items = set([item.item_code for item in scr.items if item.is_scrap_item]) + self.assertEqual(len(scr.items), 3) # 1 FG Item + 2 Scrap Items + self.assertEqual(scr_scrap_items, set(scrap_items)) + + scr.submit() + def make_return_subcontracting_receipt(**args): args = frappe._dict(args) From 592c7b5ff123f99376cfc87915e9773da7dc9b7c Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Mon, 28 Aug 2023 00:08:05 +0530 Subject: [PATCH 19/20] fix: test cases --- .../doctype/subcontracting_order/subcontracting_order.py | 5 ++++- .../subcontracting_order/test_subcontracting_order.py | 7 +++++++ .../subcontracting_receipt/subcontracting_receipt.py | 2 +- .../subcontracting_receipt/test_subcontracting_receipt.py | 1 + 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py index 0b14d4d9f5..b7b344584c 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py +++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py @@ -203,7 +203,10 @@ def get_mapped_subcontracting_receipt(source_name, target_doc=None): { "Subcontracting Order": { "doctype": "Subcontracting Receipt", - "field_map": {"supplier_warehouse": "supplier_warehouse"}, + "field_map": { + "supplier_warehouse": "supplier_warehouse", + "set_warehouse": "set_warehouse", + }, "validation": { "docstatus": ["=", 1], }, diff --git a/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py index 6a2983faaa..22fdc13cc1 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py +++ b/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py @@ -591,6 +591,13 @@ def create_subcontracting_order(**args): for idx, val in enumerate(sco.items): val.warehouse = warehouses[idx] + warehouses = set() + for item in sco.items: + warehouses.add(item.warehouse) + + if len(warehouses) == 1: + sco.set_warehouse = list(warehouses)[0] + if not args.do_not_save: sco.insert() if not args.do_not_submit: diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py index c601ddb999..c0a567d7a6 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -55,7 +55,7 @@ class SubcontractingReceipt(SubcontractingController): super(SubcontractingReceipt, self).validate() - if self.is_new() and self.get("_action") == "save": + if self.is_new() and self.get("_action") == "save" and not frappe.flags.in_test: self.get_scrap_items() self.set_missing_values() diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py index 0c163916b3..1828f6960f 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py @@ -686,6 +686,7 @@ class TestSubcontractingReceipt(FrappeTestCase): # Create Subcontracting Receipt scr = make_subcontracting_receipt(sco.name) scr.save() + scr.get_scrap_items() # Test - 1: Scrap Items should be fetched from BOM in items table with `is_scrap_item` = 1 scr_scrap_items = set([item.item_code for item in scr.items if item.is_scrap_item]) From 9d330a13edb7728e93327a10f28f73f1ae061bad Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Mon, 28 Aug 2023 16:09:05 +0530 Subject: [PATCH 20/20] fix: get `Valuation Rate` instead of BOM rate --- .../subcontracting_receipt.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py index c0a567d7a6..8a12e3bcd0 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -8,6 +8,7 @@ from frappe.utils import cint, flt, getdate, nowdate import erpnext from erpnext.accounts.utils import get_account_currency from erpnext.controllers.subcontracting_controller import SubcontractingController +from erpnext.stock.stock_ledger import get_valuation_rate class SubcontractingReceipt(SubcontractingController): @@ -159,6 +160,17 @@ class SubcontractingReceipt(SubcontractingController): bom = frappe.get_doc("BOM", item.bom) for scrap_item in bom.scrap_items: qty = flt(item.qty) * (flt(scrap_item.stock_qty) / flt(bom.quantity)) + rate = ( + get_valuation_rate( + scrap_item.item_code, + self.set_warehouse, + self.doctype, + self.name, + currency=erpnext.get_company_currency(self.company), + company=self.company, + ) + or scrap_item.rate + ) self.append( "items", { @@ -169,12 +181,12 @@ class SubcontractingReceipt(SubcontractingController): "qty": qty, "stock_uom": scrap_item.stock_uom, "recalculate_rate": 0, - "rate": scrap_item.rate, + "rate": rate, "rm_cost_per_qty": 0, "service_cost_per_qty": 0, "additional_cost_per_qty": 0, "scrap_cost_per_qty": 0, - "amount": qty * scrap_item.rate, + "amount": qty * rate, "warehouse": self.set_warehouse, "rejected_warehouse": self.rejected_warehouse, },