From 36d09ab2cb49910ed1bbc0a47082be9b11f57ccb Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Mon, 28 Oct 2024 10:42:07 +0530 Subject: [PATCH] =?UTF-8?q?fix:=20incorrect=20value=20of=20available=5Fqty?= =?UTF-8?q?=5Ffor=5Fconsumption=20in=20subcontracti=E2=80=A6=20(#43836)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix: incorrect value of available_qty_for_consumption in subcontracting receipt (cherry picked from commit ad6ce09b865786d99daca38194bdeeea53aa11e6) # Conflicts: # erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py --- .../subcontracting_receipt.py | 251 ++++++++++++++++++ 1 file changed, 251 insertions(+) diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py index 595620b745b..bda21a7417d 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -302,6 +302,257 @@ class SubcontractingReceipt(SubcontractingController): if not item.expense_account: item.expense_account = expense_account +<<<<<<< HEAD +======= + def reset_supplied_items(self): + if ( + frappe.db.get_single_value("Buying Settings", "backflush_raw_materials_of_subcontract_based_on") + == "BOM" + and self.supplied_items + ): + if not any( + item.serial_and_batch_bundle or item.batch_no or item.serial_no + for item in self.supplied_items + ): + self.supplied_items = [] + else: + self.update_rate_for_supplied_items() + + @frappe.whitelist() + def get_scrap_items(self, recalculate_rate=False): + 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)) + 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", + { + "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, + "rate": rate, + "rm_cost_per_qty": 0, + "service_cost_per_qty": 0, + "additional_cost_per_qty": 0, + "scrap_cost_per_qty": 0, + "amount": qty * rate, + "warehouse": self.set_warehouse, + "rejected_warehouse": self.rejected_warehouse, + }, + ) + + 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): + 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.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 = {} + + 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: + if supplied_item.rm_item_code not in supplied_items_details[item.name]: + supplied_items_details[item.name][supplied_item.rm_item_code] = 0.0 + + 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_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: + rm_cost_map[item.reference_name] = item.amount + + scrap_cost_map = {} + for item in self.get("items") or []: + if item.is_scrap_item: + 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 + + total_qty = total_amount = 0 + for item in self.get("items") or []: + 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) + else: + item.scrap_cost_per_qty = 0 + + 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) + + total_qty += flt(item.qty) + total_amount += item.amount + else: + 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: + 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): + if ( + frappe.db.get_single_value("Buying Settings", "backflush_raw_materials_of_subcontract_based_on") + == "BOM" + ): + return + + 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", + }, + ] + ) + +>>>>>>> ad6ce09b86 (fix: incorrect value of available_qty_for_consumption in subcontracti… (#43836)) def update_status(self, status=None, update_modified=False): if not status: if self.docstatus == 0: