chore: rename type field to secondary_item_type (backport #55469) (#58342)

This commit is contained in:
Mihir Kandoi
2026-08-23 14:07:59 +05:30
committed by GitHub
parent 647c93321b
commit a1520d06ee
33 changed files with 173 additions and 117 deletions

View File

@@ -75,7 +75,7 @@ SECONDARY_ITEM_PURPOSES = ("Manufacture", "Repack", "Disassemble")
def is_inspection_exempt_secondary_row(doc, row) -> bool: def is_inspection_exempt_secondary_row(doc, row) -> bool:
"""Whether the row is a secondary item on a document that produces secondary items.""" """Whether the row is a secondary item on a document that produces secondary items."""
if not (row.get("type") or row.get("is_legacy_scrap_item")): if not (row.get("secondary_item_type") or row.get("is_legacy_scrap_item")):
return False return False
if doc.doctype == "Stock Entry": if doc.doctype == "Stock Entry":
@@ -86,7 +86,9 @@ def is_inspection_exempt_secondary_row(doc, row) -> bool:
def stock_entry_row_requires_inspection(purpose, row): def stock_entry_row_requires_inspection(purpose, row):
"""Check if this Stock Entry row need a Quality Inspection.""" """Check if this Stock Entry row need a Quality Inspection."""
if purpose in SECONDARY_ITEM_PURPOSES and (row.get("type") or row.get("is_legacy_scrap_item")): if purpose in SECONDARY_ITEM_PURPOSES and (
row.get("secondary_item_type") or row.get("is_legacy_scrap_item")
):
return False return False
if purpose == "Manufacture": if purpose == "Manufacture":
return bool(row.is_finished_item) return bool(row.is_finished_item)

View File

@@ -161,7 +161,7 @@ class SubcontractingController(StockController):
).format(item.idx, get_link_to_form("Item", item.item_code)) ).format(item.idx, get_link_to_form("Item", item.item_code))
) )
if not item.get("type") and not item.get("is_legacy_scrap_item"): if not item.get("secondary_item_type") and not item.get("is_legacy_scrap_item"):
if not is_sub_contracted_item: if not is_sub_contracted_item:
frappe.throw( frappe.throw(
_("Row {0}: Item {1} must be a subcontracted item.").format(item.idx, item.item_name) _("Row {0}: Item {1} must be a subcontracted item.").format(item.idx, item.item_name)
@@ -1288,10 +1288,10 @@ class SubcontractingController(StockController):
total_amt = sum( total_amt = sum(
flt(item.amount) flt(item.amount)
for item in self.get("items") for item in self.get("items")
if not item.get("type") and not item.get("is_legacy_scrap_item") if not item.get("secondary_item_type") and not item.get("is_legacy_scrap_item")
) )
for item in self.items: for item in self.items:
if not item.get("type") and not item.get("is_legacy_scrap_item"): if not item.get("secondary_item_type") and not item.get("is_legacy_scrap_item"):
item.additional_cost_per_qty = ( item.additional_cost_per_qty = (
(item.amount * self.total_additional_costs) / total_amt (item.amount * self.total_additional_costs) / total_amt
) / item.qty ) / item.qty
@@ -1299,15 +1299,15 @@ class SubcontractingController(StockController):
total_qty = sum( total_qty = sum(
flt(item.qty) flt(item.qty)
for item in self.get("items") for item in self.get("items")
if not item.get("type") and not item.get("is_legacy_scrap_item") if not item.get("secondary_item_type") and not item.get("is_legacy_scrap_item")
) )
additional_cost_per_qty = self.total_additional_costs / total_qty additional_cost_per_qty = self.total_additional_costs / total_qty
for item in self.items: for item in self.items:
if not item.get("type") and not item.get("is_legacy_scrap_item"): if not item.get("secondary_item_type") and not item.get("is_legacy_scrap_item"):
item.additional_cost_per_qty = additional_cost_per_qty item.additional_cost_per_qty = additional_cost_per_qty
else: else:
for item in self.items: for item in self.items:
if not item.get("type") and not item.get("is_legacy_scrap_item"): if not item.get("secondary_item_type") and not item.get("is_legacy_scrap_item"):
item.additional_cost_per_qty = 0 item.additional_cost_per_qty = 0
@frappe.whitelist() @frappe.whitelist()

View File

@@ -241,7 +241,7 @@ class SubcontractingInwardController:
item item
for item in self.get("items") for item in self.get("items")
if not item.is_finished_item if not item.is_finished_item
and not item.type and not item.secondary_item_type
and not item.is_legacy_scrap_item and not item.is_legacy_scrap_item
and frappe.get_cached_value("Item", item.item_code, "is_customer_provided_item") and frappe.get_cached_value("Item", item.item_code, "is_customer_provided_item")
] ]
@@ -372,7 +372,7 @@ class SubcontractingInwardController:
if self.purpose in ["Subcontracting Delivery", "Subcontracting Return", "Manufacture"]: if self.purpose in ["Subcontracting Delivery", "Subcontracting Return", "Manufacture"]:
for item in self.items: for item in self.items:
if ( if (
item.is_finished_item or item.type or item.is_legacy_scrap_item item.is_finished_item or item.secondary_item_type or item.is_legacy_scrap_item
) and item.valuation_rate == 0: ) and item.valuation_rate == 0:
item.allow_zero_valuation_rate = 1 item.allow_zero_valuation_rate = 1
@@ -472,7 +472,7 @@ class SubcontractingInwardController:
self.validate_delivery_on_save() self.validate_delivery_on_save()
else: else:
for item in self.items: for item in self.items:
if not item.type and not item.is_legacy_scrap_item: if not item.secondary_item_type and not item.is_legacy_scrap_item:
delivered_qty, returned_qty = frappe.get_value( delivered_qty, returned_qty = frappe.get_value(
"Subcontracting Inward Order Item", "Subcontracting Inward Order Item",
item.scio_detail, item.scio_detail,
@@ -543,7 +543,7 @@ class SubcontractingInwardController:
bold( bold(
frappe.get_cached_value( frappe.get_cached_value(
"Subcontracting Inward Order Item" "Subcontracting Inward Order Item"
if not item.type and not item.is_legacy_scrap_item if not item.secondary_item_type and not item.is_legacy_scrap_item
else "Subcontracting Inward Order Secondary Item", else "Subcontracting Inward Order Secondary Item",
item.scio_detail, item.scio_detail,
"stock_uom", "stock_uom",
@@ -595,7 +595,7 @@ class SubcontractingInwardController:
) )
for item in [item for item in self.items if not item.is_finished_item]: for item in [item for item in self.items if not item.is_finished_item]:
if item.type or item.is_legacy_scrap_item: if item.secondary_item_type or item.is_legacy_scrap_item:
scio_secondary_item = frappe.get_value( scio_secondary_item = frappe.get_value(
"Subcontracting Inward Order Secondary Item", "Subcontracting Inward Order Secondary Item",
{ {
@@ -655,7 +655,7 @@ class SubcontractingInwardController:
for item in self.items: for item in self.items:
doctype = ( doctype = (
"Subcontracting Inward Order Item" "Subcontracting Inward Order Item"
if not item.type and not item.is_legacy_scrap_item if not item.secondary_item_type and not item.is_legacy_scrap_item
else "Subcontracting Inward Order Secondary Item" else "Subcontracting Inward Order Secondary Item"
) )
qty_map[doctype][item.scio_detail] += ( qty_map[doctype][item.scio_detail] += (
@@ -791,7 +791,7 @@ class SubcontractingInwardController:
items = [ items = [
item item
for item in self.items for item in self.items
if not item.is_finished_item and not item.type and not item.is_legacy_scrap_item if not item.is_finished_item and not item.secondary_item_type and not item.is_legacy_scrap_item
] ]
item_code_wh = frappe._dict( item_code_wh = frappe._dict(
{ {
@@ -893,7 +893,9 @@ class SubcontractingInwardController:
def update_inward_order_secondary_items(self): def update_inward_order_secondary_items(self):
if (scio := self.subcontracting_inward_order) and self.purpose == "Manufacture": if (scio := self.subcontracting_inward_order) and self.purpose == "Manufacture":
secondary_items_list = [item for item in self.items if item.type or item.is_legacy_scrap_item] secondary_items_list = [
item for item in self.items if item.secondary_item_type or item.is_legacy_scrap_item
]
secondary_items = defaultdict(float) secondary_items = defaultdict(float)
for item in secondary_items_list: for item in secondary_items_list:
@@ -967,7 +969,7 @@ class SubcontractingInwardController:
stock_uom=secondary_item.stock_uom, stock_uom=secondary_item.stock_uom,
warehouse=secondary_item.t_warehouse, warehouse=secondary_item.t_warehouse,
produced_qty=secondary_item.transfer_qty, produced_qty=secondary_item.transfer_qty,
type=secondary_item.type, secondary_item_type=secondary_item.secondary_item_type,
delivered_qty=0, delivered_qty=0,
reference_name=frappe.get_value( reference_name=frappe.get_value(
"Work Order", self.work_order, "subcontracting_inward_order_item" "Work Order", self.work_order, "subcontracting_inward_order_item"

View File

@@ -372,7 +372,7 @@ class BOM(WebsiteGenerator):
if item.process_loss_per >= 100: if item.process_loss_per >= 100:
frappe.throw( frappe.throw(
_("Row #{0}: Process Loss Percentage should be less than 100% for {1} Item {2}").format( _("Row #{0}: Process Loss Percentage should be less than 100% for {1} Item {2}").format(
item.idx, item.type, get_link_to_form("Item", item.item_code) item.idx, item.secondary_item_type, get_link_to_form("Item", item.item_code)
) )
) )
@@ -1297,7 +1297,9 @@ class BOM(WebsiteGenerator):
frappe.throw(msg, title=_("Invalid Process Loss Configuration")) frappe.throw(msg, title=_("Invalid Process Loss Configuration"))
def has_scrap_items(self): def has_scrap_items(self):
return any(d.get("type") == "Scrap" or d.get("is_legacy") for d in self.get("secondary_items")) return any(
d.get("secondary_item_type") == "Scrap" or d.get("is_legacy") for d in self.get("secondary_items")
)
def get_bom_item_rate(args, bom_doc): def get_bom_item_rate(args, bom_doc):
@@ -1465,7 +1467,7 @@ def get_bom_items_as_dict(
query = query.format( query = query.format(
table="BOM Secondary Item", table="BOM Secondary Item",
where_conditions=")", where_conditions=")",
select_columns=", item.description, bom_item.cost_allocation_per, bom_item.process_loss_per, bom_item.type, bom_item.name, bom_item.is_legacy", select_columns=", item.description, bom_item.cost_allocation_per, bom_item.process_loss_per, bom_item.secondary_item_type, bom_item.name, bom_item.is_legacy",
is_stock_item=is_stock_item, is_stock_item=is_stock_item,
qty_field="stock_qty", qty_field="stock_qty",
group_by_cond=group_by_cond, group_by_cond=group_by_cond,

View File

@@ -45,7 +45,7 @@
"stock_qty": 1.0, "stock_qty": 1.0,
"rate": 2000.0, "rate": 2000.0,
"stock_uom": "_Test UOM", "stock_uom": "_Test UOM",
"type": "Scrap", "secondary_item_type": "Scrap",
"is_legacy": 1 "is_legacy": 1
} }
], ],

View File

@@ -6,7 +6,7 @@
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"type", "secondary_item_type",
"rate", "rate",
"column_break_gres", "column_break_gres",
"is_legacy", "is_legacy",
@@ -35,7 +35,7 @@
"fields": [ "fields": [
{ {
"depends_on": "eval:!doc.is_legacy", "depends_on": "eval:!doc.is_legacy",
"fieldname": "type", "fieldname": "secondary_item_type",
"fieldtype": "Select", "fieldtype": "Select",
"in_list_view": 1, "in_list_view": 1,
"label": "Type", "label": "Type",

View File

@@ -32,7 +32,7 @@ class BOMSecondaryItem(Document):
rate: DF.Currency rate: DF.Currency
stock_qty: DF.Float stock_qty: DF.Float
stock_uom: DF.Link | None stock_uom: DF.Link | None
type: DF.Literal["", "Co-Product", "By-Product", "Scrap", "Additional Finished Good"] secondary_item_type: DF.Literal["", "Co-Product", "By-Product", "Scrap", "Additional Finished Good"]
uom: DF.Link uom: DF.Link
# end: auto-generated types # end: auto-generated types

View File

@@ -299,7 +299,7 @@ class JobCard(Document):
"stock_qty": values.qty, "stock_qty": values.qty,
"item_name": values.item_name, "item_name": values.item_name,
"stock_uom": values.stock_uom, "stock_uom": values.stock_uom,
"type": values.type, "secondary_item_type": values.secondary_item_type,
"bom_secondary_item": values.name, "bom_secondary_item": values.name,
} }
@@ -1757,7 +1757,7 @@ class JobCard(Document):
ste.stock_entry.pro_doc = frappe.get_doc("Work Order", self.work_order) ste.stock_entry.pro_doc = frappe.get_doc("Work Order", self.work_order)
ste.stock_entry.set_secondary_items_from_job_card() ste.stock_entry.set_secondary_items_from_job_card()
for row in ste.stock_entry.items: for row in ste.stock_entry.items:
if (row.type or row.is_legacy_scrap_item) and not row.t_warehouse: if (row.secondary_item_type or row.is_legacy_scrap_item) and not row.t_warehouse:
row.t_warehouse = self.target_warehouse row.t_warehouse = self.target_warehouse
if auto_submit: if auto_submit:

View File

@@ -1303,7 +1303,7 @@ class TestJobCard(ERPNextTestSuite):
"qty": 1, "qty": 1,
"process_loss_per": 10, "process_loss_per": 10,
"cost_allocation_per": 5, "cost_allocation_per": 5,
"type": "Scrap", "secondary_item_type": "Scrap",
}, },
) )
if submit: if submit:
@@ -1386,7 +1386,8 @@ class TestJobCard(ERPNextTestSuite):
}, },
) )
job_card.append( job_card.append(
"secondary_items", {"item_code": scrap_extra.name, "stock_qty": 5, "type": "Co-Product"} "secondary_items",
{"item_code": scrap_extra.name, "stock_qty": 5, "secondary_item_type": "Co-Product"},
) )
job_card.submit() job_card.submit()
@@ -1405,7 +1406,7 @@ class TestJobCard(ERPNextTestSuite):
self.assertEqual(manufacturing_entry.items[2].qty, 9) self.assertEqual(manufacturing_entry.items[2].qty, 9)
self.assertEqual(flt(manufacturing_entry.items[2].basic_rate, 3), 5.556) self.assertEqual(flt(manufacturing_entry.items[2].basic_rate, 3), 5.556)
self.assertEqual(manufacturing_entry.items[3].item_code, scrap_extra.name) self.assertEqual(manufacturing_entry.items[3].item_code, scrap_extra.name)
self.assertEqual(manufacturing_entry.items[3].type, "Co-Product") self.assertEqual(manufacturing_entry.items[3].secondary_item_type, "Co-Product")
self.assertEqual(manufacturing_entry.items[3].qty, 5) self.assertEqual(manufacturing_entry.items[3].qty, 5)
self.assertEqual(manufacturing_entry.items[3].basic_rate, 0) self.assertEqual(manufacturing_entry.items[3].basic_rate, 0)
@@ -2569,7 +2570,9 @@ class TestJobCard(ERPNextTestSuite):
) )
job_card = frappe.get_last_doc("Job Card", {"work_order": self.work_order.name}) job_card = frappe.get_last_doc("Job Card", {"work_order": self.work_order.name})
job_card.append("secondary_items", {"item_code": "_Test Item", "stock_qty": 2, "type": "Scrap"}) job_card.append(
"secondary_items", {"item_code": "_Test Item", "stock_qty": 2, "secondary_item_type": "Scrap"}
)
job_card.append( job_card.append(
"time_logs", "time_logs",
{ {

View File

@@ -5,7 +5,7 @@
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"type", "secondary_item_type",
"description", "description",
"column_break_3", "column_break_3",
"item_code", "item_code",
@@ -69,7 +69,7 @@
"read_only": 1 "read_only": 1
}, },
{ {
"fieldname": "type", "fieldname": "secondary_item_type",
"fieldtype": "Select", "fieldtype": "Select",
"in_list_view": 1, "in_list_view": 1,
"label": "Type", "label": "Type",
@@ -87,7 +87,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2026-03-06 13:51:00.492621", "modified": "2026-06-01 10:00:00.000000",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "Job Card Secondary Item", "name": "Job Card Secondary Item",

View File

@@ -22,7 +22,7 @@ class JobCardSecondaryItem(Document):
parenttype: DF.Data parenttype: DF.Data
stock_qty: DF.Float stock_qty: DF.Float
stock_uom: DF.Link | None stock_uom: DF.Link | None
type: DF.Literal["Co-Product", "By-Product", "Scrap", "Additional Finished Good"] secondary_item_type: DF.Literal["Co-Product", "By-Product", "Scrap", "Additional Finished Good"]
# end: auto-generated types # end: auto-generated types
pass pass

View File

@@ -3226,7 +3226,7 @@ def make_bom(**args):
bom.append( bom.append(
"secondary_items", "secondary_items",
{ {
"type": "Scrap", "secondary_item_type": "Scrap",
"item_code": item, "item_code": item,
"item_name": item, "item_name": item,
"uom": item_doc.stock_uom, "uom": item_doc.stock_uom,

View File

@@ -1115,7 +1115,7 @@ class TestWorkOrder(ERPNextTestSuite):
stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 10)) stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 10))
for row in stock_entry.items: for row in stock_entry.items:
if row.type or row.is_legacy_scrap_item: if row.secondary_item_type or row.is_legacy_scrap_item:
self.assertEqual(row.qty, 1) self.assertEqual(row.qty, 1)
# Partial Job Card 1 with qty 10 # Partial Job Card 1 with qty 10
@@ -1127,7 +1127,7 @@ class TestWorkOrder(ERPNextTestSuite):
stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 10)) stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 10))
for row in stock_entry.items: for row in stock_entry.items:
if row.type or row.is_legacy_scrap_item: if row.secondary_item_type or row.is_legacy_scrap_item:
self.assertEqual(row.qty, 2) self.assertEqual(row.qty, 2)
# Partial Job Card 2 with qty 10 # Partial Job Card 2 with qty 10
@@ -2501,7 +2501,7 @@ class TestWorkOrder(ERPNextTestSuite):
self.assertTrue(se_doc.additional_costs) self.assertTrue(se_doc.additional_costs)
secondary_items = [] secondary_items = []
for item in se_doc.items: for item in se_doc.items:
if item.type or item.is_legacy_scrap_item: if item.secondary_item_type or item.is_legacy_scrap_item:
secondary_items.append(item.item_code) secondary_items.append(item.item_code)
self.assertEqual( self.assertEqual(
@@ -2966,7 +2966,7 @@ class TestWorkOrder(ERPNextTestSuite):
# Secondary/Scrap item: should be taken from scrap warehouse in disassembly # Secondary/Scrap item: should be taken from scrap warehouse in disassembly
scrap_row = next((i for i in stock_entry.items if i.item_code == scrap_item), None) scrap_row = next((i for i in stock_entry.items if i.item_code == scrap_item), None)
self.assertIsNotNone(scrap_row) self.assertIsNotNone(scrap_row)
self.assertEqual(scrap_row.type, "Scrap") self.assertEqual(scrap_row.secondary_item_type, "Scrap")
self.assertTrue(scrap_row.s_warehouse) self.assertTrue(scrap_row.s_warehouse)
self.assertFalse(scrap_row.t_warehouse) self.assertFalse(scrap_row.t_warehouse)
self.assertEqual(scrap_row.s_warehouse, wo.scrap_warehouse) self.assertEqual(scrap_row.s_warehouse, wo.scrap_warehouse)
@@ -4882,7 +4882,7 @@ class TestWorkOrder(ERPNextTestSuite):
bom.append( bom.append(
"secondary_items", "secondary_items",
{ {
"type": "Scrap", "secondary_item_type": "Scrap",
"item_code": scrap_item, "item_code": scrap_item,
"item_name": scrap_item, "item_name": scrap_item,
"qty": 3, "qty": 3,
@@ -4903,7 +4903,7 @@ class TestWorkOrder(ERPNextTestSuite):
self.assertEqual(len(secondary_items), 1) self.assertEqual(len(secondary_items), 1)
row = secondary_items[0] row = secondary_items[0]
self.assertEqual(row.item_code, scrap_item) self.assertEqual(row.item_code, scrap_item)
self.assertEqual(row.type, "Scrap") self.assertEqual(row.secondary_item_type, "Scrap")
# data is fetched from the BOM (carries bom_qty) # data is fetched from the BOM (carries bom_qty)
self.assertEqual(flt(row.bom_qty), 8.0) self.assertEqual(flt(row.bom_qty), 8.0)
# qty = (bom_secondary_qty / bom_qty) * wo_qty = (3 / 8) * 20 = 7.5 # qty = (bom_secondary_qty / bom_qty) * wo_qty = (3 / 8) * 20 = 7.5
@@ -4930,7 +4930,7 @@ class TestWorkOrder(ERPNextTestSuite):
bom.append( bom.append(
"secondary_items", "secondary_items",
{ {
"type": "Scrap", "secondary_item_type": "Scrap",
"item_code": scrap_item, "item_code": scrap_item,
"item_name": scrap_item, "item_name": scrap_item,
"qty": 3, "qty": 3,
@@ -4959,7 +4959,7 @@ class TestWorkOrder(ERPNextTestSuite):
manufacture_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 8)) manufacture_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 8))
manufacture_entry.submit() manufacture_entry.submit()
generated_row = next(row for row in manufacture_entry.items if row.type == "Scrap") generated_row = next(row for row in manufacture_entry.items if row.secondary_item_type == "Scrap")
wo_order.reload() wo_order.reload()
secondary_items = wo_order.secondary_items secondary_items = wo_order.secondary_items

View File

@@ -88,7 +88,7 @@ frappe.ui.form.on("Work Order", {
return frm.doc.qty == doc.completed_qty ? "green" : "orange"; return frm.doc.qty == doc.completed_qty ? "green" : "orange";
}); });
frm.fields_dict["non_stock_items"].grid.set_column_disp_in_list_view("type", false); frm.fields_dict["non_stock_items"].grid.set_column_disp_in_list_view("secondary_item_type", false);
frm.fields_dict["secondary_items"].grid.set_column_disp_in_list_view("rate", false); frm.fields_dict["secondary_items"].grid.set_column_disp_in_list_view("rate", false);
}, },

View File

@@ -187,11 +187,14 @@ class WorkOrder(Document):
.where( .where(
(parent.work_order == self.name) (parent.work_order == self.name)
& (parent.docstatus == 1) & (parent.docstatus == 1)
& ((child.type != "") | (child.is_legacy_scrap_item == 1)) & ((child.secondary_item_type != "") | (child.is_legacy_scrap_item == 1))
) )
.select( .select(
child.item_code, child.item_code,
Case().when(child.is_legacy_scrap_item == 1, "Scrap (Legacy)").else_(child.type).as_("type"), Case()
.when(child.is_legacy_scrap_item == 1, "Scrap (Legacy)")
.else_(child.secondary_item_type)
.as_("secondary_item_type"),
child.qty, child.qty,
child.uom, child.uom,
child.amount, child.amount,
@@ -207,7 +210,7 @@ class WorkOrder(Document):
filters={"name": self.bom_no}, filters={"name": self.bom_no},
fields=[ fields=[
"secondary_items.item_code", "secondary_items.item_code",
"secondary_items.type", "secondary_items.secondary_item_type",
"secondary_items.qty", "secondary_items.qty",
"secondary_items.uom", "secondary_items.uom",
"secondary_items.cost as amount", "secondary_items.cost as amount",

View File

@@ -501,3 +501,4 @@ erpnext.patches.v16_0.set_stock_uom_in_job_card
erpnext.patches.v16_0.recalculate_purchase_receipt_billing_status erpnext.patches.v16_0.recalculate_purchase_receipt_billing_status
erpnext.patches.v16_0.repair_work_order_material_transfer erpnext.patches.v16_0.repair_work_order_material_transfer
erpnext.patches.v16_0.remove_frappe_crm_custom_fields erpnext.patches.v16_0.remove_frappe_crm_custom_fields
erpnext.patches.v16_0.rename_secondary_item_type_field

View File

@@ -41,7 +41,7 @@ def insert_into_bom():
"conversion_factor": 1, "conversion_factor": 1,
"qty": item.stock_qty, "qty": item.stock_qty,
"is_legacy": 1, "is_legacy": 1,
"type": "Scrap", "secondary_item_type": "Scrap",
} }
) )
secondary_item.insert() secondary_item.insert()
@@ -49,7 +49,14 @@ def insert_into_bom():
def insert_into_job_card(): def insert_into_job_card():
fields = ["item_code", "item_name", "description", "stock_qty", "stock_uom"] fields = ["item_code", "item_name", "description", "stock_qty", "stock_uom"]
bulk_insert("Job Card", "Job Card Scrap Item", "Job Card Secondary Item", fields, ["type"], ["Scrap"]) bulk_insert(
"Job Card",
"Job Card Scrap Item",
"Job Card Secondary Item",
fields,
["secondary_item_type"],
["Scrap"],
)
def insert_into_subcontracting_inward(): def insert_into_subcontracting_inward():
@@ -67,7 +74,7 @@ def insert_into_subcontracting_inward():
"Subcontracting Inward Order Scrap Item", "Subcontracting Inward Order Scrap Item",
"Subcontracting Inward Order Secondary Item", "Subcontracting Inward Order Secondary Item",
fields, fields,
["type"], ["secondary_item_type"],
["Scrap"], ["Scrap"],
) )

View File

@@ -0,0 +1,18 @@
import frappe
from frappe.model.utils.rename_field import rename_field
def execute():
doctypes = [
"BOM Secondary Item",
"Job Card Secondary Item",
"Stock Entry Detail",
"Subcontracting Inward Order Secondary Item",
"Subcontracting Receipt Item",
]
for doctype in doctypes:
if not frappe.db.has_column(doctype, "type"):
continue
rename_field(doctype, "type", "secondary_item_type")

View File

@@ -22,7 +22,10 @@ erpnext.stock.is_incoming_qi_purpose = (purpose) =>
purpose === "Manufacture" || erpnext.stock.qi_incoming_purposes.includes(purpose); purpose === "Manufacture" || erpnext.stock.qi_incoming_purposes.includes(purpose);
erpnext.stock.secondary_item_purposes = ["Manufacture", "Repack", "Disassemble"]; erpnext.stock.secondary_item_purposes = ["Manufacture", "Repack", "Disassemble"];
erpnext.stock.row_requires_quality_inspection = (purpose, row) => { erpnext.stock.row_requires_quality_inspection = (purpose, row) => {
if (erpnext.stock.secondary_item_purposes.includes(purpose) && (row.type || row.is_legacy_scrap_item)) if (
erpnext.stock.secondary_item_purposes.includes(purpose) &&
(row.secondary_item_type || row.is_legacy_scrap_item)
)
return false; return false;
if (purpose === "Manufacture") return !!row.is_finished_item; if (purpose === "Manufacture") return !!row.is_finished_item;
if (erpnext.stock.qi_incoming_purposes.includes(purpose)) return !!row.t_warehouse; if (erpnext.stock.qi_incoming_purposes.includes(purpose)) return !!row.t_warehouse;

View File

@@ -444,7 +444,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters):
my_filters.extend( my_filters.extend(
[ [
"and", "and",
["items.type", "is", "not set"], ["items.secondary_item_type", "is", "not set"],
"and", "and",
["items.is_legacy_scrap_item", "=", 0], ["items.is_legacy_scrap_item", "=", 0],
] ]

View File

@@ -92,7 +92,7 @@ def is_costed_out_of_finished_item(row) -> bool:
A secondary item that is not linked to a BOM has no cost allocation of its own, so it is A secondary item that is not linked to a BOM has no cost allocation of its own, so it is
valued the way the legacy scrap item was: its cost is deducted from the finished good. valued the way the legacy scrap item was: its cost is deducted from the finished good.
""" """
return bool(row.is_legacy_scrap_item or (row.type and not row.bom_secondary_item)) return bool(row.is_legacy_scrap_item or (row.secondary_item_type and not row.bom_secondary_item))
def _qty_tolerance(precision: int) -> float: def _qty_tolerance(precision: int) -> float:
@@ -977,7 +977,7 @@ class StockEntry(StockController, SubcontractingInwardController):
frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx)) frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx))
if self.purpose in ["Manufacture", "Repack"]: if self.purpose in ["Manufacture", "Repack"]:
if d.is_finished_item or d.type or d.is_legacy_scrap_item: if d.is_finished_item or d.secondary_item_type or d.is_legacy_scrap_item:
d.s_warehouse = None d.s_warehouse = None
if not d.t_warehouse: if not d.t_warehouse:
frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx)) frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx))
@@ -988,7 +988,7 @@ class StockEntry(StockController, SubcontractingInwardController):
if self.purpose == "Disassemble": if self.purpose == "Disassemble":
if has_bom: if has_bom:
if d.is_finished_item or d.type or d.is_legacy_scrap_item: if d.is_finished_item or d.secondary_item_type or d.is_legacy_scrap_item:
d.t_warehouse = None d.t_warehouse = None
if not d.s_warehouse: if not d.s_warehouse:
frappe.throw(_("Source warehouse is mandatory for row {0}").format(d.idx)) frappe.throw(_("Source warehouse is mandatory for row {0}").format(d.idx))
@@ -1536,7 +1536,7 @@ class StockEntry(StockController, SubcontractingInwardController):
continue continue
# Zero-qty secondary items carry no inventory value; skip rate calculation # Zero-qty secondary items carry no inventory value; skip rate calculation
if d.type and flt(d.transfer_qty) == 0: if d.secondary_item_type and flt(d.transfer_qty) == 0:
d.basic_rate = 0.0 d.basic_rate = 0.0
d.basic_amount = 0.0 d.basic_amount = 0.0
continue continue
@@ -1559,7 +1559,7 @@ class StockEntry(StockController, SubcontractingInwardController):
if self.bom_no: if self.bom_no:
d.basic_rate *= frappe.get_value("BOM", self.bom_no, "cost_allocation_per") / 100 d.basic_rate *= frappe.get_value("BOM", self.bom_no, "cost_allocation_per") / 100
elif d.type and d.bom_secondary_item: elif d.secondary_item_type and d.bom_secondary_item:
cost_allocation_per = flt( cost_allocation_per = flt(
frappe.get_value("BOM Secondary Item", d.bom_secondary_item, "cost_allocation_per") frappe.get_value("BOM Secondary Item", d.bom_secondary_item, "cost_allocation_per")
) )
@@ -1725,7 +1725,11 @@ class StockEntry(StockController, SubcontractingInwardController):
# Validate only if Material Consumption Entry exists for the Work Order. # Validate only if Material Consumption Entry exists for the Work Order.
if self.get_consumption_entries(): if self.get_consumption_entries():
for item in self.items: for item in self.items:
if not item.is_finished_item and not item.type and not item.is_legacy_scrap_item: if (
not item.is_finished_item
and not item.secondary_item_type
and not item.is_legacy_scrap_item
):
label = frappe.get_meta(settings.doctype).get_label( label = frappe.get_meta(settings.doctype).get_label(
"get_rm_cost_from_consumption_entry" "get_rm_cost_from_consumption_entry"
) )
@@ -2131,13 +2135,13 @@ class StockEntry(StockController, SubcontractingInwardController):
for d in self.items: for d in self.items:
if d.t_warehouse and not d.s_warehouse: if d.t_warehouse and not d.s_warehouse:
if d.type or d.is_legacy_scrap_item: if d.secondary_item_type or d.is_legacy_scrap_item:
d.is_finished_item = 0 d.is_finished_item = 0
elif self.purpose == "Repack" or d.item_code == finished_item: elif self.purpose == "Repack" or d.item_code == finished_item:
d.is_finished_item = 1 d.is_finished_item = 1
else: else:
d.is_finished_item = 0 d.is_finished_item = 0
d.type = "" d.secondary_item_type = ""
def get_finished_item(self): def get_finished_item(self):
finished_item = None finished_item = None
@@ -2881,7 +2885,7 @@ class StockEntry(StockController, SubcontractingInwardController):
"s_warehouse": s_warehouse, "s_warehouse": s_warehouse,
"t_warehouse": t_warehouse, "t_warehouse": t_warehouse,
"is_finished_item": source_row.is_finished_item, "is_finished_item": source_row.is_finished_item,
"type": source_row.type, "secondary_item_type": source_row.secondary_item_type,
"is_legacy_scrap_item": source_row.is_legacy_scrap_item, "is_legacy_scrap_item": source_row.is_legacy_scrap_item,
"bom_secondary_item": source_row.bom_secondary_item, "bom_secondary_item": source_row.bom_secondary_item,
"bom_no": source_row.bom_no, "bom_no": source_row.bom_no,
@@ -2953,7 +2957,7 @@ class StockEntry(StockController, SubcontractingInwardController):
SED.basic_rate, SED.basic_rate,
SED.conversion_factor, SED.conversion_factor,
SED.is_finished_item, SED.is_finished_item,
SED.type, SED.secondary_item_type,
SED.is_legacy_scrap_item, SED.is_legacy_scrap_item,
SED.bom_secondary_item, SED.bom_secondary_item,
SED.batch_no, SED.batch_no,
@@ -3245,8 +3249,8 @@ class StockEntry(StockController, SubcontractingInwardController):
if self.purpose in ["Manufacture", "Repack"]: if self.purpose in ["Manufacture", "Repack"]:
secondary_items_dict = self.get_secondary_items(self.fg_completed_qty) secondary_items_dict = self.get_secondary_items(self.fg_completed_qty)
for item in secondary_items_dict.values(): for item in secondary_items_dict.values():
if self.pro_doc and item.type: if self.pro_doc and item.secondary_item_type:
if self.pro_doc.scrap_warehouse and item.type == "Scrap": if self.pro_doc.scrap_warehouse and item.secondary_item_type == "Scrap":
item["to_warehouse"] = self.pro_doc.scrap_warehouse item["to_warehouse"] = self.pro_doc.scrap_warehouse
if item.process_loss_per: if item.process_loss_per:
@@ -3536,7 +3540,7 @@ class StockEntry(StockController, SubcontractingInwardController):
"from_warehouse": "", "from_warehouse": "",
"qty": row.stock_qty, "qty": row.stock_qty,
"conversion_factor": 1, "conversion_factor": 1,
"type": row.type, "secondary_item_type": row.secondary_item_type,
"item_name": row.item_name, "item_name": row.item_name,
"description": row.description, "description": row.description,
"bom_secondary_item": row.bom_secondary_item, "bom_secondary_item": row.bom_secondary_item,
@@ -3569,7 +3573,7 @@ class StockEntry(StockController, SubcontractingInwardController):
job_card_secondary_item.item_name, job_card_secondary_item.item_name,
job_card_secondary_item.description, job_card_secondary_item.description,
job_card_secondary_item.stock_uom, job_card_secondary_item.stock_uom,
job_card_secondary_item.type, job_card_secondary_item.secondary_item_type,
job_card_secondary_item.bom_secondary_item, job_card_secondary_item.bom_secondary_item,
) )
.join(job_card_secondary_item) .join(job_card_secondary_item)
@@ -3579,7 +3583,7 @@ class StockEntry(StockController, SubcontractingInwardController):
& (job_card.work_order == self.work_order) & (job_card.work_order == self.work_order)
& (job_card.docstatus == 1) & (job_card.docstatus == 1)
) )
.groupby(job_card_secondary_item.item_code, job_card_secondary_item.type) .groupby(job_card_secondary_item.item_code, job_card_secondary_item.secondary_item_type)
.orderby(job_card_secondary_item.idx) .orderby(job_card_secondary_item.idx)
) )
@@ -3621,7 +3625,10 @@ class StockEntry(StockController, SubcontractingInwardController):
.select(StockEntryDetail.item_code, StockEntryDetail.qty) .select(StockEntryDetail.item_code, StockEntryDetail.qty)
.where( .where(
(StockEntry.work_order == self.work_order) (StockEntry.work_order == self.work_order)
& ((StockEntryDetail.type.isnotnull()) | (StockEntryDetail.is_legacy_scrap_item == 1)) & (
(StockEntryDetail.secondary_item_type.isnotnull())
| (StockEntryDetail.is_legacy_scrap_item == 1)
)
& (StockEntry.docstatus == 1) & (StockEntry.docstatus == 1)
& (StockEntry.purpose.isin(["Repack", "Manufacture"])) & (StockEntry.purpose.isin(["Repack", "Manufacture"]))
) )
@@ -3925,7 +3932,7 @@ class StockEntry(StockController, SubcontractingInwardController):
if ( if (
not self.is_return not self.is_return
and child_qty <= 0 and child_qty <= 0
and not item_row.get("type") and not item_row.get("secondary_item_type")
and not item_row.get("is_legacy_scrap_item") and not item_row.get("is_legacy_scrap_item")
): ):
if self.purpose not in ["Receive from Customer", "Send to Subcontractor"]: if self.purpose not in ["Receive from Customer", "Send to Subcontractor"]:
@@ -3949,7 +3956,7 @@ class StockEntry(StockController, SubcontractingInwardController):
se_child.sco_rm_detail = item_row.get("sco_rm_detail") se_child.sco_rm_detail = item_row.get("sco_rm_detail")
se_child.scio_detail = item_row.get("scio_detail") se_child.scio_detail = item_row.get("scio_detail")
se_child.sample_quantity = item_row.get("sample_quantity", 0) se_child.sample_quantity = item_row.get("sample_quantity", 0)
se_child.type = item_row.get("type") se_child.secondary_item_type = item_row.get("secondary_item_type")
se_child.is_legacy_scrap_item = item_row.get("is_legacy") se_child.is_legacy_scrap_item = item_row.get("is_legacy")
se_child.bom_secondary_item = item_row.get("name") or item_row.get("bom_secondary_item") se_child.bom_secondary_item = item_row.get("name") or item_row.get("bom_secondary_item")

View File

@@ -1004,7 +1004,9 @@ class TestStockEntry(ERPNextTestSuite):
if d.s_warehouse: if d.s_warehouse:
rm_cost += d.amount rm_cost += d.amount
fg_cost = next(filter(lambda x: x.item_code == "_Test FG Item", s.get("items"))).amount fg_cost = next(filter(lambda x: x.item_code == "_Test FG Item", s.get("items"))).amount
secondary_item_cost = next(filter(lambda x: x.type or x.is_legacy_scrap_item, s.get("items"))).amount secondary_item_cost = next(
filter(lambda x: x.secondary_item_type or x.is_legacy_scrap_item, s.get("items"))
).amount
self.assertEqual(fg_cost, flt(rm_cost - secondary_item_cost, 2)) self.assertEqual(fg_cost, flt(rm_cost - secondary_item_cost, 2))
# When Stock Entry has only FG + Scrap # When Stock Entry has only FG + Scrap
@@ -1122,7 +1124,7 @@ class TestStockEntry(ERPNextTestSuite):
basic_rate=row.basic_rate or 100, basic_rate=row.basic_rate or 100,
) )
if row.type or row.is_legacy_scrap_item: if row.secondary_item_type or row.is_legacy_scrap_item:
row.item_code = secondary_item row.item_code = secondary_item
row.uom = frappe.db.get_value("Item", secondary_item, "stock_uom") row.uom = frappe.db.get_value("Item", secondary_item, "stock_uom")
row.stock_uom = frappe.db.get_value("Item", secondary_item, "stock_uom") row.stock_uom = frappe.db.get_value("Item", secondary_item, "stock_uom")
@@ -1130,10 +1132,16 @@ class TestStockEntry(ERPNextTestSuite):
stock_entry.inspection_required = 1 stock_entry.inspection_required = 1
stock_entry.save() stock_entry.save()
self.assertTrue([row.item_code for row in stock_entry.items if row.type or row.is_legacy_scrap_item]) self.assertTrue(
[
row.item_code
for row in stock_entry.items
if row.secondary_item_type or row.is_legacy_scrap_item
]
)
for row in stock_entry.items: for row in stock_entry.items:
if not row.type and not row.is_legacy_scrap_item: if not row.secondary_item_type and not row.is_legacy_scrap_item:
qc = frappe.get_doc( qc = frappe.get_doc(
{ {
"doctype": "Quality Inspection", "doctype": "Quality Inspection",
@@ -1153,7 +1161,7 @@ class TestStockEntry(ERPNextTestSuite):
stock_entry.reload() stock_entry.reload()
stock_entry.submit() stock_entry.submit()
for row in stock_entry.items: for row in stock_entry.items:
if row.type or row.is_legacy_scrap_item: if row.secondary_item_type or row.is_legacy_scrap_item:
self.assertFalse(row.quality_inspection) self.assertFalse(row.quality_inspection)
else: else:
self.assertTrue(row.quality_inspection) self.assertTrue(row.quality_inspection)
@@ -2769,7 +2777,7 @@ class TestStockEntry(ERPNextTestSuite):
"item_code": scrap_item, "item_code": scrap_item,
"t_warehouse": warehouse, "t_warehouse": warehouse,
"qty": 5, "qty": 5,
"type": "Scrap", "secondary_item_type": "Scrap",
"conversion_factor": 1, "conversion_factor": 1,
}, },
) )
@@ -2807,7 +2815,7 @@ class TestStockEntry(ERPNextTestSuite):
bom.append( bom.append(
"secondary_items", "secondary_items",
{ {
"type": "Scrap", "secondary_item_type": "Scrap",
"item_code": scrap_item, "item_code": scrap_item,
"item_name": scrap_item, "item_name": scrap_item,
"qty": 5, "qty": 5,
@@ -2833,7 +2841,7 @@ class TestStockEntry(ERPNextTestSuite):
se.save() se.save()
fg_row = next(d for d in se.items if d.is_finished_item) fg_row = next(d for d in se.items if d.is_finished_item)
scrap_row = next(d for d in se.items if d.type) scrap_row = next(d for d in se.items if d.secondary_item_type)
self.assertFalse(scrap_row.is_finished_item) self.assertFalse(scrap_row.is_finished_item)
self.assertEqual(flt(scrap_row.basic_amount), 250.0) self.assertEqual(flt(scrap_row.basic_amount), 250.0)
@@ -2868,7 +2876,7 @@ class TestStockEntry(ERPNextTestSuite):
bom.append( bom.append(
"secondary_items", "secondary_items",
{ {
"type": "Scrap", "secondary_item_type": "Scrap",
"item_code": scrap_item, "item_code": scrap_item,
"item_name": scrap_item, "item_name": scrap_item,
"qty": 5, "qty": 5,
@@ -2888,7 +2896,7 @@ class TestStockEntry(ERPNextTestSuite):
se = frappe.get_doc(make_stock_entry_from_wo(wo.name, "Manufacture", 10)) se = frappe.get_doc(make_stock_entry_from_wo(wo.name, "Manufacture", 10))
se.save() se.save()
scrap_row = next(d for d in se.items if d.type) scrap_row = next(d for d in se.items if d.secondary_item_type)
fg_row = next(d for d in se.items if d.is_finished_item) fg_row = next(d for d in se.items if d.is_finished_item)
self.assertEqual(flt(scrap_row.basic_rate), 0.0) self.assertEqual(flt(scrap_row.basic_rate), 0.0)
@@ -2918,7 +2926,7 @@ class TestStockEntry(ERPNextTestSuite):
"t_warehouse": "_Test Warehouse - _TC", "t_warehouse": "_Test Warehouse - _TC",
"qty": 10, "qty": 10,
"conversion_factor": 1, "conversion_factor": 1,
"type": secondary_item_type, "secondary_item_type": secondary_item_type,
}, },
) )
return se return se
@@ -2954,7 +2962,7 @@ class TestStockEntry(ERPNextTestSuite):
bom.append( bom.append(
"secondary_items", "secondary_items",
{ {
"type": "Scrap", "secondary_item_type": "Scrap",
"item_code": scrap_item, "item_code": scrap_item,
"item_name": scrap_item, "item_name": scrap_item,
"qty": 5, "qty": 5,
@@ -2979,7 +2987,7 @@ class TestStockEntry(ERPNextTestSuite):
se = frappe.get_doc(make_stock_entry_from_wo(wo.name, "Manufacture", 10)) se = frappe.get_doc(make_stock_entry_from_wo(wo.name, "Manufacture", 10))
se.save() se.save()
scrap_row = next(d for d in se.items if d.type) scrap_row = next(d for d in se.items if d.secondary_item_type)
fg_row = next(d for d in se.items if d.is_finished_item) fg_row = next(d for d in se.items if d.is_finished_item)
self.assertEqual(flt(fg_row.basic_amount), 750.0) self.assertEqual(flt(fg_row.basic_amount), 750.0)

View File

@@ -19,7 +19,7 @@
"col_break2", "col_break2",
"is_finished_item", "is_finished_item",
"is_legacy_scrap_item", "is_legacy_scrap_item",
"type", "secondary_item_type",
"quality_inspection", "quality_inspection",
"subcontracted_item", "subcontracted_item",
"against_fg", "against_fg",
@@ -570,7 +570,7 @@
}, },
{ {
"default": "0", "default": "0",
"depends_on": "eval:!doc.is_legacy_scrap_item && !doc.type", "depends_on": "eval:!doc.is_legacy_scrap_item && !doc.secondary_item_type",
"fieldname": "is_finished_item", "fieldname": "is_finished_item",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Is Finished Item", "label": "Is Finished Item",
@@ -664,7 +664,7 @@
}, },
{ {
"depends_on": "eval:parent.purpose == \"Manufacture\" && doc.t_warehouse && !doc.is_finished_item && !doc.is_legacy_scrap_item", "depends_on": "eval:parent.purpose == \"Manufacture\" && doc.t_warehouse && !doc.is_finished_item && !doc.is_legacy_scrap_item",
"fieldname": "type", "fieldname": "secondary_item_type",
"fieldtype": "Select", "fieldtype": "Select",
"label": "Type", "label": "Type",
"options": "\nCo-Product\nBy-Product\nScrap\nAdditional Finished Good" "options": "\nCo-Product\nBy-Product\nScrap\nAdditional Finished Good"

View File

@@ -68,7 +68,7 @@ class StockEntryDetail(Document):
t_warehouse: DF.Link | None t_warehouse: DF.Link | None
transfer_qty: DF.Float transfer_qty: DF.Float
transferred_qty: DF.Float transferred_qty: DF.Float
type: DF.Literal["", "Co-Product", "By-Product", "Scrap", "Additional Finished Good"] secondary_item_type: DF.Literal["", "Co-Product", "By-Product", "Scrap", "Additional Finished Good"]
uom: DF.Link uom: DF.Link
use_serial_batch_fields: DF.Check use_serial_batch_fields: DF.Check
valuation_rate: DF.Currency valuation_rate: DF.Currency

View File

@@ -195,7 +195,7 @@ def get_bom_secondary_item_rows(item, company=None):
rows = frappe.get_all( rows = frappe.get_all(
"BOM Secondary Item", "BOM Secondary Item",
filters={"item_code": item, "parenttype": "BOM", "docstatus": 1}, filters={"item_code": item, "parenttype": "BOM", "docstatus": 1},
fields=["parent", "idx", "type", "qty", "uom", "stock_qty", "stock_uom"], fields=["parent", "idx", "secondary_item_type", "qty", "uom", "stock_qty", "stock_uom"],
order_by="parent asc, idx asc", order_by="parent asc, idx asc",
) )
bom_map = get_bom_map([row.parent for row in rows], company) bom_map = get_bom_map([row.parent for row in rows], company)
@@ -219,7 +219,7 @@ def get_bom_secondary_item_rows(item, company=None):
company=bom.company, company=bom.company,
is_default=bom.is_default, is_default=bom.is_default,
is_active=bom.is_active, is_active=bom.is_active,
details=row.type, details=row.secondary_item_type,
) )
) )

View File

@@ -486,7 +486,7 @@ class SubcontractingInwardOrder(SubcontractingController):
"s_warehouse": secondary_item.warehouse, "s_warehouse": secondary_item.warehouse,
"stock_uom": secondary_item.stock_uom, "stock_uom": secondary_item.stock_uom,
"scio_detail": secondary_item.name, "scio_detail": secondary_item.name,
"type": secondary_item.type, "secondary_item_type": secondary_item.secondary_item_type,
}, },
) )

View File

@@ -367,7 +367,7 @@ class IntegrationTestSubcontractingInwardOrder(ERPNextTestSuite):
def test_secondary_items_delivery(self): def test_secondary_items_delivery(self):
new_bom = frappe.copy_doc(frappe.get_doc("BOM", "BOM-Basic FG Item-001")) new_bom = frappe.copy_doc(frappe.get_doc("BOM", "BOM-Basic FG Item-001"))
new_bom.secondary_items.append( new_bom.secondary_items.append(
frappe.new_doc("BOM Secondary Item", item_code="Basic RM 2", qty=1, type="Scrap") frappe.new_doc("BOM Secondary Item", item_code="Basic RM 2", qty=1, secondary_item_type="Scrap")
) )
new_bom.submit() new_bom.submit()
sc_bom = frappe.get_doc("Subcontracting BOM", "SB-0001") sc_bom = frappe.get_doc("Subcontracting BOM", "SB-0001")

View File

@@ -7,7 +7,7 @@
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"column_break_rptg", "column_break_rptg",
"type", "secondary_item_type",
"reference_name", "reference_name",
"column_break_jkzt", "column_break_jkzt",
"item_code", "item_code",
@@ -97,7 +97,7 @@
"fieldtype": "Column Break" "fieldtype": "Column Break"
}, },
{ {
"fieldname": "type", "fieldname": "secondary_item_type",
"fieldtype": "Select", "fieldtype": "Select",
"label": "Type", "label": "Type",
"no_copy": 1, "no_copy": 1,
@@ -114,7 +114,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2026-02-27 15:15:40.009957", "modified": "2026-06-01 10:00:00.000000",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Subcontracting", "module": "Subcontracting",
"name": "Subcontracting Inward Order Secondary Item", "name": "Subcontracting Inward Order Secondary Item",

View File

@@ -23,7 +23,7 @@ class SubcontractingInwardOrderSecondaryItem(Document):
produced_qty: DF.Float produced_qty: DF.Float
reference_name: DF.Data reference_name: DF.Data
stock_uom: DF.Link stock_uom: DF.Link
type: DF.Literal["Co-Product", "By-Product", "Scrap", "Additional Finished Good"] secondary_item_type: DF.Literal["Co-Product", "By-Product", "Scrap", "Additional Finished Good"]
warehouse: DF.Link warehouse: DF.Link
# end: auto-generated types # end: auto-generated types

View File

@@ -421,7 +421,7 @@ class SubcontractingReceipt(SubcontractingController):
self.append( self.append(
"items", "items",
{ {
"type": secondary_item.type, "secondary_item_type": secondary_item.secondary_item_type,
"is_legacy_scrap_item": secondary_item.is_legacy, "is_legacy_scrap_item": secondary_item.is_legacy,
"reference_name": item.name, "reference_name": item.name,
"item_code": secondary_item.item_code, "item_code": secondary_item.item_code,
@@ -449,7 +449,7 @@ class SubcontractingReceipt(SubcontractingController):
def remove_secondary_items(self): def remove_secondary_items(self):
for item in list(self.items): for item in list(self.items):
if item.type or item.is_legacy_scrap_item: if item.secondary_item_type or item.is_legacy_scrap_item:
self.remove(item) self.remove(item)
else: else:
item.secondary_items_cost_per_qty = 0 item.secondary_items_cost_per_qty = 0
@@ -509,7 +509,7 @@ class SubcontractingReceipt(SubcontractingController):
secondary_items_cost_map = {} secondary_items_cost_map = {}
for item in self.get("items") or []: for item in self.get("items") or []:
if item.type or item.is_legacy_scrap_item: if item.secondary_item_type or item.is_legacy_scrap_item:
qty = ( qty = (
flt(item.qty) flt(item.qty)
if item.is_legacy_scrap_item if item.is_legacy_scrap_item
@@ -524,7 +524,7 @@ class SubcontractingReceipt(SubcontractingController):
total_qty = total_amount = 0 total_qty = total_amount = 0
for item in self.get("items") or []: for item in self.get("items") or []:
if not item.type and not item.is_legacy_scrap_item: if not item.secondary_item_type and not item.is_legacy_scrap_item:
if item.qty: if item.qty:
if item.name in rm_cost_map: if item.name in rm_cost_map:
item.rm_supp_cost = rm_cost_map[item.name] item.rm_supp_cost = rm_cost_map[item.name]
@@ -568,7 +568,7 @@ class SubcontractingReceipt(SubcontractingController):
def validate_secondary_items(self): def validate_secondary_items(self):
for item in self.items: for item in self.items:
if item.type or item.is_legacy_scrap_item: if item.secondary_item_type or item.is_legacy_scrap_item:
if not item.qty: if not item.qty:
frappe.throw( frappe.throw(
_("Row #{0}: Secondary Item Qty cannot be zero").format(item.idx), _("Row #{0}: Secondary Item Qty cannot be zero").format(item.idx),

View File

@@ -1221,7 +1221,7 @@ class TestSubcontractingReceipt(ERPNextTestSuite):
scr.get_secondary_items() scr.get_secondary_items()
scr_secondary_items = set( scr_secondary_items = set(
[item.item_code for item in scr.items if item.type or item.is_legacy_scrap_item] [item.item_code for item in scr.items if item.secondary_item_type or item.is_legacy_scrap_item]
) )
self.assertEqual(len(scr.items), 3) # 1 FG Item + 2 Scrap Items self.assertEqual(len(scr.items), 3) # 1 FG Item + 2 Scrap Items
self.assertEqual(scr_secondary_items, set(secondary_items)) self.assertEqual(scr_secondary_items, set(secondary_items))

View File

@@ -9,7 +9,7 @@
"field_order": [ "field_order": [
"item_code", "item_code",
"is_legacy_scrap_item", "is_legacy_scrap_item",
"type", "secondary_item_type",
"column_break_2", "column_break_2",
"item_name", "item_name",
"section_break_4", "section_break_4",
@@ -162,12 +162,12 @@
"label": "Accepted Qty", "label": "Accepted Qty",
"no_copy": 1, "no_copy": 1,
"print_width": "100px", "print_width": "100px",
"read_only_depends_on": "eval:doc.type || doc.is_legacy_scrap_item", "read_only_depends_on": "eval:doc.secondary_item_type || doc.is_legacy_scrap_item",
"width": "100px" "width": "100px"
}, },
{ {
"columns": 1, "columns": 1,
"depends_on": "eval:!parent.is_return && !doc.type && !doc.is_legacy_scrap_item", "depends_on": "eval:!parent.is_return && !doc.secondary_item_type && !doc.is_legacy_scrap_item",
"fieldname": "rejected_qty", "fieldname": "rejected_qty",
"fieldtype": "Float", "fieldtype": "Float",
"in_list_view": 1, "in_list_view": 1,
@@ -175,7 +175,7 @@
"no_copy": 1, "no_copy": 1,
"print_hide": 1, "print_hide": 1,
"print_width": "100px", "print_width": "100px",
"read_only_depends_on": "eval:doc.type || doc.is_legacy_scrap_item", "read_only_depends_on": "eval:doc.secondary_item_type || doc.is_legacy_scrap_item",
"width": "100px" "width": "100px"
}, },
{ {
@@ -235,7 +235,7 @@
}, },
{ {
"default": "0", "default": "0",
"depends_on": "eval:!doc.type && !doc.is_legacy_scrap_item", "depends_on": "eval:!doc.secondary_item_type && !doc.is_legacy_scrap_item",
"fieldname": "rm_cost_per_qty", "fieldname": "rm_cost_per_qty",
"fieldtype": "Currency", "fieldtype": "Currency",
"label": "Raw Material Cost Per Qty", "label": "Raw Material Cost Per Qty",
@@ -245,7 +245,7 @@
}, },
{ {
"default": "0", "default": "0",
"depends_on": "eval:!doc.type && !doc.is_legacy_scrap_item", "depends_on": "eval:!doc.secondary_item_type && !doc.is_legacy_scrap_item",
"fieldname": "service_cost_per_qty", "fieldname": "service_cost_per_qty",
"fieldtype": "Currency", "fieldtype": "Currency",
"label": "Service Cost Per Qty", "label": "Service Cost Per Qty",
@@ -255,7 +255,7 @@
}, },
{ {
"default": "0", "default": "0",
"depends_on": "eval:!doc.type && !doc.is_legacy_scrap_item", "depends_on": "eval:!doc.secondary_item_type && !doc.is_legacy_scrap_item",
"fieldname": "additional_cost_per_qty", "fieldname": "additional_cost_per_qty",
"fieldtype": "Currency", "fieldtype": "Currency",
"label": "Additional Cost Per Qty", "label": "Additional Cost Per Qty",
@@ -279,7 +279,7 @@
"width": "100px" "width": "100px"
}, },
{ {
"depends_on": "eval: !parent.is_return && !doc.type && !doc.is_legacy_scrap_item", "depends_on": "eval: !parent.is_return && !doc.secondary_item_type && !doc.is_legacy_scrap_item",
"fieldname": "rejected_warehouse", "fieldname": "rejected_warehouse",
"fieldtype": "Link", "fieldtype": "Link",
"ignore_user_permissions": 1, "ignore_user_permissions": 1,
@@ -291,7 +291,7 @@
"width": "100px" "width": "100px"
}, },
{ {
"depends_on": "eval:!doc.__islocal && !doc.type && !doc.is_legacy_scrap_item", "depends_on": "eval:!doc.__islocal && !doc.secondary_item_type && !doc.is_legacy_scrap_item",
"fieldname": "quality_inspection", "fieldname": "quality_inspection",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Quality Inspection", "label": "Quality Inspection",
@@ -373,7 +373,7 @@
"no_copy": 1, "no_copy": 1,
"options": "BOM", "options": "BOM",
"print_hide": 1, "print_hide": 1,
"read_only_depends_on": "eval:doc.type || doc.is_legacy_scrap_item" "read_only_depends_on": "eval:doc.secondary_item_type || doc.is_legacy_scrap_item"
}, },
{ {
"fetch_from": "item_code.brand", "fetch_from": "item_code.brand",
@@ -500,7 +500,7 @@
"print_hide": 1 "print_hide": 1
}, },
{ {
"depends_on": "eval:(doc.use_serial_batch_fields === 0 || doc.docstatus === 1) && !doc.type && !doc.is_legacy_scrap_item", "depends_on": "eval:(doc.use_serial_batch_fields === 0 || doc.docstatus === 1) && !doc.secondary_item_type && !doc.is_legacy_scrap_item",
"fieldname": "rejected_serial_and_batch_bundle", "fieldname": "rejected_serial_and_batch_bundle",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Rejected Serial and Batch Bundle", "label": "Rejected Serial and Batch Bundle",
@@ -565,7 +565,7 @@
"label": "Add Serial / Batch Bundle" "label": "Add Serial / Batch Bundle"
}, },
{ {
"depends_on": "eval:doc.use_serial_batch_fields === 0 && !doc.type && !doc.is_legacy_scrap_item", "depends_on": "eval:doc.use_serial_batch_fields === 0 && !doc.secondary_item_type && !doc.is_legacy_scrap_item",
"fieldname": "add_serial_batch_for_rejected_qty", "fieldname": "add_serial_batch_for_rejected_qty",
"fieldtype": "Button", "fieldtype": "Button",
"label": "Add Serial / Batch No (Rejected Qty)" "label": "Add Serial / Batch No (Rejected Qty)"
@@ -579,7 +579,7 @@
"search_index": 1 "search_index": 1
}, },
{ {
"depends_on": "eval:!doc.type && !doc.is_legacy_scrap_item", "depends_on": "eval:!doc.secondary_item_type && !doc.is_legacy_scrap_item",
"fieldname": "landed_cost_voucher_amount", "fieldname": "landed_cost_voucher_amount",
"fieldtype": "Currency", "fieldtype": "Currency",
"label": "Landed Cost Voucher Amount", "label": "Landed Cost Voucher Amount",
@@ -597,7 +597,7 @@
"options": "Account" "options": "Account"
}, },
{ {
"fieldname": "type", "fieldname": "secondary_item_type",
"fieldtype": "Select", "fieldtype": "Select",
"label": "Type", "label": "Type",
"no_copy": 1, "no_copy": 1,
@@ -607,7 +607,7 @@
}, },
{ {
"default": "0", "default": "0",
"depends_on": "eval:!doc.type && !doc.is_legacy_scrap_item", "depends_on": "eval:!doc.secondary_item_type && !doc.is_legacy_scrap_item",
"fieldname": "secondary_items_cost_per_qty", "fieldname": "secondary_items_cost_per_qty",
"fieldtype": "Currency", "fieldtype": "Currency",
"label": "Secondary Items Cost Per Qty", "label": "Secondary Items Cost Per Qty",

View File

@@ -62,7 +62,7 @@ class SubcontractingReceiptItem(Document):
subcontracting_order: DF.Link | None subcontracting_order: DF.Link | None
subcontracting_order_item: DF.Data | None subcontracting_order_item: DF.Data | None
subcontracting_receipt_item: DF.Data | None subcontracting_receipt_item: DF.Data | None
type: DF.Literal["", "Co-Product", "By-Product", "Scrap", "Additional Finished Good"] secondary_item_type: DF.Literal["", "Co-Product", "By-Product", "Scrap", "Additional Finished Good"]
use_serial_batch_fields: DF.Check use_serial_batch_fields: DF.Check
warehouse: DF.Link | None warehouse: DF.Link | None
# end: auto-generated types # end: auto-generated types