# Subcontracting Order Workflow in ERPNext: Managing Supplied Materials from PO to Receipt

> Master ERPNext subcontracting orders. Automate your workflow from PO to receipt, manage supplied materials, and track stock reservations effectively for seamless operations.

- Repository: [Frappe/erpnext](https://github.com/frappe/erpnext)
- Tags: how-to-guide
- Published: 2026-05-20

---

**The Subcontracting Order workflow in ERPNext automates the four-phase process of creating orders from Purchase Orders, reserving raw materials via Stock Reservation Entries, receiving finished goods through Subcontracting Receipts, and updating order status based on material consumption and receipt quantities.**

The **Subcontracting Order (SCO)** in **frappe/erpnext** is the central document that links Purchase Orders to external manufacturers. It tracks raw materials—called **supplied items**—that you provide to subcontractors while monitoring the service items received back. Understanding this workflow and how to manage supplied materials ensures accurate inventory valuation and prevents stock discrepancies during outsourced production.

## Creating a Subcontracting Order from a Purchase Order

The workflow begins when you convert a subcontracted Purchase Order into a Subcontracting Order. In [`erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py`](https://github.com/frappe/erpnext/blob/main/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py), the `validate_purchase_order_for_subcontracting()` method ensures the selected PO has `is_subcontracted = 1`, is submitted, and not fully received.

The `populate_items_table()` method copies service items from the PO into the SCO's **Items** table, calculating conversion factors and default Bills of Materials (BOMs). Simultaneously, the `set_missing_values()` method runs helpers like `calculate_service_costs` and `calculate_supplied_items_qty_and_amount` to populate rates and totals.

Key fields initialized during creation include:

- `purchase_order` – Link to the originating PO
- `supplied_items` – Child table of type `Subcontracting Order Supplied Item`
- `reserve_stock` – Boolean flag triggering automatic reservation

When you submit the SCO, the system calls `reserve_raw_materials()` (if `reserve_stock` is checked) and updates the PO's `subcontracted_qty` via `update_subcontracted_quantity_in_po()`.

## Reserving Raw Materials for Subcontractors

When **Reserve Stock** is enabled, submitting an SCO triggers the `reserve_raw_materials()` method in [`subcontracting_order.py`](https://github.com/frappe/erpnext/blob/main/subcontracting_order.py). This creates **Stock Reservation Entry** records in `tabStock Reservation Entry` for each item in the **Supplied Items** table:

```python
for item in self.supplied_items:
    data = frappe._dict({
        "voucher_no": self.name,
        "voucher_type": self.doctype,
        "voucher_detail_no": item.name,
        "item_code": item.rm_item_code,
        "warehouse": item.reserve_warehouse,
        "stock_qty": item.required_qty,
    })
    reservation_items.append(data)

```

These reservations guarantee that the required raw materials remain available in your warehouse until physically transferred to the subcontractor. The reservation is later consumed by the **Subcontracting Receipt** to reflect actual material usage.

## Receiving Finished Goods via Subcontracting Receipt

The **Subcontracting Receipt (SCR)** records the return of finished goods and consumes the supplied raw materials. Located in [`erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py`](https://github.com/frappe/erpnext/blob/main/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py), this document performs several critical functions on submission:

1. **Validation**: The `validate()` method checks posting dates, inspection requirements, and ensures the linked SCO remains open.

2. **Consumption**: The `set_consumed_qty_in_subcontract_order()` method updates the `supplied_qty` field in each `Subcontracting Order Supplied Item` row, deducting the consumed amount from reservations.

3. **Ledger Updates**: The `make_gl_entries()` and `update_stock_ledger()` methods generate financial transactions and adjust stock balances for both the consumed raw materials and received finished goods.

4. **Status Propagation**: The receipt triggers `update_status()` on the parent SCO to re-evaluate whether the order is partially or fully received.

## Automatic Status Management and Transitions

The SCO status updates automatically through the `update_status()` method in [`subcontracting_order.py`](https://github.com/frappe/erpnext/blob/main/subcontracting_order.py). The logic evaluates both receipt percentages and material transfer status:

```python
if self.docstatus == 1:
    if self.per_received >= 100:
        status = "Completed"
    elif 0 < self.per_received < 100:
        status = "Partially Received"
    else:
        # Check material transfer status

        total_required_qty = sum(item.required_qty for item in self.supplied_items)
        total_supplied_qty = sum(item.supplied_qty for item in self.supplied_items)
        
        if total_supplied_qty >= total_required_qty:
            status = "Material Transferred"
        elif total_supplied_qty > 0:
            status = "Partial Material Transferred"
        else:
            status = "Open"

```

**Status flow**: `Draft` → `Open` → (`Partial Material Transferred` / `Material Transferred`) → `Partially Received` → `Completed`.

This status controls whether the linked Purchase Order can be modified, protecting against changes that would invalidate material allocations through validations in [`accounts_controller.py`](https://github.com/frappe/erpnext/blob/main/accounts_controller.py).

## Managing Supplied Materials: Validation and Reporting

Each row in the **Supplied Items** table represents a `Subcontracting Order Supplied Item` document requiring specific fields: `rm_item_code`, `required_qty`, `reserve_warehouse`, and `supplier_warehouse`.

The `validate_supplied_items()` method in [`subcontracting_order.py`](https://github.com/frappe/erpnext/blob/main/subcontracting_order.py) enforces that reserve and supplier warehouses differ:

```python
def validate_supplied_items(self):
    if self.supplier_warehouse:
        for item in self.supplied_items:
            if self.supplier_warehouse == item.reserve_warehouse:
                frappe.throw(_("Reserve Warehouse must be different from Supplier Warehouse"))

```

For monitoring material balances, the **Subcontract Order Summary** report ([`erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.py`](https://github.com/frappe/erpnext/blob/main/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.py)) displays required versus supplied quantities per item. Query status programmatically:

```python
sco = frappe.get_doc("Subcontracting Order", "SCO-00015")
for si in sco.supplied_items:
    print(f"{si.rm_item_code}: required {si.required_qty}, supplied {si.supplied_qty}")

```

## Practical Implementation Examples

### Create a Subcontracting Order via REST API

```python
import frappe

sco = frappe.get_doc({
    "doctype": "Subcontracting Order",
    "purchase_order": "PO-00023",
    "supplier": "Supplier XYZ",
    "supplier_warehouse": "Supplier WH",
    "reserve_stock": 1,
    "supplied_items": [
        {
            "rm_item_code": "RAW-STEEL-01",
            "required_qty": 500,
            "reserve_warehouse": "Raw Materials WH"
        },
        {
            "rm_item_code": "RAW-PAINT-02",
            "required_qty": 200,
            "reserve_warehouse": "Paint WH"
        },
    ],
}).insert()
sco.submit()  # Triggers reserve_raw_materials()

```

### Record a Subcontracting Receipt

```python
receipt = frappe.get_doc({
    "doctype": "Subcontracting Receipt",
    "subcontracting_order": "SCO-00015",
    "supplier": "Supplier XYZ",
    "posting_date": frappe.utils.today(),
    "items": [
        {
            "item_code": "FINISHED-GOOD-01",
            "qty": 250,
            "rate": 150,
        },
    ],
    "supplied_items": [
        {
            "rm_item_code": "RAW-STEEL-01",
            "supplied_qty": 250,
            "warehouse": "Raw Materials WH",
        },
    ],
}).insert()
receipt.submit()  # Consumes reserved stock and updates SCO status

```

### Manual Reservation When reserve_stock Was Unchecked

```python
sco = frappe.get_doc("Subcontracting Order", "SCO-00015")
sco.reserve_raw_materials()
frappe.db.commit()

```

## Summary

- **Subcontracting Orders** bridge Purchase Orders and external manufacturers, tracking both service items and raw material supplies through the [`subcontracting_order.py`](https://github.com/frappe/erpnext/blob/main/subcontracting_order.py) controller.
- The **four-phase workflow** comprises: Creation (from PO), Reservation (`reserve_raw_materials` creating Stock Reservation Entries), Receipt (Subcontracting Receipt consumption), and Status Closure (`update_status`).
- **Stock Reservation Entries** created in `tabStock Reservation Entry` ensure raw materials remain allocated until physically consumed by receipts.
- **Status transitions** depend on both finished goods receipt percentages (`per_received`) and supplied material quantities (`supplied_qty` vs `required_qty`), preventing premature closure.
- Key implementation files include [`erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py`](https://github.com/frappe/erpnext/blob/main/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py), [`subcontracting_receipt.py`](https://github.com/frappe/erpnext/blob/main/subcontracting_receipt.py), and [`erpnext/controllers/subcontracting_controller.py`](https://github.com/frappe/erpnext/blob/main/erpnext/controllers/subcontracting_controller.py).

## Frequently Asked Questions

### How do I link a Subcontracting Order to an existing Purchase Order?

Set the `purchase_order` field when creating the SCO. The system validates the link through `validate_purchase_order_for_subcontracting()`, ensuring the PO has `is_subcontracted = 1` and is not fully received. Upon SCO submission, the method `update_subcontracted_quantity_in_po()` automatically updates the subcontracted quantity on the PO to maintain synchronization.

### What happens if I don't enable Reserve Stock when creating an SCO?

If `reserve_stock` is unchecked, the system skips the automatic `reserve_raw_materials()` call during submission. You must manually trigger reservations later by calling `sco.reserve_raw_materials()` or manage material transfers through separate Stock Entry documents. Without reservations, no `Stock Reservation Entry` records are created in `tabStock Reservation Entry`, risking stock allocation conflicts.

### How does ERPNext prevent over-consumption of supplied materials?

The `set_consumed_qty_in_subcontract_order()` method in [`subcontracting_receipt.py`](https://github.com/frappe/erpnext/blob/main/subcontracting_receipt.py) validates consumption against the `required_qty` specified in the SCO's **Supplied Items** table. Additionally, warehouse validation in `validate_supplied_items()` ensures you cannot reserve and consume from the same supplier warehouse, maintaining separation between your stock and the subcontractor's location.

### Can I modify a Purchase Order after creating a Subcontracting Order?

No. The system blocks PO modifications through validations in [`accounts_controller.py`](https://github.com/frappe/erpnext/blob/main/accounts_controller.py) once a Subcontracting Order exists. This prevents discrepancies between ordered quantities and reserved materials. To make changes, you must cancel or amend the SCO first, which restores the ability to edit the PO.