Merge pull request #50189 from mihir-kandoi/inward-fix

fix: feat: multiple changes related to subcontracting inward
This commit is contained in:
Mihir Kandoi
2025-10-22 00:22:34 +05:30
committed by GitHub
16 changed files with 525 additions and 168 deletions

View File

@@ -1297,7 +1297,7 @@ class SalesInvoice(SellingController):
item.idx, item.idx,
item.stock_qty, item.stock_qty,
item.stock_uom, item.stock_uom,
frappe.bold(item.item_code), get_link_to_form("Item", item.item_code),
frappe.bold(max_qty), frappe.bold(max_qty),
) )
) )

View File

@@ -3939,8 +3939,8 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
if parent.is_subcontracted and not parent.can_update_items(): if parent.is_subcontracted and not parent.can_update_items():
frappe.throw( frappe.throw(
_( _(
"Items cannot be updated as Subcontracting Inward Order is created against the Sales Order {0}." "Items cannot be updated as Subcontracting Inward Order(s) exist against this Subcontracted Sales Order."
).format(frappe.bold(parent.name)) )
) )
parent.validate_selling_price() parent.validate_selling_price()
parent.validate_for_duplicate_items() parent.validate_for_duplicate_items()

View File

@@ -156,7 +156,7 @@ class SubcontractingController(StockController):
frappe.throw( frappe.throw(
_( _(
"Row {0}: Delivery Warehouse cannot be same as Customer Warehouse for Item {1}." "Row {0}: Delivery Warehouse cannot be same as Customer Warehouse for Item {1}."
).format(item.idx, frappe.bold(item.item_name)) ).format(item.idx, get_link_to_form("Item", item.item_code))
) )
if not item.get("is_scrap_item"): if not item.get("is_scrap_item"):
@@ -664,6 +664,8 @@ class SubcontractingController(StockController):
def __add_supplied_or_received_item(self, item_row, bom_item, qty): def __add_supplied_or_received_item(self, item_row, bom_item, qty):
bom_item.conversion_factor = item_row.conversion_factor bom_item.conversion_factor = item_row.conversion_factor
if self.subcontract_data.order_doctype == "Subcontracting Inward Order":
bom_item.pop("rate")
rm_obj = self.append(self.raw_material_table, bom_item) rm_obj = self.append(self.raw_material_table, bom_item)
if rm_obj.get("qty"): if rm_obj.get("qty"):
# Qty field not exists # Qty field not exists

View File

@@ -9,7 +9,7 @@ from erpnext.stock.serial_batch_bundle import get_serial_batch_list_from_item
class SubcontractingInwardController: class SubcontractingInwardController:
def validate_subcontracting_inward(self): def validate_subcontracting_inward(self):
self.validate_inward_order() self.validate_inward_order()
self.validate_customer_provided_item_for_inward() self.set_allow_zero_valuation_rate()
self.validate_warehouse_() self.validate_warehouse_()
self.validate_serial_batch_for_return_or_delivery() self.validate_serial_batch_for_return_or_delivery()
self.validate_delivery() self.validate_delivery()
@@ -50,11 +50,22 @@ class SubcontractingInwardController:
self.validate_material_receipt() self.validate_material_receipt()
case purpose if purpose in ["Return Raw Material to Customer", "Subcontracting Return"]: case purpose if purpose in ["Return Raw Material to Customer", "Subcontracting Return"]:
self.validate_returns() self.validate_returns()
case "Material Transfer for Manufacture":
self.validate_material_transfer()
case "Manufacture": case "Manufacture":
self.validate_manufacture() self.validate_manufacture()
def validate_material_receipt(self): def validate_material_receipt(self):
rm_item_fg_combo = []
for item in self.items: for item in self.items:
if not frappe.get_cached_value("Item", item.item_code, "is_customer_provided_item"):
frappe.throw(
_("Row #{0}: Item {1} is not a Customer Provided Item.").format(
item.idx,
get_link_to_form("Item", item.item_code),
)
)
if ( if (
item.scio_detail item.scio_detail
and frappe.get_cached_value( and frappe.get_cached_value(
@@ -65,17 +76,44 @@ class SubcontractingInwardController:
frappe.throw( frappe.throw(
_( _(
"Row #{0}: Item {1} mismatch. Changing of item code is not permitted, add another row instead." "Row #{0}: Item {1} mismatch. Changing of item code is not permitted, add another row instead."
).format(item.idx, bold(item.item_code)) ).format(item.idx, get_link_to_form("Item", item.item_code))
) )
if not item.scio_detail: # item is additional
if item.against_fg:
if (item.item_code, item.against_fg) not in rm_item_fg_combo:
rm_item_fg_combo.append((item.item_code, item.against_fg))
else:
frappe.throw(
_(
"Row #{0}: Customer Provided Item {1} against Subcontracting Inward Order Item {2} ({3}) cannot be added multiple times."
).format(
item.idx,
get_link_to_form("Item", item.item_code),
bold(item.against_fg),
get_link_to_form(
"Item",
frappe.get_cached_value(
"Subcontracting Inward Order Item", item.against_fg, "item_code"
),
),
)
)
else:
frappe.throw(
_(
"Row #{0}: Please select the Finished Good Item against which this Customer Provided Item will be used."
).format(item.idx)
)
def validate_returns(self): def validate_returns(self):
for item in self.items: for item in self.items:
if not item.scio_detail: if not item.scio_detail:
frappe.throw( frappe.throw(
_("Row #{0}: Item {1} is not a part of Subcontracting Inward Order {2}").format( _("Row #{0}: Item {1} is not a part of Subcontracting Inward Order {2}").format(
item.idx, item.idx,
bold(item.item_code), get_link_to_form("Item", item.item_code),
bold(self.subcontracting_inward_order), get_link_to_form("Subcontracting Inward Order", self.subcontracting_inward_order),
) )
) )
elif item.item_code != ( elif item.item_code != (
@@ -86,7 +124,7 @@ class SubcontractingInwardController:
): ):
frappe.throw( frappe.throw(
_("Row #{0}: Item {1} mismatch. Changing of item code is not permitted.").format( _("Row #{0}: Item {1} mismatch. Changing of item code is not permitted.").format(
item.idx, bold(item.item_code) item.idx, get_link_to_form("Item", item.item_code)
) )
) )
@@ -101,7 +139,7 @@ class SubcontractingInwardController:
frappe.throw( frappe.throw(
_( _(
"Row #{0}: Returned quantity cannot be greater than available quantity for Item {1}" "Row #{0}: Returned quantity cannot be greater than available quantity for Item {1}"
).format(item.idx, bold(item.item_code)) ).format(item.idx, get_link_to_form("Item", item.item_code))
) )
else: else:
data = frappe.get_value( data = frappe.get_value(
@@ -114,16 +152,88 @@ class SubcontractingInwardController:
frappe.throw( frappe.throw(
_( _(
"Row #{0}: Returned quantity cannot be greater than available quantity to return for Item {1}" "Row #{0}: Returned quantity cannot be greater than available quantity to return for Item {1}"
).format(item.idx, bold(item.item_code)) ).format(item.idx, get_link_to_form("Item", item.item_code))
)
def validate_material_transfer(self):
customer_warehouse = frappe.get_cached_value(
"Subcontracting Inward Order", self.subcontracting_inward_order, "customer_warehouse"
)
item_codes = []
for item in self.items:
if not frappe.get_cached_value("Item", item.item_code, "is_customer_provided_item"):
continue
elif item.s_warehouse != customer_warehouse:
frappe.throw(
_("Row #{0}: For Customer Provided Item {1}, Source Warehouse must be {2}").format(
item.idx,
get_link_to_form("Item", item.item_code),
get_link_to_form("Warehouse", customer_warehouse),
)
)
elif item.item_code in item_codes:
frappe.throw(
_(
"Row #{0}: Customer Provided Item {1} cannot be added multiple times in the Subcontracting Inward process."
).format(
item.idx,
get_link_to_form("Item", item.item_code),
)
)
else:
work_order_items = frappe.get_all(
"Work Order Item",
{"parent": self.work_order, "docstatus": 1, "is_customer_provided_item": 1},
["item_code", "transferred_qty", "required_qty", "stock_reserved_qty"],
)
wo_item_dict = frappe._dict(
{
wo_item.item_code: frappe._dict(
{
"transferred_qty": wo_item.transferred_qty,
"required_qty": wo_item.required_qty,
"stock_reserved_qty": wo_item.stock_reserved_qty,
}
)
for wo_item in work_order_items
}
)
if wo_item := wo_item_dict.get(item.item_code):
if wo_item.transferred_qty + item.transfer_qty > max(
wo_item.required_qty, wo_item.stock_reserved_qty
):
frappe.throw(
_(
"Row #{0}: Overconsumption of Customer Provided Item {1} against Work Order {2} is not allowed in the Subcontracting Inward process."
).format(
item.idx,
get_link_to_form("Item", item.item_code),
get_link_to_form("Work Order", self.work_order),
)
)
else:
item_codes.append(item.item_code)
else:
frappe.throw(
_("Row #{0}: Customer Provided Item {1} is not a part of Work Order {2}").format(
item.idx,
get_link_to_form("Item", item.item_code),
get_link_to_form("Work Order", self.work_order),
)
) )
def validate_manufacture(self): def validate_manufacture(self):
skip_transfer, customer_warehouse, wip_warehouse = frappe.get_cached_value( if next(item for item in self.items if item.is_finished_item).t_warehouse != (
"Work Order", fg_warehouse := frappe.get_cached_value("Work Order", self.work_order, "fg_warehouse")
self.work_order, ):
["skip_transfer", "source_warehouse", "wip_warehouse"], frappe.throw(
) _(
warehouse = customer_warehouse if skip_transfer else wip_warehouse "Target Warehouse for Finished Good must be same as Finished Good Warehouse {1} in Work Order {2} linked to the Subcontracting Inward Order."
).format(
get_link_to_form("Warehouse", fg_warehouse),
get_link_to_form("Work Order", self.work_order),
)
)
items = [ items = [
item item
@@ -133,74 +243,133 @@ class SubcontractingInwardController:
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")
] ]
table = frappe.qb.DocType("Subcontracting Inward Order Received Item") customer_warehouse = frappe.get_cached_value(
query = ( "Subcontracting Inward Order", self.subcontracting_inward_order, "customer_warehouse"
frappe.qb.from_(table)
.select(
table.rm_item_code,
(table.received_qty - table.returned_qty).as_("total_qty"),
table.consumed_qty,
table.name,
)
.where(
(table.docstatus == 1)
& (table.parent == self.subcontracting_inward_order)
& (table.main_item_code == frappe.get_cached_value("BOM", self.bom_no, "item"))
& (table.warehouse == warehouse)
& (table.rm_item_code.isin([item.item_code for item in items]))
)
) )
rm_item_dict = frappe._dict( if frappe.get_cached_value("Work Order", self.work_order, "skip_transfer"):
{ table = frappe.qb.DocType("Subcontracting Inward Order Received Item")
d.rm_item_code: frappe._dict( query = (
{"name": d.name, "total_qty": d.total_qty, "qty": d.consumed_qty} frappe.qb.from_(table)
.select(
table.rm_item_code,
(table.received_qty - table.returned_qty).as_("total_qty"),
table.consumed_qty,
table.name,
) )
for d in query.run(as_dict=True) .where(
} (table.docstatus == 1)
) & (table.parent == self.subcontracting_inward_order)
& (
for item in items: table.reference_name
if rm := rm_item_dict.get(item.item_code): == frappe.get_cached_value(
if rm.qty + item.transfer_qty > rm.total_qty: "Work Order", self.work_order, "subcontracting_inward_order_item"
frappe.throw(
_(
"Row #{0}: Customer Provided Item {1} exceeds quantity available through Subcontracting Inward Order"
).format(item.idx, bold(item.item_code), item.transfer_qty)
)
elif item.s_warehouse != warehouse:
frappe.throw(
_("Row #{0}: For Customer Provided Item {1}, Source Warehouse must be {2}").format(
item.idx,
bold(item.item_code),
bold(warehouse),
) )
) )
else: & (table.rm_item_code.isin([item.item_code for item in items]))
frappe.throw(
_(
"Row #{0}: Customer Provided Item {1} is not a part of Subcontracting Inward Order {2}"
).format(
item.idx,
bold(item.item_code),
bold(self.subcontracting_inward_order),
)
) )
)
rm_item_dict = frappe._dict(
{
d.rm_item_code: frappe._dict(
{"name": d.name, "total_qty": d.total_qty, "qty": d.consumed_qty}
)
for d in query.run(as_dict=True)
}
)
def validate_customer_provided_item_for_inward(self): item_codes = []
if self.subcontracting_inward_order: for item in items:
if self.purpose in ["Subcontracting Delivery", "Subcontracting Return"]: if rm := rm_item_dict.get(item.item_code):
for item in self.items: if rm.qty + item.transfer_qty > rm.total_qty:
if (item.is_finished_item or item.is_scrap_item) and item.valuation_rate == 0:
item.allow_zero_valuation_rate = 1
elif self.purpose == "Receive from Customer":
for item in self.items:
if not frappe.get_cached_value("Item", item.item_code, "is_customer_provided_item"):
frappe.throw( frappe.throw(
_("Row #{0}: Item {1} is not a customer provided item.").format( _(
"Row #{0}: Customer Provided Item {1} exceeds quantity available through Subcontracting Inward Order"
).format(item.idx, get_link_to_form("Item", item.item_code), item.transfer_qty)
)
elif item.s_warehouse != customer_warehouse:
frappe.throw(
_(
"Row #{0}: For Customer Provided Item {1}, Source Warehouse must be {2}"
).format(
item.idx,
get_link_to_form("Item", item.item_code),
get_link_to_form("Warehouse", customer_warehouse),
)
)
elif item.item_code in item_codes:
frappe.throw(
_(
"Row #{0}: Customer Provided Item {1} cannot be added multiple times in the Subcontracting Inward process."
).format(
item.idx, item.idx,
get_link_to_form("Item", item.item_code), get_link_to_form("Item", item.item_code),
) )
) )
else:
item_codes.append(item.item_code)
else:
frappe.throw(
_(
"Row #{0}: Customer Provided Item {1} is not a part of Subcontracting Inward Order {2}"
).format(
item.idx,
get_link_to_form("Item", item.item_code),
get_link_to_form("Subcontracting Inward Order", self.subcontracting_inward_order),
)
)
else:
work_order_items = frappe.get_all(
"Work Order Item",
{"parent": self.work_order, "docstatus": 1, "is_customer_provided_item": 1},
["item_code", "transferred_qty", "consumed_qty"],
)
wo_item_dict = frappe._dict(
{
wo_item.item_code: frappe._dict(
{"transferred_qty": wo_item.transferred_qty, "consumed_qty": wo_item.consumed_qty}
)
for wo_item in work_order_items
}
)
item_codes = []
for item in items:
if wo_item := wo_item_dict.get(item.item_code):
if wo_item.consumed_qty + item.transfer_qty > wo_item.transferred_qty:
frappe.throw(
_(
"Row #{0}: Overconsumption of Customer Provided Item {1} against Work Order {2} is not allowed in the Subcontracting Inward process."
).format(
item.idx,
get_link_to_form("Item", item.item_code),
get_link_to_form("Work Order", self.work_order),
)
)
elif item.item_code in item_codes:
frappe.throw(
_(
"Row #{0}: Customer Provided Item {1} cannot be added multiple times in the Subcontracting Inward process."
).format(
item.idx,
get_link_to_form("Item", item.item_code),
)
)
else:
item_codes.append(item.item_code)
else:
frappe.throw(
_("Row #{0}: Customer Provided Item {1} is not a part of Work Order {2}").format(
item.idx,
get_link_to_form("Item", item.item_code),
get_link_to_form("Work Order", self.work_order),
)
)
def set_allow_zero_valuation_rate(self):
if self.subcontracting_inward_order:
if self.purpose in ["Subcontracting Delivery", "Subcontracting Return", "Manufacture"]:
for item in self.items:
if (item.is_finished_item or item.is_scrap_item) and item.valuation_rate == 0:
item.allow_zero_valuation_rate = 1
def validate_warehouse_(self): def validate_warehouse_(self):
if self.subcontracting_inward_order and self.purpose in [ if self.subcontracting_inward_order and self.purpose in [
@@ -222,13 +391,13 @@ class SubcontractingInwardController:
frappe.throw( frappe.throw(
_( _(
"Row #{0}: Target Warehouse must be same as Customer Warehouse {1} from the linked Subcontracting Inward Order" "Row #{0}: Target Warehouse must be same as Customer Warehouse {1} from the linked Subcontracting Inward Order"
).format(item.idx, bold(customer_warehouse)) ).format(item.idx, get_link_to_form("Warehouse", customer_warehouse))
) )
else: else:
frappe.throw( frappe.throw(
_( _(
"Row #{0}: Source Warehouse must be same as Customer Warehouse {1} from the linked Subcontracting Inward Order" "Row #{0}: Source Warehouse must be same as Customer Warehouse {1} from the linked Subcontracting Inward Order"
).format(item.idx, bold(customer_warehouse)) ).format(item.idx, get_link_to_form("Warehouse", customer_warehouse))
) )
def validate_serial_batch_for_return_or_delivery(self): def validate_serial_batch_for_return_or_delivery(self):
@@ -249,7 +418,10 @@ class SubcontractingInwardController:
frappe.throw( frappe.throw(
_( _(
"Row #{0}: Serial No(s) {1} are not a part of the linked Subcontracting Inward Order. Please select valid Serial No(s)." "Row #{0}: Serial No(s) {1} are not a part of the linked Subcontracting Inward Order. Please select valid Serial No(s)."
).format(item.idx, ", ".join([bold(sn) for sn in incorrect_serial_nos])) ).format(
item.idx,
", ".join([get_link_to_form("Serial No", sn) for sn in incorrect_serial_nos]),
)
) )
if batch_list and ( if batch_list and (
incorrect_batch_nos := [bn for bn in batch_list if bn not in list(batch_nos.keys())] incorrect_batch_nos := [bn for bn in batch_list if bn not in list(batch_nos.keys())]
@@ -257,7 +429,10 @@ class SubcontractingInwardController:
frappe.throw( frappe.throw(
_( _(
"Row #{0}: Batch No(s) {1} is not a part of the linked Subcontracting Inward Order. Please select valid Batch No(s)." "Row #{0}: Batch No(s) {1} is not a part of the linked Subcontracting Inward Order. Please select valid Batch No(s)."
).format(item.idx, ", ".join([bold(bn) for bn in incorrect_batch_nos])) ).format(
item.idx,
", ".join([get_link_to_form("Batch No", bn) for bn in incorrect_batch_nos]),
)
) )
def get_serial_nos_and_batches_from_sres(self, scio_detail, only_pending=True): def get_serial_nos_and_batches_from_sres(self, scio_detail, only_pending=True):
@@ -302,7 +477,7 @@ class SubcontractingInwardController:
frappe.throw( frappe.throw(
_( _(
"Row #{0}: Cannot cancel this Stock Entry as returned quantity cannot be greater than delivered quantity for Item {1} in the linked Subcontracting Inward Order" "Row #{0}: Cannot cancel this Stock Entry as returned quantity cannot be greater than delivered quantity for Item {1} in the linked Subcontracting Inward Order"
).format(item.idx, bold(item.item_code)) ).format(item.idx, get_link_to_form("Item", item.item_code))
) )
def validate_delivery_on_save(self): def validate_delivery_on_save(self):
@@ -315,8 +490,8 @@ class SubcontractingInwardController:
frappe.throw( frappe.throw(
_("Row #{0}: Item {1} is not a part of Subcontracting Inward Order {2}").format( _("Row #{0}: Item {1} is not a part of Subcontracting Inward Order {2}").format(
item.idx, item.idx,
bold(item.item_code), get_link_to_form("Item", item.item_code),
bold(self.subcontracting_inward_order), get_link_to_form("Subcontracting Inward Order", self.subcontracting_inward_order),
) )
) )
@@ -359,7 +534,7 @@ class SubcontractingInwardController:
"Row #{0}: Quantity of Item {1} cannot be more than {2} {3} against Subcontracting Inward Order {4}" "Row #{0}: Quantity of Item {1} cannot be more than {2} {3} against Subcontracting Inward Order {4}"
).format( ).format(
item.idx, item.idx,
bold(item.item_code), get_link_to_form("Item", item.item_code),
bold(max_allowed_qty), bold(max_allowed_qty),
bold( bold(
frappe.get_cached_value( frappe.get_cached_value(
@@ -370,7 +545,7 @@ class SubcontractingInwardController:
"stock_uom", "stock_uom",
) )
), ),
bold(self.subcontracting_inward_order), get_link_to_form("Subcontracting Inward Order", self.subcontracting_inward_order),
) )
) )
@@ -400,7 +575,6 @@ class SubcontractingInwardController:
& (table.voucher_type == "Subcontracting Inward Order") & (table.voucher_type == "Subcontracting Inward Order")
& (table.voucher_no == self.subcontracting_inward_order) & (table.voucher_no == self.subcontracting_inward_order)
& (table.voucher_detail_no == item.scio_detail) & (table.voucher_detail_no == item.scio_detail)
& (table.warehouse == item.s_warehouse)
) )
.orderby(table.creation) .orderby(table.creation)
) )
@@ -522,7 +696,7 @@ class SubcontractingInwardController:
) < scio_rm_item.work_order_qty: ) < scio_rm_item.work_order_qty:
frappe.throw( frappe.throw(
_("Row #{0}: Work Order exists against full or partial quantity of Item {1}").format( _("Row #{0}: Work Order exists against full or partial quantity of Item {1}").format(
item.idx, bold(item.item_code) item.idx, get_link_to_form("Item", item.item_code)
) )
) )
@@ -561,7 +735,7 @@ class SubcontractingInwardController:
frappe.throw( frappe.throw(
_( _(
"Row #{0}: Cannot cancel this Manufacturing Stock Entry as quantity of Scrap Item {1} produced cannot be less than quantity delivered." "Row #{0}: Cannot cancel this Manufacturing Stock Entry as quantity of Scrap Item {1} produced cannot be less than quantity delivered."
).format(item.idx, bold(item.item_code)) ).format(item.idx, get_link_to_form("Item", item.item_code))
) )
else: else:
scio_rm_item = frappe.get_value( scio_rm_item = frappe.get_value(
@@ -570,7 +744,7 @@ class SubcontractingInwardController:
"docstatus": 1, "docstatus": 1,
"rm_item_code": item.item_code, "rm_item_code": item.item_code,
"warehouse": item.s_warehouse, "warehouse": item.s_warehouse,
"reference_name": fg_item_name, # if this field is set then the additional item is NOT customer provided "is_customer_provided_item": 0,
"is_additional_item": 1, "is_additional_item": 1,
}, },
["consumed_qty", "billed_qty", "returned_qty"], ["consumed_qty", "billed_qty", "returned_qty"],
@@ -582,7 +756,7 @@ class SubcontractingInwardController:
frappe.throw( frappe.throw(
_( _(
"Row #{0}: Cannot cancel this Manufacturing Stock Entry as billed quantity of Item {1} cannot be greater than consumed quantity." "Row #{0}: Cannot cancel this Manufacturing Stock Entry as billed quantity of Item {1} cannot be greater than consumed quantity."
).format(item.idx, bold(item.item_code)) ).format(item.idx, get_link_to_form("Item", item.item_code))
) )
def update_inward_order_item(self): def update_inward_order_item(self):
@@ -638,7 +812,9 @@ class SubcontractingInwardController:
data = frappe._dict() data = frappe._dict()
for item in self.items: for item in self.items:
if item.scio_detail: if item.scio_detail:
data[item.scio_detail] = item.transfer_qty if self._action == "submit" else -item.transfer_qty data[item.scio_detail] = frappe._dict(
{"transfer_qty": item.transfer_qty, "rate": item.customer_provided_item_cost}
)
else: else:
scio_rm = frappe.new_doc( scio_rm = frappe.new_doc(
"Subcontracting Inward Order Received Item", "Subcontracting Inward Order Received Item",
@@ -657,9 +833,16 @@ class SubcontractingInwardController:
consumed_qty=0, consumed_qty=0,
work_order_qty=0, work_order_qty=0,
returned_qty=0, returned_qty=0,
rate=item.customer_provided_item_cost,
is_customer_provided_item=True,
is_additional_item=True, is_additional_item=True,
reference_name=item.against_fg,
main_item_code=frappe.get_cached_value(
"Subcontracting Inward Order Item", item.against_fg, "item_code"
),
) )
scio_rm.insert() scio_rm.insert()
scio_rm.submit()
item.db_set("scio_detail", scio_rm.name) item.db_set("scio_detail", scio_rm.name)
if data: if data:
@@ -670,39 +853,47 @@ class SubcontractingInwardController:
"name": ["in", list(data.keys())], "name": ["in", list(data.keys())],
"docstatus": 1, "docstatus": 1,
}, },
fields=["name", "required_qty", "received_qty"], fields=["rate", "name", "required_qty", "received_qty"],
) )
deleted_docs = [] deleted_docs = []
table = frappe.qb.DocType("Subcontracting Inward Order Received Item") table = frappe.qb.DocType("Subcontracting Inward Order Received Item")
case_expr = Case() case_expr_qty, case_expr_rate = Case(), Case()
for d in result: for d in result:
d.received_qty += data[d.name] d.received_qty += (
data[d.name].transfer_qty if self._action == "submit" else -data[d.name].transfer_qty
)
d.rate += data[d.name].rate if self._action == "submit" else -data[d.name].rate
if not d.required_qty and not d.received_qty: if not d.required_qty and not d.received_qty:
deleted_docs.append(d.name) deleted_docs.append(d.name)
frappe.delete_doc("Subcontracting Inward Order Received Item", d.name) frappe.delete_doc("Subcontracting Inward Order Received Item", d.name)
else: else:
case_expr = case_expr.when(table.name == d.name, d.received_qty) case_expr_qty = case_expr_qty.when(table.name == d.name, d.received_qty)
case_expr_rate = case_expr_rate.when(table.name == d.name, d.rate)
if len(list(set(data.keys()) - set(deleted_docs))) > 0: if final_list := list(set(data.keys()) - set(deleted_docs)):
frappe.qb.update(table).set(table.received_qty, case_expr).where( frappe.qb.update(table).set(table.received_qty, case_expr_qty).set(
(table.name.isin(list(set(data.keys()) - set(deleted_docs)))) & (table.docstatus == 1) table.rate, case_expr_rate
).run() ).where((table.name.isin(final_list)) & (table.docstatus == 1)).run()
def update_inward_order_received_items_for_manufacture(self): def update_inward_order_received_items_for_manufacture(self):
customer_warehouse = frappe.get_cached_value(
"Subcontracting Inward Order", self.subcontracting_inward_order, "customer_warehouse"
)
items = [item for item in self.items if not item.is_finished_item and not item.is_scrap_item] items = [item for item in self.items if not item.is_finished_item and not item.is_scrap_item]
item_code_wh = frappe._dict( item_code_wh = frappe._dict(
{ {
(item.item_code, item.s_warehouse): item.transfer_qty (
if self._action == "submit" item.item_code,
else -item.transfer_qty customer_warehouse
if frappe.get_cached_value("Item", item.item_code, "is_customer_provided_item")
else item.s_warehouse,
): item.transfer_qty if self._action == "submit" else -item.transfer_qty
for item in items for item in items
} }
) )
item_codes, warehouses = zip(*list(item_code_wh.keys()), strict=True) item_codes, warehouses = zip(*list(item_code_wh.keys()), strict=True)
item_codes = list(item_codes)
warehouses = list(warehouses)
table = frappe.qb.DocType("Subcontracting Inward Order Received Item") table = frappe.qb.DocType("Subcontracting Inward Order Received Item")
data = ( data = (
@@ -712,22 +903,21 @@ class SubcontractingInwardController:
table.rm_item_code, table.rm_item_code,
table.is_customer_provided_item, table.is_customer_provided_item,
table.consumed_qty, table.consumed_qty,
table.required_qty,
table.warehouse, table.warehouse,
table.is_additional_item,
) )
.where( .where(
(table.docstatus == 1) (table.docstatus == 1)
& (table.rm_item_code.isin(item_codes)) & (table.rm_item_code.isin(list(set(item_codes))))
& ((table.warehouse.isin(warehouses)) | (table.warehouse.isnull())) & (
(table.warehouse.isin(list(set(warehouses)))) | (table.warehouse.isnull())
) # warehouse will always be null for non additional self procured raw materials
& (table.parent == self.subcontracting_inward_order) & (table.parent == self.subcontracting_inward_order)
& ( & (
( table.reference_name
table.reference_name == frappe.get_cached_value(
== frappe.get_cached_value( "Work Order", self.work_order, "subcontracting_inward_order_item"
"Work Order", self.work_order, "subcontracting_inward_order_item"
)
) )
| (table.reference_name.isnull())
) )
) )
) )
@@ -745,25 +935,26 @@ class SubcontractingInwardController:
used_item_wh.append((d.rm_item_code, d.warehouse)) used_item_wh.append((d.rm_item_code, d.warehouse))
qty = d.consumed_qty + item_code_wh[(d.rm_item_code, d.warehouse)] qty = d.consumed_qty + item_code_wh[(d.rm_item_code, d.warehouse)]
if qty or d.is_customer_provided_item: if qty or d.is_customer_provided_item or not d.is_additional_item:
case_expr = case_expr.when((table.name == d.name), qty) case_expr = case_expr.when((table.name == d.name), qty)
else: else:
deleted_docs.append(d.name) deleted_docs.append(d.name)
frappe.delete_doc("Subcontracting Inward Order Received Item", d.name) frappe.delete_doc("Subcontracting Inward Order Received Item", d.name)
final_name_list = list(set([d.name for d in data]) - set(deleted_docs)) if final_list := list(set([d.name for d in data]) - set(deleted_docs)):
if len(final_name_list) > 0:
frappe.qb.update(table).set(table.consumed_qty, case_expr).where( frappe.qb.update(table).set(table.consumed_qty, case_expr).where(
(table.name.isin(final_name_list)) & (table.docstatus == 1) (table.name.isin(final_list)) & (table.docstatus == 1)
).run() ).run()
main_item_code = next(fg for fg in self.items if fg.is_finished_item).item_code main_item_code = next(fg for fg in self.items if fg.is_finished_item).item_code
for extra_item in [ for extra_item in [
item item
for item in items for item in items
if (item.item_code, item.s_warehouse) not in [(d.rm_item_code, d.warehouse) for d in data] if not frappe.get_cached_value("Item", item.item_code, "is_customer_provided_item")
and (item.item_code, item.s_warehouse)
not in [(d.rm_item_code, d.warehouse) for d in data if not d.is_customer_provided_item]
]: ]:
frappe.new_doc( doc = frappe.new_doc(
"Subcontracting Inward Order Received Item", "Subcontracting Inward Order Received Item",
parent=self.subcontracting_inward_order, parent=self.subcontracting_inward_order,
parenttype="Subcontracting Inward Order", parenttype="Subcontracting Inward Order",
@@ -783,7 +974,9 @@ class SubcontractingInwardController:
consumed_qty=extra_item.transfer_qty, consumed_qty=extra_item.transfer_qty,
warehouse=extra_item.s_warehouse, warehouse=extra_item.s_warehouse,
is_additional_item=True, is_additional_item=True,
).insert() )
doc.insert()
doc.submit()
def update_inward_order_scrap_items(self): def update_inward_order_scrap_items(self):
if (scio := self.subcontracting_inward_order) and self.purpose == "Manufacture": if (scio := self.subcontracting_inward_order) and self.purpose == "Manufacture":
@@ -835,8 +1028,9 @@ class SubcontractingInwardController:
table.name == value.name, value.produced_qty + scrap_items.get(key) table.name == value.name, value.produced_qty + scrap_items.get(key)
) )
final_list = list(set([v.name for v in scrap_item_dict.values()]) - set(deleted_docs)) if final_list := list(
if len(final_list) > 0: set([v.name for v in scrap_item_dict.values()]) - set(deleted_docs)
):
frappe.qb.update(table).set(table.produced_qty, case_expr).where( frappe.qb.update(table).set(table.produced_qty, case_expr).where(
(table.name.isin(final_list)) & (table.docstatus == 1) (table.name.isin(final_list)) & (table.docstatus == 1)
).run() ).run()
@@ -847,7 +1041,7 @@ class SubcontractingInwardController:
for item in scrap_items_list for item in scrap_items_list
if (item.item_code, item.t_warehouse) not in [(d.item_code, d.warehouse) for d in result] if (item.item_code, item.t_warehouse) not in [(d.item_code, d.warehouse) for d in result]
]: ]:
frappe.new_doc( doc = frappe.new_doc(
"Subcontracting Inward Order Scrap Item", "Subcontracting Inward Order Scrap Item",
parent=scio, parent=scio,
parenttype="Subcontracting Inward Order", parenttype="Subcontracting Inward Order",
@@ -862,7 +1056,9 @@ class SubcontractingInwardController:
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"
), ),
).insert() )
doc.insert()
doc.submit()
def cancel_stock_reservation_entries_for_inward(self): def cancel_stock_reservation_entries_for_inward(self):
if self.purpose == "Receive from Customer": if self.purpose == "Receive from Customer":
@@ -1022,3 +1218,17 @@ class SubcontractingInwardController:
) )
update_subcontracting_inward_order_status(self.subcontracting_inward_order) update_subcontracting_inward_order_status(self.subcontracting_inward_order)
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_fg_reference_names(doctype, txt, searchfield, start, page_len, filters):
return frappe.get_all(
"Subcontracting Inward Order Item",
limit_start=start,
limit_page_length=page_len,
filters={"parent": filters.get("parent"), "item_code": ("like", "%%%s%%" % txt), "docstatus": 1},
fields=["name", "item_code", "delivery_warehouse"],
as_list=True,
order_by="idx",
)

View File

@@ -652,7 +652,11 @@ frappe.ui.form.on("Work Order Item", {
required_qty: row.required_qty || 1, required_qty: row.required_qty || 1,
item_name: r.message.item_name, item_name: r.message.item_name,
description: r.message.description, description: r.message.description,
source_warehouse: r.message.default_warehouse, source_warehouse:
r.message.is_customer_provided_item &&
frm.doc.subcontracting_inward_order_item
? frm.doc.source_warehouse
: r.message.default_warehouse,
allow_alternative_item: r.message.allow_alternative_item, allow_alternative_item: r.message.allow_alternative_item,
include_item_in_manufacturing: r.message.include_item_in_manufacturing, include_item_in_manufacturing: r.message.include_item_in_manufacturing,
}); });

View File

@@ -185,8 +185,6 @@ class WorkOrder(Document):
if not self.subcontracting_inward_order: if not self.subcontracting_inward_order:
self.validate_sales_order() self.validate_sales_order()
else:
self.validate_self_rm_warehouse()
self.set_default_warehouse() self.set_default_warehouse()
self.validate_warehouse_belongs_to_company() self.validate_warehouse_belongs_to_company()
@@ -276,13 +274,100 @@ class WorkOrder(Document):
): ):
frappe.throw( frappe.throw(
_( _(
"Source Warehouse {0} must be same as Customer Warehouse {1} in the Subcontracting Inward Order" "Source Warehouse {0} must be same as Customer Warehouse {1} in the Subcontracting Inward Order."
).format( ).format(
frappe.bold(self.source_warehouse), get_link_to_form("Warehouse", self.source_warehouse),
frappe.bold(rm_receipt_warehouse), get_link_to_form("Warehouse", rm_receipt_warehouse),
) )
) )
if self.fg_warehouse != (
delivery_warehouse := frappe.get_cached_value(
"Subcontracting Inward Order Item",
self.subcontracting_inward_order_item,
"delivery_warehouse",
)
):
frappe.throw(
_(
"Target Warehouse {0} must be same as Delivery Warehouse {1} in the Subcontracting Inward Order Item."
).format(
get_link_to_form("Warehouse", self.fg_warehouse),
get_link_to_form(
"Warehouse",
delivery_warehouse,
),
)
)
possible_customer_provided_items = frappe.get_all(
"Subcontracting Inward Order Received Item",
{
"reference_name": self.subcontracting_inward_order_item,
"is_customer_provided_item": 1,
"docstatus": 1,
},
["rm_item_code", "received_qty", "returned_qty", "work_order_qty"],
)
item_codes = []
for item in self.required_items:
if item.is_customer_provided_item:
if item.source_warehouse != self.source_warehouse:
frappe.throw(
_(
"Row #{0}: Source Warehouse {1} for item {2} must be same as Source Warehouse {3} in the Work Order."
).format(
item.idx,
get_link_to_form("Warehouse", item.source_warehouse),
get_link_to_form("Item", item.item_code),
get_link_to_form("Warehouse", self.source_warehouse),
)
)
elif item.item_code in item_codes:
frappe.throw(
_("Row #{0}: Customer Provided Item {1} cannot be added multiple times.").format(
item.idx,
get_link_to_form("Item", item.item_code),
)
)
else:
row = next(
(i for i in possible_customer_provided_items if i.rm_item_code == item.item_code),
None,
)
if row:
if item.required_qty > row.received_qty - row.returned_qty - row.work_order_qty:
frappe.throw(
_(
"Row #{0}: Customer Provided Item {1} has insufficient quantity in the Subcontracting Inward Order. Available quantity is {2}."
).format(
item.idx,
get_link_to_form("Item", item.item_code),
frappe.bold(row.received_qty - row.returned_qty - row.work_order_qty),
)
)
else:
item_codes.append(item.item_code)
else:
frappe.throw(
_(
"Row #{0}: Customer Provided Item {1} does not exist in the Required Items table linked to the Subcontracting Inward Order."
).format(
item.idx,
get_link_to_form("Item", item.item_code),
)
)
elif frappe.get_cached_value("Warehouse", item.source_warehouse, "customer"):
frappe.throw(
_(
"Row #{0}: Source Warehouse {1} for item {2} cannot be a customer warehouse."
).format(
item.idx,
get_link_to_form("Warehouse", item.source_warehouse),
get_link_to_form("Item", item.item_code),
)
)
def set_warehouses(self): def set_warehouses(self):
for row in self.required_items: for row in self.required_items:
if not row.source_warehouse: if not row.source_warehouse:
@@ -356,15 +441,6 @@ class WorkOrder(Document):
else: else:
frappe.throw(_("Sales Order {0} is not valid").format(self.sales_order)) frappe.throw(_("Sales Order {0} is not valid").format(self.sales_order))
def validate_self_rm_warehouse(self):
for item in [item for item in self.required_items if not item.is_customer_provided_item]:
if frappe.get_cached_value("Warehouse", item.source_warehouse, "customer"):
frappe.throw(
_("Row #{0}: Source Warehouse {1} for item {2} cannot be a customer warehouse.").format(
item.idx, frappe.bold(item.source_warehouse), frappe.bold(item.item_code)
)
)
def check_sales_order_on_hold_or_close(self): def check_sales_order_on_hold_or_close(self):
status = frappe.db.get_value("Sales Order", self.sales_order, "status") status = frappe.db.get_value("Sales Order", self.sales_order, "status")
if status in ("Closed", "On Hold"): if status in ("Closed", "On Hold"):
@@ -703,33 +779,40 @@ class WorkOrder(Document):
def set_qty_change(self): def set_qty_change(self):
if scio_item_name := self.get("subcontracting_inward_order_item"): if scio_item_name := self.get("subcontracting_inward_order_item"):
scio_rm_item_names = frappe.db.get_all(
"Subcontracting Inward Order Received Item",
filters={"reference_name": scio_item_name, "docstatus": 1, "is_customer_provided_item": 1},
pluck="name",
)
self.qty_change = frappe._dict() self.qty_change = frappe._dict()
data = frappe.get_all( data = frappe.get_all(
"Subcontracting Inward Order Received Item", "Subcontracting Inward Order Received Item",
{"name": ["in", scio_rm_item_names]}, {"reference_name": scio_item_name, "docstatus": 1, "is_customer_provided_item": 1},
["rm_item_code", "required_qty as bom_qty", "work_order_qty", "received_qty"], ["rm_item_code", "required_qty as bom_qty", "work_order_qty", "received_qty"],
) )
for d in data: for d in data:
wo_item = next( wo_item = next(
wo_item for wo_item in self.get("required_items") if wo_item.item_code == d.rm_item_code (
wo_item
for wo_item in self.get("required_items")
if wo_item.item_code == d.rm_item_code
),
None,
) )
if ( if (
d.work_order_qty + (wo_item.required_qty if self._action == "submit" else 0) wo_item
) == d.bom_qty and d.received_qty > d.bom_qty: and (d.work_order_qty + (wo_item.required_qty if self._action == "submit" else 0))
== d.bom_qty
and d.received_qty > d.bom_qty
):
self.qty_change[wo_item.name] = d.received_qty - d.bom_qty self.qty_change[wo_item.name] = d.received_qty - d.bom_qty
def update_subcontracting_inward_order_received_items(self): def update_subcontracting_inward_order_received_items(self):
if scio_item_name := self.get("subcontracting_inward_order_item"): if scio_item_name := self.get("subcontracting_inward_order_item"):
scio_rm_data = frappe.get_all( scio_rm_data = frappe.get_all(
"Subcontracting Inward Order Received Item", "Subcontracting Inward Order Received Item",
filters={"reference_name": scio_item_name, "docstatus": 1}, filters={
"reference_name": scio_item_name,
"docstatus": 1,
"rm_item_code": ["in", [d.item_code for d in self.get("required_items")]],
},
fields=["name", "rm_item_code"], fields=["name", "rm_item_code"],
) )
@@ -1328,7 +1411,7 @@ class WorkOrder(Document):
frappe.msgprint( frappe.msgprint(
_( _(
"Warning: Quantity exceeds maximum producible quantity based on quantity of raw materials received through the Subcontracting Inward Order {0}." "Warning: Quantity exceeds maximum producible quantity based on quantity of raw materials received through the Subcontracting Inward Order {0}."
).format(frappe.bold(self.subcontracting_inward_order)), ).format(get_link_to_form("Subcontracting Inward Order", self.subcontracting_inward_order)),
alert=True, alert=True,
indicator="orange", indicator="orange",
) )
@@ -2179,14 +2262,13 @@ def make_stock_entry(
stock_entry.from_bom = 1 stock_entry.from_bom = 1
stock_entry.bom_no = work_order.bom_no stock_entry.bom_no = work_order.bom_no
stock_entry.use_multi_level_bom = work_order.use_multi_level_bom stock_entry.use_multi_level_bom = work_order.use_multi_level_bom
if purpose in ["Material Transfer for Manufacture", "Manufacture"]:
stock_entry.subcontracting_inward_order = work_order.subcontracting_inward_order
# accept 0 qty as well # accept 0 qty as well
stock_entry.fg_completed_qty = ( stock_entry.fg_completed_qty = (
qty if qty is not None else (flt(work_order.qty) - flt(work_order.produced_qty)) qty if qty is not None else (flt(work_order.qty) - flt(work_order.produced_qty))
) )
if purpose == "Manufacture" and work_order.subcontracting_inward_order:
stock_entry.subcontracting_inward_order = work_order.subcontracting_inward_order
if work_order.bom_no: if work_order.bom_no:
stock_entry.inspection_required = frappe.db.get_value("BOM", work_order.bom_no, "inspection_required") stock_entry.inspection_required = frappe.db.get_value("BOM", work_order.bom_no, "inspection_required")

View File

@@ -151,6 +151,17 @@ frappe.ui.form.on("Stock Entry", {
if (!check_should_not_attach_bom_items(frm.doc.bom_no)) { if (!check_should_not_attach_bom_items(frm.doc.bom_no)) {
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype); erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
} }
if (frm.doc.purpose == "Receive from Customer") {
frm.set_query("against_fg", "items", function () {
return {
query: "erpnext.controllers.subcontracting_inward_controller.get_fg_reference_names",
filters: {
parent: frm.doc.subcontracting_inward_order,
},
};
});
}
}, },
setup_quality_inspection: function (frm) { setup_quality_inspection: function (frm) {
@@ -854,6 +865,10 @@ frappe.ui.form.on("Stock Entry Detail", {
if (item.is_finished_item) { if (item.is_finished_item) {
frm.events.set_fg_completed_qty(frm); frm.events.set_fg_completed_qty(frm);
} }
if (frm.doc.purpose === "Receive from Customer") {
item.t_warehouse = frm.doc.items.find((item) => item.scio_detail).t_warehouse;
}
}, },
set_basic_rate_manually(frm, cdt, cdn) { set_basic_rate_manually(frm, cdt, cdn) {
let row = locals[cdt][cdn]; let row = locals[cdt][cdn];

View File

@@ -904,10 +904,9 @@ class StockEntry(StockController, SubcontractingInwardController):
if d.s_warehouse or d.set_basic_rate_manually: if d.s_warehouse or d.set_basic_rate_manually:
continue continue
if d.allow_zero_valuation_rate and self.purpose != "Receive from Customer": if d.allow_zero_valuation_rate and d.basic_rate and self.purpose != "Receive from Customer":
d.basic_rate = 0.0 d.basic_rate = 0.0
items.append(d.item_code) items.append(d.item_code)
elif d.is_finished_item: elif d.is_finished_item:
if self.purpose == "Manufacture": if self.purpose == "Manufacture":
d.basic_rate = self.get_basic_rate_for_manufactured_item( d.basic_rate = self.get_basic_rate_for_manufactured_item(

View File

@@ -21,6 +21,7 @@
"is_scrap_item", "is_scrap_item",
"quality_inspection", "quality_inspection",
"subcontracted_item", "subcontracted_item",
"against_fg",
"section_break_8", "section_break_8",
"description", "description",
"column_break_10", "column_break_10",
@@ -113,7 +114,8 @@
"label": "Target Warehouse", "label": "Target Warehouse",
"oldfieldname": "t_warehouse", "oldfieldname": "t_warehouse",
"oldfieldtype": "Link", "oldfieldtype": "Link",
"options": "Warehouse" "options": "Warehouse",
"read_only_depends_on": "eval:parent.purpose === \"Receive from Customer\""
}, },
{ {
"fieldname": "sec_break1", "fieldname": "sec_break1",
@@ -641,6 +643,16 @@
"print_hide": 1, "print_hide": 1,
"read_only": 1, "read_only": 1,
"search_index": 1 "search_index": 1
},
{
"depends_on": "eval:parent.purpose === \"Receive from Customer\" && !doc.scio_detail",
"fieldname": "against_fg",
"fieldtype": "Link",
"label": "Against Finished Good",
"mandatory_depends_on": "eval:parent.purpose === \"Receive from Customer\" && !doc.scio_detail",
"no_copy": 1,
"options": "Subcontracting Inward Order Item",
"set_only_once": 1
} }
], ],
"grid_page_length": 50, "grid_page_length": 50,
@@ -648,7 +660,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2025-10-14 14:10:38.373099", "modified": "2025-10-16 11:50:50.573443",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Stock Entry Detail", "name": "Stock Entry Detail",

View File

@@ -16,6 +16,7 @@ class StockEntryDetail(Document):
actual_qty: DF.Float actual_qty: DF.Float
additional_cost: DF.Currency additional_cost: DF.Currency
against_fg: DF.Link | None
against_stock_entry: DF.Link | None against_stock_entry: DF.Link | None
allow_alternative_item: DF.Check allow_alternative_item: DF.Check
allow_zero_valuation_rate: DF.Check allow_zero_valuation_rate: DF.Check

View File

@@ -60,6 +60,14 @@ frappe.ui.form.on("Subcontracting Inward Order", {
}; };
}); });
frm.set_query("bom", "items", () => {
return {
filters: {
is_active: 1,
},
};
});
frm.set_query("set_delivery_warehouse", () => { frm.set_query("set_delivery_warehouse", () => {
return { return {
filters: { filters: {

View File

@@ -275,10 +275,13 @@ class SubcontractingInwardOrder(SubcontractingController):
d.precision("qty"), d.precision("qty"),
) )
for item in self.get("received_items") for item in self.get("received_items")
if item.reference_name == d.name and item.is_customer_provided_item if item.reference_name == d.name and item.is_customer_provided_item and item.required_qty
] ]
) )
qty = int(qty) if frappe.get_cached_value("UOM", d.stock_uom, "must_be_whole_number") else qty qty = min(
int(qty) if frappe.get_cached_value("UOM", d.stock_uom, "must_be_whole_number") else qty,
d.qty - d.produced_qty,
)
item_details.update({"qty": qty, "max_producible_qty": qty}) item_details.update({"qty": qty, "max_producible_qty": qty})
item_list.append(item_details) item_list.append(item_details)

View File

@@ -66,6 +66,7 @@ class IntegrationTestSubcontractingInwardOrder(IntegrationTestCase):
"transfer_qty": 5, "transfer_qty": 5,
"uom": "Nos", "uom": "Nos",
"conversion_factor": 1, "conversion_factor": 1,
"against_fg": scio.items[0].name,
}, },
) )
rm_in.submit() rm_in.submit()

View File

@@ -46,6 +46,7 @@
"in_global_search": 1, "in_global_search": 1,
"label": "Item Name", "label": "Item Name",
"print_hide": 1, "print_hide": 1,
"read_only": 1,
"reqd": 1 "reqd": 1
}, },
{ {
@@ -185,7 +186,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2025-10-14 10:29:29.256455", "modified": "2025-10-18 18:04:04.204651",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Subcontracting", "module": "Subcontracting",
"name": "Subcontracting Inward Order Item", "name": "Subcontracting Inward Order Item",

View File

@@ -22,7 +22,9 @@
"column_break_16", "column_break_16",
"consumed_qty", "consumed_qty",
"work_order_qty", "work_order_qty",
"returned_qty" "returned_qty",
"section_break_yhve",
"rate"
], ],
"fields": [ "fields": [
{ {
@@ -32,7 +34,8 @@
"in_list_view": 1, "in_list_view": 1,
"label": "Item Code", "label": "Item Code",
"options": "Item", "options": "Item",
"read_only": 1 "read_only": 1,
"reqd": 1
}, },
{ {
"columns": 2, "columns": 2,
@@ -70,7 +73,8 @@
"fieldname": "reference_name", "fieldname": "reference_name",
"fieldtype": "Data", "fieldtype": "Data",
"label": "Reference Name", "label": "Reference Name",
"read_only": 1 "read_only": 1,
"reqd": 1
}, },
{ {
"fieldname": "section_break_13", "fieldname": "section_break_13",
@@ -116,7 +120,6 @@
}, },
{ {
"default": "0", "default": "0",
"depends_on": "eval:doc.returned_qty",
"fieldname": "returned_qty", "fieldname": "returned_qty",
"fieldtype": "Float", "fieldtype": "Float",
"label": "Returned Qty", "label": "Returned Qty",
@@ -128,7 +131,7 @@
{ {
"allow_on_submit": 1, "allow_on_submit": 1,
"default": "0", "default": "0",
"depends_on": "eval:doc.work_order_qty", "depends_on": "eval:!(!doc.is_customer_provided_item && doc.is_additional_item)",
"fieldname": "work_order_qty", "fieldname": "work_order_qty",
"fieldtype": "Float", "fieldtype": "Float",
"label": "Work Order Qty", "label": "Work Order Qty",
@@ -146,7 +149,6 @@
"reqd": 1 "reqd": 1
}, },
{ {
"depends_on": "eval:!doc.is_customer_provided_item",
"fieldname": "warehouse", "fieldname": "warehouse",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Warehouse", "label": "Warehouse",
@@ -166,11 +168,27 @@
}, },
{ {
"default": "0", "default": "0",
"depends_on": "eval:!doc.bom_detail_no",
"fieldname": "is_additional_item", "fieldname": "is_additional_item",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Is Additional Item", "label": "Is Additional Item",
"read_only": 1 "read_only": 1
},
{
"depends_on": "eval:doc.is_customer_provided_item",
"fieldname": "section_break_yhve",
"fieldtype": "Section Break"
},
{
"default": "0",
"fieldname": "rate",
"fieldtype": "Currency",
"label": "Rate",
"mandatory_depends_on": "eval:doc.is_customer_provided_item",
"no_copy": 1,
"non_negative": 1,
"options": "Company:company:default_currency",
"read_only": 1,
"read_only_depends_on": "eval:doc.is_customer_provided_item"
} }
], ],
"grid_page_length": 50, "grid_page_length": 50,
@@ -178,7 +196,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2025-10-14 10:18:58.905093", "modified": "2025-10-21 23:44:18.302327",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Subcontracting", "module": "Subcontracting",
"name": "Subcontracting Inward Order Received Item", "name": "Subcontracting Inward Order Received Item",

View File

@@ -19,12 +19,13 @@ class SubcontractingInwardOrderReceivedItem(Document):
consumed_qty: DF.Float consumed_qty: DF.Float
is_additional_item: DF.Check is_additional_item: DF.Check
is_customer_provided_item: DF.Check is_customer_provided_item: DF.Check
main_item_code: DF.Link | None main_item_code: DF.Link
parent: DF.Data parent: DF.Data
parentfield: DF.Data parentfield: DF.Data
parenttype: DF.Data parenttype: DF.Data
rate: DF.Currency
received_qty: DF.Float received_qty: DF.Float
reference_name: DF.Data | None reference_name: DF.Data
required_qty: DF.Float required_qty: DF.Float
returned_qty: DF.Float returned_qty: DF.Float
rm_item_code: DF.Link rm_item_code: DF.Link