Skip to content

Commit 2c73e37

Browse files
rohitwaghchauremergify[bot]
authored andcommitted
feat: backflush based on in BOM
(cherry picked from commit 877d99c)
1 parent 3bee79b commit 2c73e37

9 files changed

Lines changed: 201 additions & 77 deletions

File tree

erpnext/manufacturing/doctype/bom/bom.json

Lines changed: 96 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -7,31 +7,18 @@
77
"engine": "InnoDB",
88
"field_order": [
99
"production_item_tab",
10+
"final_product_section",
1011
"company",
1112
"item",
13+
"column_break_ztxc",
1214
"quantity",
13-
"cb0",
14-
"is_active",
15-
"is_default",
16-
"allow_alternative_item",
17-
"set_rate_of_sub_assembly_item_based_on_bom",
18-
"is_phantom_bom",
19-
"cost_allocation_section",
15+
"uom",
16+
"cost_allocation__process_loss_section",
2017
"cost_allocation_per",
21-
"column_break_srby",
2218
"cost_allocation",
23-
"process_loss_section",
19+
"column_break_tgkb",
2420
"process_loss_percentage",
25-
"column_break_ssj2",
2621
"process_loss_qty",
27-
"currency_detail",
28-
"rm_cost_as_per",
29-
"buying_price_list",
30-
"price_list_currency",
31-
"plc_conversion_rate",
32-
"column_break_ivyw",
33-
"currency",
34-
"conversion_rate",
3522
"operations_section_section",
3623
"with_operations",
3724
"track_semi_finished_goods",
@@ -46,8 +33,27 @@
4633
"operations",
4734
"materials_section",
4835
"items",
49-
"secondary_items_tab",
36+
"section_break_hygk",
5037
"secondary_items",
38+
"bom_conf_tab",
39+
"bom_configuration_section",
40+
"column_break_zbzp",
41+
"is_active",
42+
"is_default",
43+
"set_rate_of_sub_assembly_item_based_on_bom",
44+
"cb0",
45+
"is_phantom_bom",
46+
"allow_alternative_item",
47+
"quality_inspection_section_break",
48+
"inspection_required",
49+
"column_break_dxp7",
50+
"quality_inspection_template",
51+
"default_warehouse_section",
52+
"default_source_warehouse",
53+
"column_break_inep",
54+
"default_target_warehouse",
55+
"consume_components_section",
56+
"backflush_based_on",
5157
"costing",
5258
"operating_cost",
5359
"raw_material_cost",
@@ -59,23 +65,21 @@
5965
"column_break_26",
6066
"total_cost",
6167
"base_total_cost",
62-
"quality_inspection_tab",
63-
"quality_inspection_section_break",
64-
"inspection_required",
65-
"column_break_dxp7",
66-
"quality_inspection_template",
6768
"more_info_tab",
69+
"currency_detail",
70+
"rm_cost_as_per",
71+
"buying_price_list",
72+
"price_list_currency",
73+
"plc_conversion_rate",
74+
"column_break_ivyw",
75+
"currency",
76+
"conversion_rate",
6877
"production_item_info_section",
6978
"item_name",
70-
"uom",
7179
"image",
7280
"column_break_27",
7381
"description",
7482
"has_variants",
75-
"default_warehouse_section",
76-
"default_source_warehouse",
77-
"column_break_inep",
78-
"default_target_warehouse",
7983
"section_break_ouuf",
8084
"project",
8185
"section_break0",
@@ -99,17 +103,18 @@
99103
],
100104
"fields": [
101105
{
102-
"description": "Item to be manufactured or repacked",
106+
"description": "The final item that will be produced using this BOM.",
103107
"fieldname": "item",
104108
"fieldtype": "Link",
105109
"in_list_view": 1,
106110
"in_standard_filter": 1,
107-
"label": "Item",
111+
"label": "Item to Manufacture",
108112
"oldfieldname": "item",
109113
"oldfieldtype": "Link",
110114
"options": "Item",
111115
"reqd": 1,
112-
"search_index": 1
116+
"search_index": 1,
117+
"show_description_on_click": 1
113118
},
114119
{
115120
"fetch_from": "item.item_name",
@@ -130,23 +135,26 @@
130135
"read_only": 1
131136
},
132137
{
138+
"depends_on": "item",
133139
"fetch_from": "item.stock_uom",
134140
"fieldname": "uom",
135141
"fieldtype": "Link",
136-
"label": "Item UOM",
142+
"label": "Unit Of Measure",
137143
"options": "UOM",
138144
"read_only": 1
139145
},
140146
{
141147
"default": "1",
142-
"description": "Quantity of item obtained after manufacturing / repacking from given quantities of raw materials",
148+
"depends_on": "item",
149+
"description": "How many units of the final product this BOM makes.",
143150
"fieldname": "quantity",
144151
"fieldtype": "Float",
145-
"label": "Quantity",
152+
"label": "Quantity (Output Qty)",
146153
"non_negative": 1,
147154
"oldfieldname": "quantity",
148155
"oldfieldtype": "Currency",
149-
"reqd": 1
156+
"reqd": 1,
157+
"show_description_on_click": 1
150158
},
151159
{
152160
"fieldname": "cb0",
@@ -288,14 +296,13 @@
288296
{
289297
"fieldname": "materials_section",
290298
"fieldtype": "Section Break",
291-
"label": "Raw Materials",
292299
"oldfieldtype": "Section Break"
293300
},
294301
{
295302
"allow_bulk_edit": 1,
296303
"fieldname": "items",
297304
"fieldtype": "Table",
298-
"label": "Items",
305+
"label": "Components",
299306
"oldfieldname": "bom_materials",
300307
"oldfieldtype": "Table",
301308
"options": "BOM Item",
@@ -415,6 +422,7 @@
415422
"depends_on": "eval:!doc.is_phantom_bom",
416423
"fieldname": "website_section",
417424
"fieldtype": "Tab Break",
425+
"hidden": 1,
418426
"label": "Website"
419427
},
420428
{
@@ -528,11 +536,6 @@
528536
"fieldtype": "Section Break",
529537
"label": "Operations"
530538
},
531-
{
532-
"fieldname": "process_loss_section",
533-
"fieldtype": "Section Break",
534-
"label": "Process Loss"
535-
},
536539
{
537540
"fieldname": "process_loss_percentage",
538541
"fieldtype": "Percent",
@@ -546,10 +549,6 @@
546549
"non_negative": 1,
547550
"read_only": 1
548551
},
549-
{
550-
"fieldname": "column_break_ssj2",
551-
"fieldtype": "Column Break"
552-
},
553552
{
554553
"fieldname": "more_info_tab",
555554
"fieldtype": "Tab Break",
@@ -668,11 +667,6 @@
668667
"fieldname": "section_break_ouuf",
669668
"fieldtype": "Section Break"
670669
},
671-
{
672-
"fieldname": "quality_inspection_tab",
673-
"fieldtype": "Tab Break",
674-
"label": "Quality Inspection"
675-
},
676670
{
677671
"fieldname": "secondary_items",
678672
"fieldtype": "Table",
@@ -697,20 +691,6 @@
697691
"options": "Company:company:default_currency",
698692
"read_only": 1
699693
},
700-
{
701-
"fieldname": "secondary_items_tab",
702-
"fieldtype": "Tab Break",
703-
"label": "Secondary Items"
704-
},
705-
{
706-
"fieldname": "cost_allocation_section",
707-
"fieldtype": "Section Break",
708-
"label": "Cost Allocation"
709-
},
710-
{
711-
"fieldname": "column_break_srby",
712-
"fieldtype": "Column Break"
713-
},
714694
{
715695
"fieldname": "cost_allocation",
716696
"fieldtype": "Currency",
@@ -725,14 +705,63 @@
725705
"fieldtype": "Percent",
726706
"label": "% Cost Allocation",
727707
"non_negative": 1
708+
},
709+
{
710+
"collapsible": 1,
711+
"fieldname": "bom_configuration_section",
712+
"fieldtype": "Section Break"
713+
},
714+
{
715+
"fieldname": "column_break_zbzp",
716+
"fieldtype": "Column Break"
717+
},
718+
{
719+
"fieldname": "column_break_ztxc",
720+
"fieldtype": "Column Break"
721+
},
722+
{
723+
"collapsible": 1,
724+
"fieldname": "cost_allocation__process_loss_section",
725+
"fieldtype": "Section Break",
726+
"label": "Cost Allocation / Process Loss"
727+
},
728+
{
729+
"fieldname": "column_break_tgkb",
730+
"fieldtype": "Column Break"
731+
},
732+
{
733+
"fieldname": "section_break_hygk",
734+
"fieldtype": "Section Break"
735+
},
736+
{
737+
"fieldname": "final_product_section",
738+
"fieldtype": "Section Break"
739+
},
740+
{
741+
"fieldname": "bom_conf_tab",
742+
"fieldtype": "Tab Break",
743+
"label": "BOM Configuration"
744+
},
745+
{
746+
"fieldname": "consume_components_section",
747+
"fieldtype": "Section Break",
748+
"label": "Consume Components"
749+
},
750+
{
751+
"description": "Controls how raw materials are consumed during the \u2018Manufacture\u2019 stock entry.",
752+
"fieldname": "backflush_based_on",
753+
"fieldtype": "Select",
754+
"label": "Based On",
755+
"options": "\nBOM\nMaterial Transferred for Manufacture",
756+
"show_description_on_click": 1
728757
}
729758
],
730759
"icon": "fa fa-sitemap",
731760
"idx": 1,
732761
"image_field": "image",
733762
"is_submittable": 1,
734763
"links": [],
735-
"modified": "2026-02-26 14:13:34.040181",
764+
"modified": "2026-04-17 15:22:33.598938",
736765
"modified_by": "Administrator",
737766
"module": "Manufacturing",
738767
"name": "BOM",

erpnext/manufacturing/doctype/bom/bom.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ class BOM(WebsiteGenerator):
117117

118118
allow_alternative_item: DF.Check
119119
amended_from: DF.Link | None
120+
backflush_based_on: DF.Literal["", "BOM", "Material Transferred for Manufacture"]
120121
base_operating_cost: DF.Currency
121122
base_raw_material_cost: DF.Currency
122123
base_secondary_items_cost: DF.Currency
@@ -1982,3 +1983,16 @@ def get_secondary_items_from_sub_assemblies(bom_no, company, qty, secondary_item
19821983
get_secondary_items_from_sub_assemblies(row.bom_no, company, qty, secondary_items)
19831984

19841985
return secondary_items
1986+
1987+
1988+
def get_backflush_based_on(bom_no):
1989+
backflush_based_on = None
1990+
if bom_no:
1991+
backflush_based_on = frappe.get_cached_value("BOM", bom_no, "backflush_based_on")
1992+
1993+
if not backflush_based_on:
1994+
backflush_based_on = frappe.db.get_single_value(
1995+
"Manufacturing Settings", "backflush_raw_materials_based_on"
1996+
)
1997+
1998+
return backflush_based_on

erpnext/manufacturing/doctype/production_plan/test_production_plan.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2879,6 +2879,9 @@ def make_bom(**args):
28792879
}
28802880
)
28812881

2882+
if args.backflush_based_on:
2883+
bom.backflush_based_on = args.backflush_based_on
2884+
28822885
if args.operating_cost_per_bom_quantity:
28832886
bom.fg_based_operating_cost = 1
28842887
bom.operating_cost_per_bom_quantity = args.operating_cost_per_bom_quantity

erpnext/manufacturing/doctype/work_order/test_work_order.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4240,6 +4240,66 @@ def test_operating_time(self):
42404240
self.assertEqual(wo_order.operations[0].time_in_mins, 72)
42414241
self.assertEqual(wo_order.operations[1].time_in_mins, 240)
42424242

4243+
def test_backflush_based_on_in_bom(self):
4244+
raw_material_1 = make_item(item_code="BOM RM 1", properties={"is_stock_item": 1}).name
4245+
raw_material_2 = make_item(item_code="BOM RM 2", properties={"is_stock_item": 1}).name
4246+
fg_item = make_item(item_code="BOM FG 1", properties={"is_stock_item": 1}).name
4247+
4248+
frappe.db.set_single_value("Manufacturing Settings", "backflush_raw_materials_based_on", "BOM")
4249+
4250+
backflush_based_on = frappe.db.get_single_value(
4251+
"Manufacturing Settings", "backflush_raw_materials_based_on"
4252+
)
4253+
self.assertEqual(backflush_based_on, "BOM")
4254+
4255+
for item_code in [raw_material_1, raw_material_2]:
4256+
test_stock_entry.make_stock_entry(
4257+
item_code=item_code, target="Stores - _TC", qty=1, basic_rate=100
4258+
)
4259+
4260+
bom = make_bom(
4261+
item=fg_item,
4262+
quantity=1,
4263+
raw_materials=[raw_material_1],
4264+
backflush_based_on="Material Transferred for Manufacture",
4265+
)
4266+
4267+
wo_order = make_wo_order_test_record(item=fg_item, qty=1, source_warehouse="Stores - _TC")
4268+
4269+
self.assertEqual(bom.name, wo_order.bom_no)
4270+
backflush_based_on = frappe.db.get_value("BOM", wo_order.bom_no, "backflush_based_on")
4271+
self.assertEqual(backflush_based_on, "Material Transferred for Manufacture")
4272+
4273+
material_transfer_entry = frappe.get_doc(
4274+
make_stock_entry(wo_order.name, "Material Transfer for Manufacture", 1)
4275+
)
4276+
material_transfer_entry.save()
4277+
4278+
# Add second raw material in the material transfer entry which is not in the BOM to simulate backflush based on material transfer scenario
4279+
material_transfer_entry.append(
4280+
"items",
4281+
{
4282+
"item_code": raw_material_2,
4283+
"item_name": raw_material_2,
4284+
"item_group": frappe.get_value("Item", raw_material_2, "item_group"),
4285+
"uom": frappe.get_value("Item", raw_material_2, "stock_uom"),
4286+
"conversion_factor": 1,
4287+
"s_warehouse": "Stores - _TC",
4288+
"t_warehouse": material_transfer_entry.items[0].t_warehouse,
4289+
"qty": 1,
4290+
},
4291+
)
4292+
4293+
material_transfer_entry.submit()
4294+
4295+
manufacture_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 1))
4296+
manufacture_entry.save()
4297+
4298+
self.assertEqual(len(manufacture_entry.items), 3)
4299+
for row in manufacture_entry.items:
4300+
if row.s_warehouse:
4301+
self.assertIn(row.item_code, [raw_material_1, raw_material_2])
4302+
42434303

42444304
def get_reserved_entries(voucher_no, warehouse=None):
42454305
doctype = frappe.qb.DocType("Stock Reservation Entry")

0 commit comments

Comments
 (0)