diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js
index ac2d962d075d..fe8fea688fcc 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.js
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.js
@@ -402,13 +402,15 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
);
}
} else {
- this.frm.add_custom_button(
- __("Subcontracting Order"),
- () => {
- me.make_subcontracting_order();
- },
- __("Create")
- );
+ if (!doc.items.every((item) => item.qty == item.sco_qty)) {
+ this.frm.add_custom_button(
+ __("Subcontracting Order"),
+ () => {
+ me.make_subcontracting_order();
+ },
+ __("Create")
+ );
+ }
}
}
}
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index d7af491edbe8..23a382c050d9 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.py
@@ -871,27 +871,40 @@ def make_inter_company_sales_order(source_name, target_doc=None):
@frappe.whitelist()
def make_subcontracting_order(source_name, target_doc=None, save=False, submit=False, notify=False):
- target_doc = get_mapped_subcontracting_order(source_name, target_doc)
-
- if (save or submit) and frappe.has_permission(target_doc.doctype, "create"):
- target_doc.save()
+ if not is_po_fully_subcontracted(source_name):
+ target_doc = get_mapped_subcontracting_order(source_name, target_doc)
+
+ if (save or submit) and frappe.has_permission(target_doc.doctype, "create"):
+ target_doc.save()
+
+ if submit and frappe.has_permission(target_doc.doctype, "submit", target_doc):
+ try:
+ target_doc.submit()
+ except Exception as e:
+ target_doc.add_comment("Comment", _("Submit Action Failed") + "
" + str(e))
+
+ if notify:
+ frappe.msgprint(
+ _("Subcontracting Order {0} created.").format(
+ get_link_to_form(target_doc.doctype, target_doc.name)
+ ),
+ indicator="green",
+ alert=True,
+ )
- if submit and frappe.has_permission(target_doc.doctype, "submit", target_doc):
- try:
- target_doc.submit()
- except Exception as e:
- target_doc.add_comment("Comment", _("Submit Action Failed") + "
" + str(e))
+ return target_doc
+ else:
+ frappe.throw(_("This PO has been fully subcontracted."))
- if notify:
- frappe.msgprint(
- _("Subcontracting Order {0} created.").format(
- get_link_to_form(target_doc.doctype, target_doc.name)
- ),
- indicator="green",
- alert=True,
- )
- return target_doc
+def is_po_fully_subcontracted(po_name):
+ table = frappe.qb.DocType("Purchase Order Item")
+ query = (
+ frappe.qb.from_(table)
+ .select(table.name)
+ .where((table.parent == po_name) & (table.qty != table.sco_qty))
+ )
+ return not query.run(as_dict=True)
def get_mapped_subcontracting_order(source_name, target_doc=None):
@@ -943,7 +956,8 @@ def post_process(source_doc, target_doc):
"material_request": "material_request",
"material_request_item": "material_request_item",
},
- "field_no_map": [],
+ "field_no_map": ["qty", "fg_item_qty", "amount"],
+ "condition": lambda item: item.qty != item.sco_qty,
},
},
target_doc,
@@ -951,12 +965,3 @@ def post_process(source_doc, target_doc):
)
return target_doc
-
-
-@frappe.whitelist()
-def is_subcontracting_order_created(po_name) -> bool:
- return (
- True
- if frappe.db.exists("Subcontracting Order", {"purchase_order": po_name, "docstatus": ["=", 1]})
- else False
- )
diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
index 63d50989111e..97d87d931dbd 100644
--- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
@@ -1025,7 +1025,7 @@ def test_update_items_for_subcontracting_purchase_order(self):
)
def update_items(po, qty):
- trans_items = [po.items[0].as_dict()]
+ trans_items = [po.items[0].as_dict().update({"docname": po.items[0].name})]
trans_items[0]["qty"] = qty
trans_items[0]["fg_item_qty"] = qty
trans_items = json.dumps(trans_items, default=str)
@@ -1080,6 +1080,73 @@ def update_items(po, qty):
self.assertEqual(po.items[0].qty, 30)
self.assertEqual(po.items[0].fg_item_qty, 30)
+ def test_new_sc_flow(self):
+ from erpnext.buying.doctype.purchase_order.purchase_order import make_subcontracting_order
+
+ po = create_po_for_sc_testing()
+ sco = make_subcontracting_order(po.name)
+
+ sco.items[0].qty = 5
+ sco.items.pop(1)
+ sco.items[1].qty = 25
+ sco.save()
+ sco.submit()
+
+ # Test - 1: Quantity of Service Items should change based on change in Quantity of its corresponding Finished Goods Item
+ self.assertEqual(sco.service_items[0].qty, 5)
+
+ # Test - 2: Subcontracted Quantity for the PO Items of each line item should be updated accordingly
+ po.reload()
+ self.assertEqual(po.items[0].sco_qty, 5)
+ self.assertEqual(po.items[1].sco_qty, 0)
+ self.assertEqual(po.items[2].sco_qty, 12.5)
+
+ # Test - 3: Amount for both FG Item and its Service Item should be updated correctly based on change in Quantity
+ self.assertEqual(sco.items[0].amount, 2000)
+ self.assertEqual(sco.service_items[0].amount, 500)
+
+ # Test - 4: Service Items should be removed if its corresponding Finished Good line item is deleted
+ self.assertEqual(len(sco.service_items), 2)
+
+ # Test - 5: Service Item quantity calculation should be based upon conversion factor calculated from its corresponding PO Item
+ self.assertEqual(sco.service_items[1].qty, 12.5)
+
+ sco = make_subcontracting_order(po.name)
+
+ sco.items[0].qty = 6
+
+ # Test - 6: Saving document should not be allowed if Quantity exceeds available Subcontracting Quantity of any Purchase Order Item
+ self.assertRaises(frappe.ValidationError, sco.save)
+
+ sco.items[0].qty = 5
+ sco.items.pop()
+ sco.items.pop()
+ sco.save()
+ sco.submit()
+
+ sco = make_subcontracting_order(po.name)
+
+ # Test - 7: Since line item 1 is now fully subcontracted, new SCO should by default only have the remaining 2 line items
+ self.assertEqual(len(sco.items), 2)
+
+ sco.items.pop(0)
+ sco.save()
+ sco.submit()
+
+ # Test - 8: Subcontracted Quantity for each PO Item should be subtracted if SCO gets cancelled
+ po.reload()
+ self.assertEqual(po.items[2].sco_qty, 25)
+ sco.cancel()
+ po.reload()
+ self.assertEqual(po.items[2].sco_qty, 12.5)
+
+ sco = make_subcontracting_order(po.name)
+ sco.save()
+ sco.submit()
+
+ # Test - 8: Since this PO is now fully subcontracted, creating a new SCO from it should throw error
+ self.assertRaises(frappe.ValidationError, make_subcontracting_order, po.name)
+
@IntegrationTestCase.change_settings("Buying Settings", {"auto_create_subcontracting_order": 1})
def test_auto_create_subcontracting_order(self):
from erpnext.controllers.tests.test_subcontracting_controller import (
@@ -1173,6 +1240,53 @@ def test_po_billed_amount_against_return_entry(self):
self.assertEqual(po.per_billed, 100)
+def create_po_for_sc_testing():
+ from erpnext.controllers.tests.test_subcontracting_controller import (
+ make_bom_for_subcontracted_items,
+ make_raw_materials,
+ make_service_items,
+ make_subcontracted_items,
+ )
+
+ make_subcontracted_items()
+ make_raw_materials()
+ make_service_items()
+ make_bom_for_subcontracted_items()
+
+ service_items = [
+ {
+ "warehouse": "_Test Warehouse - _TC",
+ "item_code": "Subcontracted Service Item 1",
+ "qty": 10,
+ "rate": 100,
+ "fg_item": "Subcontracted Item SA1",
+ "fg_item_qty": 10,
+ },
+ {
+ "warehouse": "_Test Warehouse - _TC",
+ "item_code": "Subcontracted Service Item 2",
+ "qty": 20,
+ "rate": 25,
+ "fg_item": "Subcontracted Item SA2",
+ "fg_item_qty": 15,
+ },
+ {
+ "warehouse": "_Test Warehouse - _TC",
+ "item_code": "Subcontracted Service Item 3",
+ "qty": 25,
+ "rate": 10,
+ "fg_item": "Subcontracted Item SA3",
+ "fg_item_qty": 50,
+ },
+ ]
+
+ return create_purchase_order(
+ rm_items=service_items,
+ is_subcontracted=1,
+ supplier_warehouse="_Test Warehouse 1 - _TC",
+ )
+
+
def prepare_data_for_internal_transfer():
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
from erpnext.selling.doctype.customer.test_customer import create_internal_customer
diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
index c2f9f9efbb25..66d4d024eb96 100644
--- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
+++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
@@ -1,7 +1,7 @@
{
"actions": [],
"autoname": "hash",
- "creation": "2013-05-24 19:29:06",
+ "creation": "2024-12-09 12:54:24.652161",
"doctype": "DocType",
"document_type": "Document",
"editable_grid": 1,
@@ -26,6 +26,7 @@
"quantity_and_rate",
"qty",
"stock_uom",
+ "sco_qty",
"col_break2",
"uom",
"conversion_factor",
@@ -929,13 +930,21 @@
"fieldtype": "Currency",
"label": "Distributed Discount Amount",
"options": "currency"
+ },
+ {
+ "allow_on_submit": 1,
+ "fieldname": "sco_qty",
+ "fieldtype": "Float",
+ "label": "Subcontracted Quantity",
+ "no_copy": 1,
+ "read_only": 1
}
],
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2024-06-02 06:20:10.508290",
+ "modified": "2024-12-10 12:11:18.536089",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order Item",
diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.py b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.py
index 79c66c3a30e9..9d65e6bbc1de 100644
--- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.py
+++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.py
@@ -52,6 +52,7 @@ class PurchaseOrderItem(Document):
item_name: DF.Data
item_tax_rate: DF.Code | None
item_tax_template: DF.Link | None
+ job_card: DF.Link | None
last_purchase_rate: DF.Currency
manufacturer: DF.Link | None
manufacturer_part_no: DF.Data | None
@@ -81,6 +82,7 @@ class PurchaseOrderItem(Document):
sales_order_item: DF.Data | None
sales_order_packed_item: DF.Data | None
schedule_date: DF.Date
+ sco_qty: DF.Float
stock_qty: DF.Float
stock_uom: DF.Link
stock_uom_rate: DF.Currency
diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py
index 15a13cb90fdd..51840d499895 100644
--- a/erpnext/controllers/subcontracting_controller.py
+++ b/erpnext/controllers/subcontracting_controller.py
@@ -103,6 +103,19 @@ def validate_items(self):
_("Row {0}: Item {1} must be a subcontracted item.").format(item.idx, item.item_name)
)
+ if (
+ self.doctype not in "Subcontracting Receipt"
+ and item.qty
+ > flt(get_pending_sco_qty(self.purchase_order).get(item.purchase_order_item))
+ / item.sc_conversion_factor
+ ):
+ frappe.throw(
+ _(
+ "Row {0}: Item {1}'s quantity cannot be higher than the available quantity."
+ ).format(item.idx, item.item_name)
+ )
+ item.amount = item.qty * item.rate
+
if item.bom:
is_active, bom_item = frappe.get_value("BOM", item.bom, ["is_active", "item"])
@@ -1116,6 +1129,12 @@ def get_item_details(items):
return item_details
+def get_pending_sco_qty(po_name):
+ table = frappe.qb.DocType("Purchase Order Item")
+ query = frappe.qb.from_(table).select(table.name, table.qty, table.sco_qty).where(table.parent == po_name)
+ return {item.name: item.qty - item.sco_qty for item in query.run(as_dict=True)}
+
+
@frappe.whitelist()
def make_rm_stock_entry(
subcontract_order, rm_items=None, order_doctype="Subcontracting Order", target_doc=None
diff --git a/erpnext/controllers/tests/test_subcontracting_controller.py b/erpnext/controllers/tests/test_subcontracting_controller.py
index 6530ee9a52f4..d9f019e211a8 100644
--- a/erpnext/controllers/tests/test_subcontracting_controller.py
+++ b/erpnext/controllers/tests/test_subcontracting_controller.py
@@ -1261,6 +1261,7 @@ def make_raw_materials():
for item, properties in raw_materials.items():
if not frappe.db.exists("Item", item):
properties.update({"is_stock_item": 1})
+ properties.update({"valuation_rate": 100})
make_item(item, properties)
@@ -1311,7 +1312,7 @@ def make_bom_for_subcontracted_items():
for item_code, raw_materials in boms.items():
if not frappe.db.exists("BOM", {"item": item_code}):
- make_bom(item=item_code, raw_materials=raw_materials, rate=100)
+ make_bom(item=item_code, raw_materials=raw_materials, rate=100, currency="INR")
def set_backflush_based_on(based_on):
diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js
index c6ee3a3e0e35..e9513a47597d 100644
--- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js
+++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js
@@ -5,10 +5,38 @@ frappe.provide("erpnext.buying");
erpnext.landed_cost_taxes_and_charges.setup_triggers("Subcontracting Order");
+// client script for Subcontracting Order Item is not necessarily required as the server side code will do everything that is necessary.
+// this is just so that the user does not get potentially confused
+frappe.ui.form.on("Subcontracting Order Item", {
+ qty(frm, cdt, cdn) {
+ const row = locals[cdt][cdn];
+ frappe.model.set_value(cdt, cdn, "amount", row.qty * row.rate);
+ const service_item = frm.doc.service_items[row.idx - 1];
+ frappe.model.set_value(
+ service_item.doctype,
+ service_item.name,
+ "qty",
+ row.qty * row.sc_conversion_factor
+ );
+ frappe.model.set_value(service_item.doctype, service_item.name, "fg_item_qty", row.qty);
+ frappe.model.set_value(
+ service_item.doctype,
+ service_item.name,
+ "amount",
+ row.qty * row.sc_conversion_factor * service_item.rate
+ );
+ },
+ before_items_remove(frm, cdt, cdn) {
+ const row = locals[cdt][cdn];
+ frm.toggle_enable(["service_items"], true);
+ frm.get_field("service_items").grid.grid_rows[row.idx - 1].remove();
+ frm.toggle_enable(["service_items"], false);
+ },
+});
+
frappe.ui.form.on("Subcontracting Order", {
setup: (frm) => {
frm.get_field("items").grid.cannot_add_rows = true;
- frm.get_field("items").grid.only_sortable();
frm.trigger("set_queries");
frm.set_indicator_formatter("item_code", (doc) => (doc.qty <= doc.received_qty ? "green" : "orange"));
diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py
index 05c0c73b7cca..dee13195cfdc 100644
--- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py
+++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py
@@ -6,7 +6,6 @@
from frappe.model.mapper import get_mapped_doc
from frappe.utils import flt
-from erpnext.buying.doctype.purchase_order.purchase_order import is_subcontracting_order_created
from erpnext.buying.utils import check_on_hold_or_closed_status
from erpnext.controllers.subcontracting_controller import SubcontractingController
from erpnext.stock.stock_balance import update_bin_qty
@@ -120,20 +119,15 @@ def validate(self):
def on_submit(self):
self.update_prevdoc_status()
self.update_status()
+ self.update_sco_qty_in_po()
def on_cancel(self):
self.update_prevdoc_status()
self.update_status()
+ self.update_sco_qty_in_po(cancel=True)
def validate_purchase_order_for_subcontracting(self):
if self.purchase_order:
- if is_subcontracting_order_created(self.purchase_order):
- frappe.throw(
- _(
- "Only one Subcontracting Order can be created against a Purchase Order, cancel the existing Subcontracting Order to create a new one."
- )
- )
-
po = frappe.get_doc("Purchase Order", self.purchase_order)
if not po.is_subcontracted:
@@ -154,10 +148,23 @@ def validate_purchase_order_for_subcontracting(self):
frappe.throw(_("Please select a Subcontracting Purchase Order."))
def validate_service_items(self):
- for item in self.service_items:
- if frappe.get_value("Item", item.item_code, "is_stock_item"):
- msg = f"Service Item {item.item_name} must be a non-stock item."
- frappe.throw(_(msg))
+ purchase_order_items = [item.purchase_order_item for item in self.items]
+ self.service_items = [
+ service_item
+ for service_item in self.service_items
+ if service_item.purchase_order_item in purchase_order_items
+ ]
+
+ for service_item in self.service_items:
+ if frappe.get_value("Item", service_item.item_code, "is_stock_item"):
+ frappe.throw(_("Service Item {0} must be a non-stock item.").format(service_item.item_code))
+
+ item = next(
+ item for item in self.items if item.purchase_order_item == service_item.purchase_order_item
+ )
+ service_item.qty = item.qty * item.sc_conversion_factor
+ service_item.fg_item_qty = item.qty
+ service_item.amount = service_item.qty * service_item.rate
def validate_supplied_items(self):
if self.supplier_warehouse:
@@ -241,6 +248,18 @@ def populate_items_table(self):
for si in self.service_items:
if si.fg_item:
item = frappe.get_doc("Item", si.fg_item)
+
+ po_item = frappe.get_doc("Purchase Order Item", si.purchase_order_item)
+ available_qty = po_item.qty - po_item.sco_qty
+
+ if available_qty == 0:
+ continue
+
+ si.qty = available_qty
+ conversion_factor = po_item.qty / po_item.fg_item_qty
+ si.fg_item_qty = available_qty / conversion_factor
+ si.amount = available_qty * si.rate
+
bom = (
frappe.db.get_value(
"Subcontracting BOM",
@@ -257,6 +276,7 @@ def populate_items_table(self):
"schedule_date": self.schedule_date,
"description": item.description,
"qty": si.fg_item_qty,
+ "sc_conversion_factor": conversion_factor,
"stock_uom": item.stock_uom,
"bom": bom,
"purchase_order_item": si.purchase_order_item,
@@ -310,6 +330,12 @@ def update_status(self, status=None, update_modified=True):
self.update_ordered_qty_for_subcontracting()
self.update_reserved_qty_for_subcontracting()
+ def update_sco_qty_in_po(self, cancel=False):
+ for service_item in self.service_items:
+ doc = frappe.get_doc("Purchase Order Item", service_item.purchase_order_item)
+ doc.sco_qty = (doc.sco_qty + service_item.qty) if not cancel else (doc.sco_qty - service_item.qty)
+ doc.save()
+
@frappe.whitelist()
def make_subcontracting_receipt(source_name, target_doc=None):
diff --git a/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py
index 69e53d9bb011..f52b03a3b751 100644
--- a/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py
+++ b/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py
@@ -49,12 +49,6 @@ def setUp(self):
make_service_items()
make_bom_for_subcontracted_items()
- def test_populate_items_table(self):
- sco = get_subcontracting_order()
- sco.items = None
- sco.populate_items_table()
- self.assertEqual(len(sco.service_items), len(sco.items))
-
def test_set_missing_values(self):
sco = get_subcontracting_order()
before = {sco.total_qty, sco.total, sco.total_additional_costs}
diff --git a/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json b/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json
index 45a93fea4269..25cc525018f2 100644
--- a/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json
+++ b/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json
@@ -54,7 +54,8 @@
"column_break_nfod",
"section_break_34",
"purchase_order_item",
- "page_break"
+ "page_break",
+ "sc_conversion_factor"
],
"fields": [
{
@@ -147,8 +148,8 @@
"fieldtype": "Float",
"in_list_view": 1,
"label": "Quantity",
+ "non_negative": 1,
"print_width": "60px",
- "read_only": 1,
"reqd": 1,
"width": "60px"
},
@@ -400,13 +401,20 @@
{
"fieldname": "column_break_nfod",
"fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "sc_conversion_factor",
+ "fieldtype": "Float",
+ "hidden": 1,
+ "label": "SC Conversion Factor",
+ "read_only": 1
}
],
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2024-12-06 15:23:05.252346",
+ "modified": "2024-12-13 13:35:28.935898",
"modified_by": "Administrator",
"module": "Subcontracting",
"name": "Subcontracting Order Item",
diff --git a/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.py b/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.py
index 7a426f91cb00..d8f2e5664e73 100644
--- a/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.py
+++ b/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.py
@@ -42,6 +42,7 @@ class SubcontractingOrderItem(Document):
received_qty: DF.Float
returned_qty: DF.Float
rm_cost_per_qty: DF.Currency
+ sc_conversion_factor: DF.Float
schedule_date: DF.Date | None
service_cost_per_qty: DF.Currency
stock_uom: DF.Link
diff --git a/erpnext/subcontracting/doctype/subcontracting_order_service_item/subcontracting_order_service_item.json b/erpnext/subcontracting/doctype/subcontracting_order_service_item/subcontracting_order_service_item.json
index 0a74c2f8869a..5663fd529f2c 100644
--- a/erpnext/subcontracting/doctype/subcontracting_order_service_item/subcontracting_order_service_item.json
+++ b/erpnext/subcontracting/doctype/subcontracting_order_service_item/subcontracting_order_service_item.json
@@ -155,7 +155,7 @@
],
"istable": 1,
"links": [],
- "modified": "2024-03-27 13:10:46.534662",
+ "modified": "2024-12-05 17:33:46.099601",
"modified_by": "Administrator",
"module": "Subcontracting",
"name": "Subcontracting Order Service Item",
diff --git a/erpnext/subcontracting/doctype/subcontracting_order_service_item/subcontracting_order_service_item.py b/erpnext/subcontracting/doctype/subcontracting_order_service_item/subcontracting_order_service_item.py
index cc4901baf45c..661a2b2702ce 100644
--- a/erpnext/subcontracting/doctype/subcontracting_order_service_item/subcontracting_order_service_item.py
+++ b/erpnext/subcontracting/doctype/subcontracting_order_service_item/subcontracting_order_service_item.py
@@ -19,6 +19,8 @@ class SubcontractingOrderServiceItem(Document):
fg_item_qty: DF.Float
item_code: DF.Link
item_name: DF.Data
+ material_request: DF.Link | None
+ material_request_item: DF.Data | None
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data