mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-31 18:59:08 +00:00
@@ -25,7 +25,7 @@
|
||||
"raw_materials_received_section",
|
||||
"received_items",
|
||||
"scrap_items_generated_section",
|
||||
"scrap_items",
|
||||
"secondary_items",
|
||||
"service_items_section",
|
||||
"service_items",
|
||||
"tab_other_info",
|
||||
@@ -252,17 +252,10 @@
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "scrap_items",
|
||||
"depends_on": "secondary_items",
|
||||
"fieldname": "scrap_items_generated_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Scrap Items Generated"
|
||||
},
|
||||
{
|
||||
"fieldname": "scrap_items",
|
||||
"fieldtype": "Table",
|
||||
"label": "Scrap Items",
|
||||
"no_copy": 1,
|
||||
"options": "Subcontracting Inward Order Scrap Item"
|
||||
"label": "Secondary Items Generated"
|
||||
},
|
||||
{
|
||||
"fieldname": "per_returned",
|
||||
@@ -300,13 +293,20 @@
|
||||
"label": "Customer Currency",
|
||||
"options": "Currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "secondary_items",
|
||||
"fieldtype": "Table",
|
||||
"label": "Secondary Items",
|
||||
"no_copy": 1,
|
||||
"options": "Subcontracting Inward Order Secondary Item"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-12-09 15:52:55.781346",
|
||||
"modified": "2026-02-26 17:16:21.697846",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Subcontracting",
|
||||
"name": "Subcontracting Inward Order",
|
||||
|
||||
@@ -25,8 +25,8 @@ class SubcontractingInwardOrder(SubcontractingController):
|
||||
from erpnext.subcontracting.doctype.subcontracting_inward_order_received_item.subcontracting_inward_order_received_item import (
|
||||
SubcontractingInwardOrderReceivedItem,
|
||||
)
|
||||
from erpnext.subcontracting.doctype.subcontracting_inward_order_scrap_item.subcontracting_inward_order_scrap_item import (
|
||||
SubcontractingInwardOrderScrapItem,
|
||||
from erpnext.subcontracting.doctype.subcontracting_inward_order_secondary_item.subcontracting_inward_order_secondary_item import (
|
||||
SubcontractingInwardOrderSecondaryItem,
|
||||
)
|
||||
from erpnext.subcontracting.doctype.subcontracting_inward_order_service_item.subcontracting_inward_order_service_item import (
|
||||
SubcontractingInwardOrderServiceItem,
|
||||
@@ -48,7 +48,7 @@ class SubcontractingInwardOrder(SubcontractingController):
|
||||
per_returned: DF.Percent
|
||||
received_items: DF.Table[SubcontractingInwardOrderReceivedItem]
|
||||
sales_order: DF.Link
|
||||
scrap_items: DF.Table[SubcontractingInwardOrderScrapItem]
|
||||
secondary_items: DF.Table[SubcontractingInwardOrderSecondaryItem]
|
||||
service_items: DF.Table[SubcontractingInwardOrderServiceItem]
|
||||
set_delivery_warehouse: DF.Link | None
|
||||
status: DF.Literal[
|
||||
@@ -474,23 +474,25 @@ class SubcontractingInwardOrder(SubcontractingController):
|
||||
stock_entry.add_to_stock_entry_detail(items_dict)
|
||||
|
||||
if (
|
||||
frappe.get_single_value("Selling Settings", "deliver_scrap_items")
|
||||
and self.scrap_items
|
||||
frappe.get_single_value("Selling Settings", "deliver_secondary_items")
|
||||
and self.secondary_items
|
||||
and scio_details
|
||||
):
|
||||
scrap_items = [
|
||||
scrap_item for scrap_item in self.scrap_items if scrap_item.reference_name in scio_details
|
||||
secondary_items = [
|
||||
secondary_item
|
||||
for secondary_item in self.secondary_items
|
||||
if secondary_item.reference_name in scio_details
|
||||
]
|
||||
for scrap_item in scrap_items:
|
||||
qty = scrap_item.produced_qty - scrap_item.delivered_qty
|
||||
for secondary_item in secondary_items:
|
||||
qty = secondary_item.produced_qty - secondary_item.delivered_qty
|
||||
if qty > 0:
|
||||
items_dict = {
|
||||
scrap_item.item_code: {
|
||||
"qty": scrap_item.produced_qty - scrap_item.delivered_qty,
|
||||
"from_warehouse": scrap_item.warehouse,
|
||||
"stock_uom": scrap_item.stock_uom,
|
||||
"scio_detail": scrap_item.name,
|
||||
"is_scrap_item": 1,
|
||||
secondary_item.item_code: {
|
||||
"qty": secondary_item.produced_qty - secondary_item.delivered_qty,
|
||||
"from_warehouse": secondary_item.warehouse,
|
||||
"stock_uom": secondary_item.stock_uom,
|
||||
"scio_detail": secondary_item.name,
|
||||
"type": secondary_item.type,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -323,10 +323,12 @@ class IntegrationTestSubcontractingInwardOrder(ERPNextTestSuite):
|
||||
delivery.items[0].qty = 6
|
||||
self.assertRaises(frappe.ValidationError, delivery.submit)
|
||||
|
||||
@ERPNextTestSuite.change_settings("Selling Settings", {"deliver_scrap_items": 1})
|
||||
@ERPNextTestSuite.change_settings("Selling Settings", {"deliver_secondary_items": 1})
|
||||
def test_secondary_items_delivery(self):
|
||||
new_bom = frappe.copy_doc(frappe.get_doc("BOM", "BOM-Basic FG Item-001"))
|
||||
new_bom.scrap_items.append(frappe.new_doc("BOM Scrap Item", item_code="Basic RM 2", qty=1))
|
||||
new_bom.secondary_items.append(
|
||||
frappe.new_doc("BOM Secondary Item", item_code="Basic RM 2", qty=1, type="Scrap")
|
||||
)
|
||||
new_bom.submit()
|
||||
sc_bom = frappe.get_doc("Subcontracting BOM", "SB-0001")
|
||||
sc_bom.finished_good_bom = new_bom.name
|
||||
@@ -343,12 +345,12 @@ class IntegrationTestSubcontractingInwardOrder(ERPNextTestSuite):
|
||||
frappe.new_doc("Stock Entry").update(make_stock_entry_from_wo(wo.name, "Manufacture")).submit()
|
||||
|
||||
scio.reload()
|
||||
self.assertEqual(scio.scrap_items[0].item_code, "Basic RM 2")
|
||||
self.assertEqual(scio.secondary_items[0].item_code, "Basic RM 2")
|
||||
|
||||
delivery = frappe.new_doc("Stock Entry").update(scio.make_subcontracting_delivery())
|
||||
self.assertEqual(delivery.items[-1].item_code, "Basic RM 2")
|
||||
|
||||
frappe.db.set_single_value("Selling Settings", "deliver_scrap_items", 0)
|
||||
frappe.db.set_single_value("Selling Settings", "deliver_secondary_items", 0)
|
||||
delivery = frappe.new_doc("Stock Entry").update(scio.make_subcontracting_delivery())
|
||||
self.assertNotEqual(delivery.items[-1].item_code, "Basic RM 2")
|
||||
|
||||
|
||||
@@ -6,13 +6,15 @@
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"column_break_rptg",
|
||||
"type",
|
||||
"reference_name",
|
||||
"column_break_jkzt",
|
||||
"item_code",
|
||||
"fg_item_code",
|
||||
"column_break_hoxe",
|
||||
"stock_uom",
|
||||
"warehouse",
|
||||
"column_break_rptg",
|
||||
"reference_name",
|
||||
"section_break_gqk9",
|
||||
"produced_qty",
|
||||
"column_break_n4xc",
|
||||
@@ -93,16 +95,29 @@
|
||||
{
|
||||
"fieldname": "column_break_n4xc",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "type",
|
||||
"fieldtype": "Select",
|
||||
"label": "Type",
|
||||
"no_copy": 1,
|
||||
"options": "Co-Product\nBy-Product\nScrap\nAdditional Finished Good",
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_jkzt",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-10-14 10:28:30.192350",
|
||||
"modified": "2026-02-27 15:15:40.009957",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Subcontracting",
|
||||
"name": "Subcontracting Inward Order Scrap Item",
|
||||
"name": "Subcontracting Inward Order Secondary Item",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"row_format": "Dynamic",
|
||||
@@ -5,7 +5,7 @@
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class SubcontractingInwardOrderScrapItem(Document):
|
||||
class SubcontractingInwardOrderSecondaryItem(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
@@ -23,6 +23,7 @@ class SubcontractingInwardOrderScrapItem(Document):
|
||||
produced_qty: DF.Float
|
||||
reference_name: DF.Data
|
||||
stock_uom: DF.Link
|
||||
type: DF.Literal["Co-Product", "By-Product", "Scrap", "Additional Finished Good"]
|
||||
warehouse: DF.Link
|
||||
# end: auto-generated types
|
||||
|
||||
@@ -439,6 +439,13 @@ def get_mapped_subcontracting_receipt(source_name, target_doc=None, items=None):
|
||||
target.purchase_order = source_parent.purchase_order
|
||||
target.purchase_order_item = source.purchase_order_item
|
||||
target.qty = items.get(source.name) or (flt(source.qty) - flt(source.received_qty))
|
||||
target.received_qty = target.qty
|
||||
if process_loss_per := frappe.get_value("BOM", source.bom, "process_loss_percentage"):
|
||||
target.process_loss_qty = flt(
|
||||
target.qty * (process_loss_per / 100), target.precision("process_loss_qty")
|
||||
)
|
||||
target.qty -= target.process_loss_qty
|
||||
|
||||
target.amount = (flt(source.qty) - flt(source.received_qty)) * flt(source.rate)
|
||||
|
||||
items = {item["name"]: item["qty"] for item in items} if items else {}
|
||||
|
||||
@@ -425,7 +425,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-11-03 12:29:45.156101",
|
||||
"modified": "2026-02-27 23:03:36.436504",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Subcontracting",
|
||||
"name": "Subcontracting Order Item",
|
||||
|
||||
@@ -174,6 +174,7 @@ frappe.ui.form.on("Subcontracting Receipt", {
|
||||
|
||||
frm.trigger("setup_quality_inspection");
|
||||
frm.trigger("set_route_options_for_new_doc");
|
||||
frm.set_df_property("items", "cannot_add_rows", true);
|
||||
},
|
||||
|
||||
set_warehouse: (frm) => {
|
||||
@@ -184,15 +185,15 @@ frappe.ui.form.on("Subcontracting Receipt", {
|
||||
set_warehouse_in_children(frm.doc.items, "rejected_warehouse", frm.doc.rejected_warehouse);
|
||||
},
|
||||
|
||||
get_scrap_items: (frm) => {
|
||||
get_secondary_items: (frm) => {
|
||||
frappe.call({
|
||||
doc: frm.doc,
|
||||
method: "get_scrap_items",
|
||||
method: "get_secondary_items",
|
||||
args: {
|
||||
recalculate_rate: true,
|
||||
},
|
||||
freeze: true,
|
||||
freeze_message: __("Getting Scrap Items"),
|
||||
freeze_message: __("Getting Secondary Items"),
|
||||
callback: (r) => {
|
||||
if (!r.exc) {
|
||||
frm.refresh();
|
||||
@@ -422,11 +423,19 @@ frappe.ui.form.on("Subcontracting Receipt Item", {
|
||||
set_missing_values(frm);
|
||||
},
|
||||
|
||||
rejected_qty(frm) {
|
||||
set_missing_values(frm);
|
||||
},
|
||||
|
||||
process_loss_qty(frm) {
|
||||
set_missing_values(frm);
|
||||
},
|
||||
|
||||
rate(frm) {
|
||||
set_missing_values(frm);
|
||||
},
|
||||
|
||||
items_delete: (frm) => {
|
||||
items_delete(frm) {
|
||||
set_missing_values(frm);
|
||||
},
|
||||
|
||||
|
||||
@@ -29,8 +29,8 @@
|
||||
"col_break_warehouse",
|
||||
"supplier_warehouse",
|
||||
"items_section",
|
||||
"get_scrap_items",
|
||||
"items",
|
||||
"get_secondary_items",
|
||||
"section_break0",
|
||||
"total_qty",
|
||||
"column_break_27",
|
||||
@@ -631,13 +631,6 @@
|
||||
"label": "Edit Posting Date and Time",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: (!doc.__islocal && doc.docstatus == 0)",
|
||||
"fieldname": "get_scrap_items",
|
||||
"fieldtype": "Button",
|
||||
"label": "Get Scrap Items",
|
||||
"options": "get_scrap_items"
|
||||
},
|
||||
{
|
||||
"fieldname": "supplier_delivery_note",
|
||||
"fieldtype": "Data",
|
||||
@@ -674,12 +667,19 @@
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Connections",
|
||||
"show_dashboard": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: (!doc.__islocal && doc.docstatus == 0)",
|
||||
"fieldname": "get_secondary_items",
|
||||
"fieldtype": "Button",
|
||||
"label": "Get Secondary Items",
|
||||
"options": "get_secondary_items"
|
||||
}
|
||||
],
|
||||
"in_create": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-10-08 21:43:27.065640",
|
||||
"modified": "2026-02-27 17:59:44.107193",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Subcontracting",
|
||||
"name": "Subcontracting Receipt",
|
||||
|
||||
@@ -144,12 +144,12 @@ class SubcontractingReceipt(SubcontractingController):
|
||||
super().validate()
|
||||
|
||||
if self.is_new() and self.get("_action") == "save" and not frappe.in_test:
|
||||
self.get_scrap_items()
|
||||
self.get_secondary_items()
|
||||
|
||||
self.set_missing_values()
|
||||
|
||||
if self.get("_action") == "submit":
|
||||
self.validate_scrap_items()
|
||||
self.validate_secondary_items()
|
||||
self.validate_accepted_warehouse()
|
||||
self.validate_rejected_warehouse()
|
||||
|
||||
@@ -343,39 +343,66 @@ class SubcontractingReceipt(SubcontractingController):
|
||||
self.update_rate_for_supplied_items()
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_scrap_items(self, recalculate_rate=False):
|
||||
self.remove_scrap_items()
|
||||
def get_secondary_items(self, recalculate_rate: bool | None = False):
|
||||
self.remove_secondary_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
|
||||
for secondary_item in bom.secondary_items:
|
||||
per_unit = secondary_item.stock_qty / bom.quantity
|
||||
received_qty = flt(item.received_qty * per_unit, item.precision("received_qty"))
|
||||
qty = flt(
|
||||
item.received_qty * (per_unit - (secondary_item.process_loss_qty / bom.quantity)),
|
||||
item.precision("qty"),
|
||||
)
|
||||
if not secondary_item.is_legacy:
|
||||
lcv_cost_per_qty = (
|
||||
flt(item.landed_cost_voucher_amount) / flt(item.qty) if flt(item.qty) else 0.0
|
||||
)
|
||||
fg_item_cost = (
|
||||
flt(item.rm_cost_per_qty)
|
||||
+ flt(item.secondary_items_cost_per_qty)
|
||||
+ flt(item.additional_cost_per_qty)
|
||||
+ flt(lcv_cost_per_qty)
|
||||
+ flt(item.service_cost_per_qty)
|
||||
) * flt(item.received_qty)
|
||||
rate = (
|
||||
(item.amount if self.is_new() else fg_item_cost)
|
||||
* (secondary_item.cost_allocation_per / 100)
|
||||
) / qty
|
||||
else:
|
||||
rate = (
|
||||
get_valuation_rate(
|
||||
secondary_item.item_code,
|
||||
self.set_warehouse,
|
||||
self.doctype,
|
||||
self.name,
|
||||
currency=erpnext.get_company_currency(self.company),
|
||||
company=self.company,
|
||||
)
|
||||
or secondary_item.rate
|
||||
)
|
||||
|
||||
self.append(
|
||||
"items",
|
||||
{
|
||||
"is_scrap_item": 1,
|
||||
"type": secondary_item.type,
|
||||
"is_legacy_scrap_item": secondary_item.is_legacy,
|
||||
"reference_name": item.name,
|
||||
"item_code": scrap_item.item_code,
|
||||
"item_name": scrap_item.item_name,
|
||||
"qty": qty,
|
||||
"stock_uom": scrap_item.stock_uom,
|
||||
"item_code": secondary_item.item_code,
|
||||
"item_name": secondary_item.item_name,
|
||||
"qty": received_qty
|
||||
if not secondary_item.is_legacy
|
||||
else flt(item.qty) * (flt(secondary_item.stock_qty) / flt(bom.quantity)),
|
||||
"received_qty": received_qty,
|
||||
"process_loss_qty": received_qty - qty,
|
||||
"stock_uom": secondary_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,
|
||||
"secondary_items_cost_per_qty": 0,
|
||||
"amount": qty * rate,
|
||||
"warehouse": self.set_warehouse,
|
||||
"rejected_warehouse": self.rejected_warehouse,
|
||||
@@ -386,15 +413,12 @@ class SubcontractingReceipt(SubcontractingController):
|
||||
self.calculate_additional_costs()
|
||||
self.calculate_items_qty_and_amount()
|
||||
|
||||
def remove_scrap_items(self, recalculate_rate=False):
|
||||
def remove_secondary_items(self):
|
||||
for item in list(self.items):
|
||||
if item.is_scrap_item:
|
||||
if item.type or item.is_legacy_scrap_item:
|
||||
self.remove(item)
|
||||
else:
|
||||
item.scrap_cost_per_qty = 0
|
||||
|
||||
if recalculate_rate:
|
||||
self.calculate_items_qty_and_amount()
|
||||
item.secondary_items_cost_per_qty = 0
|
||||
|
||||
@frappe.whitelist()
|
||||
def set_missing_values(self):
|
||||
@@ -449,30 +473,35 @@ class SubcontractingReceipt(SubcontractingController):
|
||||
else:
|
||||
rm_cost_map[item.reference_name] = item.amount
|
||||
|
||||
scrap_cost_map = {}
|
||||
secondary_items_cost_map = {}
|
||||
for item in self.get("items") or []:
|
||||
if item.is_scrap_item:
|
||||
item.amount = flt(item.qty) * flt(item.rate)
|
||||
if item.type or item.is_legacy_scrap_item:
|
||||
qty = (
|
||||
flt(item.qty)
|
||||
if item.is_legacy_scrap_item
|
||||
else (flt(item.received_qty) - flt(item.process_loss_qty))
|
||||
)
|
||||
item.amount = qty * flt(item.rate)
|
||||
|
||||
if item.reference_name in scrap_cost_map:
|
||||
scrap_cost_map[item.reference_name] += item.amount
|
||||
if item.reference_name in secondary_items_cost_map:
|
||||
secondary_items_cost_map[item.reference_name] += item.amount
|
||||
else:
|
||||
scrap_cost_map[item.reference_name] = item.amount
|
||||
secondary_items_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 not item.type and not item.is_legacy_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
|
||||
item.rm_cost_per_qty = item.rm_supp_cost / (item.received_qty or 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 secondary_items_cost_map:
|
||||
item.secondary_items_cost_per_qty = secondary_items_cost_map[item.name] / item.qty
|
||||
secondary_items_cost_map.pop(item.name)
|
||||
else:
|
||||
item.scrap_cost_per_qty = 0
|
||||
item.secondary_items_cost_per_qty = 0
|
||||
|
||||
lcv_cost_per_qty = 0.0
|
||||
if item.landed_cost_voucher_amount:
|
||||
@@ -483,36 +512,44 @@ class SubcontractingReceipt(SubcontractingController):
|
||||
+ flt(item.service_cost_per_qty)
|
||||
+ flt(item.additional_cost_per_qty)
|
||||
+ flt(lcv_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)
|
||||
if item.bom:
|
||||
item.received_qty = flt(item.qty) + flt(item.rejected_qty) + flt(item.process_loss_qty)
|
||||
item.amount = (
|
||||
flt(item.received_qty)
|
||||
* flt(item.rate)
|
||||
* (frappe.get_value("BOM", item.bom, "cost_allocation_per") / 100)
|
||||
)
|
||||
item.rate = item.amount / (item.qty or item.rejected_qty)
|
||||
else:
|
||||
item.qty = flt(item.received_qty) - flt(item.process_loss_qty)
|
||||
item.amount = flt(item.qty) * flt(item.rate)
|
||||
|
||||
total_qty += flt(item.qty)
|
||||
total_qty += flt(item.qty) + flt(item.rejected_qty)
|
||||
total_amount += item.amount
|
||||
else:
|
||||
self.total_qty = total_qty
|
||||
self.total = total_amount
|
||||
|
||||
def validate_scrap_items(self):
|
||||
def validate_secondary_items(self):
|
||||
for item in self.items:
|
||||
if item.is_scrap_item:
|
||||
if item.type or item.is_legacy_scrap_item:
|
||||
if not item.qty:
|
||||
frappe.throw(
|
||||
_("Row #{0}: Scrap Item Qty cannot be zero").format(item.idx),
|
||||
_("Row #{0}: Secondary 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(
|
||||
_("Row #{0}: Rejected Qty cannot be set for Secondary 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(
|
||||
_("Row #{0}: Finished Good reference is mandatory for Secondary Item {1}.").format(
|
||||
item.idx, frappe.bold(item.item_code)
|
||||
),
|
||||
)
|
||||
|
||||
@@ -597,6 +597,7 @@ class TestSubcontractingReceipt(ERPNextTestSuite):
|
||||
|
||||
scr.items[0].qty = 6 # Accepted Qty
|
||||
scr.items[0].rejected_qty = 4
|
||||
scr.set_missing_values()
|
||||
scr.save()
|
||||
|
||||
# consumed_qty should be (accepted_qty * qty_consumed_per_unit) = (6 * 1) = 6
|
||||
@@ -1154,7 +1155,7 @@ class TestSubcontractingReceipt(ERPNextTestSuite):
|
||||
# ValidationError should not be raised as `Inspection Required before Purchase` is disabled
|
||||
scr2.submit()
|
||||
|
||||
def test_scrap_items_for_subcontracting_receipt(self):
|
||||
def test_secondary_items_for_subcontracting_receipt(self):
|
||||
set_backflush_based_on("BOM")
|
||||
|
||||
fg_item = "Subcontracted Item SA1"
|
||||
@@ -1166,9 +1167,9 @@ class TestSubcontractingReceipt(ERPNextTestSuite):
|
||||
]
|
||||
|
||||
# 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]
|
||||
secondary_item_1 = make_item(properties={"is_stock_item": 1, "valuation_rate": 10}).name
|
||||
secondary_item_2 = make_item(properties={"is_stock_item": 1, "valuation_rate": 20}).name
|
||||
secondary_items = [secondary_item_1, secondary_item_2]
|
||||
|
||||
service_items = [
|
||||
{
|
||||
@@ -1187,13 +1188,14 @@ class TestSubcontractingReceipt(ERPNextTestSuite):
|
||||
)
|
||||
for idx, item in enumerate(bom.items):
|
||||
item.qty = 1 * (idx + 1)
|
||||
for idx, item in enumerate(scrap_items):
|
||||
for idx, item in enumerate(secondary_items):
|
||||
bom.append(
|
||||
"scrap_items",
|
||||
"secondary_items",
|
||||
{
|
||||
"item_code": item,
|
||||
"stock_qty": 1 * (idx + 1),
|
||||
"rate": 10 * (idx + 1),
|
||||
"is_legacy": 1,
|
||||
},
|
||||
)
|
||||
bom.save()
|
||||
@@ -1216,12 +1218,13 @@ class TestSubcontractingReceipt(ERPNextTestSuite):
|
||||
# Create Subcontracting Receipt
|
||||
scr = make_subcontracting_receipt(sco.name)
|
||||
scr.save()
|
||||
scr.get_scrap_items()
|
||||
scr.get_secondary_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])
|
||||
scr_secondary_items = set(
|
||||
[item.item_code for item in scr.items if item.type or item.is_legacy_scrap_item]
|
||||
)
|
||||
self.assertEqual(len(scr.items), 3) # 1 FG Item + 2 Scrap Items
|
||||
self.assertEqual(scr_scrap_items, set(scrap_items))
|
||||
self.assertEqual(scr_secondary_items, set(secondary_items))
|
||||
|
||||
scr.submit()
|
||||
|
||||
|
||||
@@ -8,9 +8,10 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"item_code",
|
||||
"is_legacy_scrap_item",
|
||||
"type",
|
||||
"column_break_2",
|
||||
"item_name",
|
||||
"is_scrap_item",
|
||||
"section_break_4",
|
||||
"description",
|
||||
"brand",
|
||||
@@ -22,6 +23,7 @@
|
||||
"qty",
|
||||
"rejected_qty",
|
||||
"returned_qty",
|
||||
"process_loss_qty",
|
||||
"col_break2",
|
||||
"stock_uom",
|
||||
"conversion_factor",
|
||||
@@ -33,7 +35,7 @@
|
||||
"rm_cost_per_qty",
|
||||
"service_cost_per_qty",
|
||||
"additional_cost_per_qty",
|
||||
"scrap_cost_per_qty",
|
||||
"secondary_items_cost_per_qty",
|
||||
"rm_supp_cost",
|
||||
"warehouse_and_reference",
|
||||
"warehouse",
|
||||
@@ -144,7 +146,7 @@
|
||||
"default": "0",
|
||||
"fieldname": "received_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Received Quantity",
|
||||
"label": "Qty (As per BOM)",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"print_width": "100px",
|
||||
@@ -157,22 +159,23 @@
|
||||
"fieldname": "qty",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Accepted Quantity",
|
||||
"label": "Accepted Qty",
|
||||
"no_copy": 1,
|
||||
"print_width": "100px",
|
||||
"read_only_depends_on": "eval:doc.type || doc.is_legacy_scrap_item",
|
||||
"width": "100px"
|
||||
},
|
||||
{
|
||||
"columns": 1,
|
||||
"depends_on": "eval: !parent.is_return",
|
||||
"depends_on": "eval:!parent.is_return && !doc.type && !doc.is_legacy_scrap_item",
|
||||
"fieldname": "rejected_qty",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Rejected Quantity",
|
||||
"label": "Rejected Qty",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"print_width": "100px",
|
||||
"read_only_depends_on": "eval: doc.is_scrap_item",
|
||||
"read_only_depends_on": "eval:doc.type || doc.is_legacy_scrap_item",
|
||||
"width": "100px"
|
||||
},
|
||||
{
|
||||
@@ -181,6 +184,7 @@
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.stock_uom",
|
||||
"fieldname": "stock_uom",
|
||||
"fieldtype": "Link",
|
||||
"label": "Stock UOM",
|
||||
@@ -230,7 +234,7 @@
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval: !doc.is_scrap_item",
|
||||
"depends_on": "eval:!doc.type && !doc.is_legacy_scrap_item",
|
||||
"fieldname": "rm_cost_per_qty",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Raw Material Cost Per Qty",
|
||||
@@ -240,7 +244,7 @@
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval: !doc.is_scrap_item",
|
||||
"depends_on": "eval:!doc.type && !doc.is_legacy_scrap_item",
|
||||
"fieldname": "service_cost_per_qty",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Service Cost Per Qty",
|
||||
@@ -250,7 +254,7 @@
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval: !doc.is_scrap_item",
|
||||
"depends_on": "eval:!doc.type && !doc.is_legacy_scrap_item",
|
||||
"fieldname": "additional_cost_per_qty",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Additional Cost Per Qty",
|
||||
@@ -274,7 +278,7 @@
|
||||
"width": "100px"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: !parent.is_return",
|
||||
"depends_on": "eval: !parent.is_return && !doc.type && !doc.is_legacy_scrap_item",
|
||||
"fieldname": "rejected_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"ignore_user_permissions": 1,
|
||||
@@ -283,11 +287,10 @@
|
||||
"options": "Warehouse",
|
||||
"print_hide": 1,
|
||||
"print_width": "100px",
|
||||
"read_only_depends_on": "eval: doc.is_scrap_item",
|
||||
"width": "100px"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.__islocal",
|
||||
"depends_on": "eval:!doc.__islocal && !doc.type && !doc.is_legacy_scrap_item",
|
||||
"fieldname": "quality_inspection",
|
||||
"fieldtype": "Link",
|
||||
"label": "Quality Inspection",
|
||||
@@ -369,7 +372,7 @@
|
||||
"no_copy": 1,
|
||||
"options": "BOM",
|
||||
"print_hide": 1,
|
||||
"read_only_depends_on": "eval: doc.is_scrap_item"
|
||||
"read_only_depends_on": "eval:doc.type || doc.is_legacy_scrap_item"
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.brand",
|
||||
@@ -496,7 +499,7 @@
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
|
||||
"depends_on": "eval:(doc.use_serial_batch_fields === 0 || doc.docstatus === 1) && !doc.type && !doc.is_legacy_scrap_item",
|
||||
"fieldname": "rejected_serial_and_batch_bundle",
|
||||
"fieldtype": "Link",
|
||||
"label": "Rejected Serial and Batch Bundle",
|
||||
@@ -504,26 +507,6 @@
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"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",
|
||||
@@ -553,6 +536,7 @@
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.bom",
|
||||
"fieldname": "include_exploded_items",
|
||||
"fieldtype": "Check",
|
||||
"label": "Include Exploded Items",
|
||||
@@ -580,7 +564,7 @@
|
||||
"label": "Add Serial / Batch Bundle"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 0",
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 0 && !doc.type && !doc.is_legacy_scrap_item",
|
||||
"fieldname": "add_serial_batch_for_rejected_qty",
|
||||
"fieldtype": "Button",
|
||||
"label": "Add Serial / Batch No (Rejected Qty)"
|
||||
@@ -594,6 +578,7 @@
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.type && !doc.is_legacy_scrap_item",
|
||||
"fieldname": "landed_cost_voucher_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Landed Cost Voucher Amount",
|
||||
@@ -609,13 +594,48 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Service Expense Account",
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
"fieldname": "type",
|
||||
"fieldtype": "Select",
|
||||
"label": "Type",
|
||||
"no_copy": 1,
|
||||
"options": "\nCo-Product\nBy-Product\nScrap\nAdditional Finished Good",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:!doc.type && !doc.is_legacy_scrap_item",
|
||||
"fieldname": "secondary_items_cost_per_qty",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Secondary Items Cost Per Qty",
|
||||
"no_copy": 1,
|
||||
"non_negative": 1,
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "is_legacy_scrap_item",
|
||||
"fieldname": "is_legacy_scrap_item",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Legacy Scrap Item",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "process_loss_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Process Loss Qty",
|
||||
"non_negative": 1
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-09-26 12:00:38.877638",
|
||||
"modified": "2026-03-09 15:11:16.977539",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Subcontracting",
|
||||
"name": "Subcontracting Receipt Item",
|
||||
|
||||
@@ -25,7 +25,7 @@ class SubcontractingReceiptItem(Document):
|
||||
expense_account: DF.Link | None
|
||||
image: DF.Attach | None
|
||||
include_exploded_items: DF.Check
|
||||
is_scrap_item: DF.Check
|
||||
is_legacy_scrap_item: DF.Check
|
||||
item_code: DF.Link
|
||||
item_name: DF.Data | None
|
||||
job_card: DF.Link | None
|
||||
@@ -36,6 +36,7 @@ class SubcontractingReceiptItem(Document):
|
||||
parent: DF.Data
|
||||
parentfield: DF.Data
|
||||
parenttype: DF.Data
|
||||
process_loss_qty: DF.Float
|
||||
project: DF.Link | None
|
||||
purchase_order: DF.Link | None
|
||||
purchase_order_item: DF.Data | None
|
||||
@@ -52,7 +53,7 @@ class SubcontractingReceiptItem(Document):
|
||||
rm_cost_per_qty: DF.Currency
|
||||
rm_supp_cost: DF.Currency
|
||||
schedule_date: DF.Date | None
|
||||
scrap_cost_per_qty: DF.Float
|
||||
secondary_items_cost_per_qty: DF.Currency
|
||||
serial_and_batch_bundle: DF.Link | None
|
||||
serial_no: DF.SmallText | None
|
||||
service_cost_per_qty: DF.Currency
|
||||
@@ -61,6 +62,7 @@ class SubcontractingReceiptItem(Document):
|
||||
subcontracting_order: DF.Link | None
|
||||
subcontracting_order_item: DF.Data | None
|
||||
subcontracting_receipt_item: DF.Data | None
|
||||
type: DF.Literal["", "Co-Product", "By-Product", "Scrap", "Additional Finished Good"]
|
||||
use_serial_batch_fields: DF.Check
|
||||
warehouse: DF.Link | None
|
||||
# end: auto-generated types
|
||||
|
||||
Reference in New Issue
Block a user