mirror of
https://github.com/frappe/erpnext.git
synced 2026-04-14 20:35:09 +00:00
Merge pull request #50189 from mihir-kandoi/inward-fix
fix: feat: multiple changes related to subcontracting inward
This commit is contained in:
@@ -1297,7 +1297,7 @@ class SalesInvoice(SellingController):
|
||||
item.idx,
|
||||
item.stock_qty,
|
||||
item.stock_uom,
|
||||
frappe.bold(item.item_code),
|
||||
get_link_to_form("Item", item.item_code),
|
||||
frappe.bold(max_qty),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -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():
|
||||
frappe.throw(
|
||||
_(
|
||||
"Items cannot be updated as Subcontracting Inward Order is created against the Sales Order {0}."
|
||||
).format(frappe.bold(parent.name))
|
||||
"Items cannot be updated as Subcontracting Inward Order(s) exist against this Subcontracted Sales Order."
|
||||
)
|
||||
)
|
||||
parent.validate_selling_price()
|
||||
parent.validate_for_duplicate_items()
|
||||
|
||||
@@ -156,7 +156,7 @@ class SubcontractingController(StockController):
|
||||
frappe.throw(
|
||||
_(
|
||||
"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"):
|
||||
@@ -664,6 +664,8 @@ class SubcontractingController(StockController):
|
||||
|
||||
def __add_supplied_or_received_item(self, item_row, bom_item, qty):
|
||||
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)
|
||||
if rm_obj.get("qty"):
|
||||
# Qty field not exists
|
||||
|
||||
@@ -9,7 +9,7 @@ from erpnext.stock.serial_batch_bundle import get_serial_batch_list_from_item
|
||||
class SubcontractingInwardController:
|
||||
def validate_subcontracting_inward(self):
|
||||
self.validate_inward_order()
|
||||
self.validate_customer_provided_item_for_inward()
|
||||
self.set_allow_zero_valuation_rate()
|
||||
self.validate_warehouse_()
|
||||
self.validate_serial_batch_for_return_or_delivery()
|
||||
self.validate_delivery()
|
||||
@@ -50,11 +50,22 @@ class SubcontractingInwardController:
|
||||
self.validate_material_receipt()
|
||||
case purpose if purpose in ["Return Raw Material to Customer", "Subcontracting Return"]:
|
||||
self.validate_returns()
|
||||
case "Material Transfer for Manufacture":
|
||||
self.validate_material_transfer()
|
||||
case "Manufacture":
|
||||
self.validate_manufacture()
|
||||
|
||||
def validate_material_receipt(self):
|
||||
rm_item_fg_combo = []
|
||||
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 (
|
||||
item.scio_detail
|
||||
and frappe.get_cached_value(
|
||||
@@ -65,17 +76,44 @@ class SubcontractingInwardController:
|
||||
frappe.throw(
|
||||
_(
|
||||
"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):
|
||||
for item in self.items:
|
||||
if not item.scio_detail:
|
||||
frappe.throw(
|
||||
_("Row #{0}: Item {1} is not a part of Subcontracting Inward Order {2}").format(
|
||||
item.idx,
|
||||
bold(item.item_code),
|
||||
bold(self.subcontracting_inward_order),
|
||||
get_link_to_form("Item", item.item_code),
|
||||
get_link_to_form("Subcontracting Inward Order", self.subcontracting_inward_order),
|
||||
)
|
||||
)
|
||||
elif item.item_code != (
|
||||
@@ -86,7 +124,7 @@ class SubcontractingInwardController:
|
||||
):
|
||||
frappe.throw(
|
||||
_("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(
|
||||
_(
|
||||
"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:
|
||||
data = frappe.get_value(
|
||||
@@ -114,16 +152,88 @@ class SubcontractingInwardController:
|
||||
frappe.throw(
|
||||
_(
|
||||
"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):
|
||||
skip_transfer, customer_warehouse, wip_warehouse = frappe.get_cached_value(
|
||||
"Work Order",
|
||||
self.work_order,
|
||||
["skip_transfer", "source_warehouse", "wip_warehouse"],
|
||||
)
|
||||
warehouse = customer_warehouse if skip_transfer else wip_warehouse
|
||||
if next(item for item in self.items if item.is_finished_item).t_warehouse != (
|
||||
fg_warehouse := frappe.get_cached_value("Work Order", self.work_order, "fg_warehouse")
|
||||
):
|
||||
frappe.throw(
|
||||
_(
|
||||
"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 = [
|
||||
item
|
||||
@@ -133,74 +243,133 @@ class SubcontractingInwardController:
|
||||
and frappe.get_cached_value("Item", item.item_code, "is_customer_provided_item")
|
||||
]
|
||||
|
||||
table = frappe.qb.DocType("Subcontracting Inward Order Received Item")
|
||||
query = (
|
||||
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]))
|
||||
)
|
||||
customer_warehouse = frappe.get_cached_value(
|
||||
"Subcontracting Inward Order", self.subcontracting_inward_order, "customer_warehouse"
|
||||
)
|
||||
rm_item_dict = frappe._dict(
|
||||
{
|
||||
d.rm_item_code: frappe._dict(
|
||||
{"name": d.name, "total_qty": d.total_qty, "qty": d.consumed_qty}
|
||||
if frappe.get_cached_value("Work Order", self.work_order, "skip_transfer"):
|
||||
table = frappe.qb.DocType("Subcontracting Inward Order Received Item")
|
||||
query = (
|
||||
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)
|
||||
}
|
||||
)
|
||||
|
||||
for item in items:
|
||||
if rm := rm_item_dict.get(item.item_code):
|
||||
if rm.qty + item.transfer_qty > rm.total_qty:
|
||||
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),
|
||||
.where(
|
||||
(table.docstatus == 1)
|
||||
& (table.parent == self.subcontracting_inward_order)
|
||||
& (
|
||||
table.reference_name
|
||||
== frappe.get_cached_value(
|
||||
"Work Order", self.work_order, "subcontracting_inward_order_item"
|
||||
)
|
||||
)
|
||||
else:
|
||||
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),
|
||||
)
|
||||
& (table.rm_item_code.isin([item.item_code for item in items]))
|
||||
)
|
||||
)
|
||||
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):
|
||||
if self.subcontracting_inward_order:
|
||||
if self.purpose in ["Subcontracting Delivery", "Subcontracting Return"]:
|
||||
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
|
||||
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"):
|
||||
item_codes = []
|
||||
for item in items:
|
||||
if rm := rm_item_dict.get(item.item_code):
|
||||
if rm.qty + item.transfer_qty > rm.total_qty:
|
||||
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,
|
||||
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):
|
||||
if self.subcontracting_inward_order and self.purpose in [
|
||||
@@ -222,13 +391,13 @@ class SubcontractingInwardController:
|
||||
frappe.throw(
|
||||
_(
|
||||
"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:
|
||||
frappe.throw(
|
||||
_(
|
||||
"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):
|
||||
@@ -249,7 +418,10 @@ class SubcontractingInwardController:
|
||||
frappe.throw(
|
||||
_(
|
||||
"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 (
|
||||
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(
|
||||
_(
|
||||
"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):
|
||||
@@ -302,7 +477,7 @@ class SubcontractingInwardController:
|
||||
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"
|
||||
).format(item.idx, bold(item.item_code))
|
||||
).format(item.idx, get_link_to_form("Item", item.item_code))
|
||||
)
|
||||
|
||||
def validate_delivery_on_save(self):
|
||||
@@ -315,8 +490,8 @@ class SubcontractingInwardController:
|
||||
frappe.throw(
|
||||
_("Row #{0}: Item {1} is not a part of Subcontracting Inward Order {2}").format(
|
||||
item.idx,
|
||||
bold(item.item_code),
|
||||
bold(self.subcontracting_inward_order),
|
||||
get_link_to_form("Item", item.item_code),
|
||||
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}"
|
||||
).format(
|
||||
item.idx,
|
||||
bold(item.item_code),
|
||||
get_link_to_form("Item", item.item_code),
|
||||
bold(max_allowed_qty),
|
||||
bold(
|
||||
frappe.get_cached_value(
|
||||
@@ -370,7 +545,7 @@ class SubcontractingInwardController:
|
||||
"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_no == self.subcontracting_inward_order)
|
||||
& (table.voucher_detail_no == item.scio_detail)
|
||||
& (table.warehouse == item.s_warehouse)
|
||||
)
|
||||
.orderby(table.creation)
|
||||
)
|
||||
@@ -522,7 +696,7 @@ class SubcontractingInwardController:
|
||||
) < scio_rm_item.work_order_qty:
|
||||
frappe.throw(
|
||||
_("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(
|
||||
_(
|
||||
"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:
|
||||
scio_rm_item = frappe.get_value(
|
||||
@@ -570,7 +744,7 @@ class SubcontractingInwardController:
|
||||
"docstatus": 1,
|
||||
"rm_item_code": item.item_code,
|
||||
"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,
|
||||
},
|
||||
["consumed_qty", "billed_qty", "returned_qty"],
|
||||
@@ -582,7 +756,7 @@ class SubcontractingInwardController:
|
||||
frappe.throw(
|
||||
_(
|
||||
"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):
|
||||
@@ -638,7 +812,9 @@ class SubcontractingInwardController:
|
||||
data = frappe._dict()
|
||||
for item in self.items:
|
||||
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:
|
||||
scio_rm = frappe.new_doc(
|
||||
"Subcontracting Inward Order Received Item",
|
||||
@@ -657,9 +833,16 @@ class SubcontractingInwardController:
|
||||
consumed_qty=0,
|
||||
work_order_qty=0,
|
||||
returned_qty=0,
|
||||
rate=item.customer_provided_item_cost,
|
||||
is_customer_provided_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.submit()
|
||||
item.db_set("scio_detail", scio_rm.name)
|
||||
|
||||
if data:
|
||||
@@ -670,39 +853,47 @@ class SubcontractingInwardController:
|
||||
"name": ["in", list(data.keys())],
|
||||
"docstatus": 1,
|
||||
},
|
||||
fields=["name", "required_qty", "received_qty"],
|
||||
fields=["rate", "name", "required_qty", "received_qty"],
|
||||
)
|
||||
|
||||
deleted_docs = []
|
||||
table = frappe.qb.DocType("Subcontracting Inward Order Received Item")
|
||||
case_expr = Case()
|
||||
case_expr_qty, case_expr_rate = Case(), Case()
|
||||
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:
|
||||
deleted_docs.append(d.name)
|
||||
frappe.delete_doc("Subcontracting Inward Order Received Item", d.name)
|
||||
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:
|
||||
frappe.qb.update(table).set(table.received_qty, case_expr).where(
|
||||
(table.name.isin(list(set(data.keys()) - set(deleted_docs)))) & (table.docstatus == 1)
|
||||
).run()
|
||||
if final_list := list(set(data.keys()) - set(deleted_docs)):
|
||||
frappe.qb.update(table).set(table.received_qty, case_expr_qty).set(
|
||||
table.rate, case_expr_rate
|
||||
).where((table.name.isin(final_list)) & (table.docstatus == 1)).run()
|
||||
|
||||
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]
|
||||
item_code_wh = frappe._dict(
|
||||
{
|
||||
(item.item_code, item.s_warehouse): item.transfer_qty
|
||||
if self._action == "submit"
|
||||
else -item.transfer_qty
|
||||
(
|
||||
item.item_code,
|
||||
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
|
||||
}
|
||||
)
|
||||
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")
|
||||
data = (
|
||||
@@ -712,22 +903,21 @@ class SubcontractingInwardController:
|
||||
table.rm_item_code,
|
||||
table.is_customer_provided_item,
|
||||
table.consumed_qty,
|
||||
table.required_qty,
|
||||
table.warehouse,
|
||||
table.is_additional_item,
|
||||
)
|
||||
.where(
|
||||
(table.docstatus == 1)
|
||||
& (table.rm_item_code.isin(item_codes))
|
||||
& ((table.warehouse.isin(warehouses)) | (table.warehouse.isnull()))
|
||||
& (table.rm_item_code.isin(list(set(item_codes))))
|
||||
& (
|
||||
(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.reference_name
|
||||
== frappe.get_cached_value(
|
||||
"Work Order", self.work_order, "subcontracting_inward_order_item"
|
||||
)
|
||||
table.reference_name
|
||||
== frappe.get_cached_value(
|
||||
"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))
|
||||
|
||||
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)
|
||||
else:
|
||||
deleted_docs.append(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 len(final_name_list) > 0:
|
||||
if final_list := list(set([d.name for d in data]) - set(deleted_docs)):
|
||||
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()
|
||||
|
||||
main_item_code = next(fg for fg in self.items if fg.is_finished_item).item_code
|
||||
for extra_item in [
|
||||
item
|
||||
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",
|
||||
parent=self.subcontracting_inward_order,
|
||||
parenttype="Subcontracting Inward Order",
|
||||
@@ -783,7 +974,9 @@ class SubcontractingInwardController:
|
||||
consumed_qty=extra_item.transfer_qty,
|
||||
warehouse=extra_item.s_warehouse,
|
||||
is_additional_item=True,
|
||||
).insert()
|
||||
)
|
||||
doc.insert()
|
||||
doc.submit()
|
||||
|
||||
def update_inward_order_scrap_items(self):
|
||||
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)
|
||||
)
|
||||
|
||||
final_list = list(set([v.name for v in scrap_item_dict.values()]) - set(deleted_docs))
|
||||
if len(final_list) > 0:
|
||||
if final_list := list(
|
||||
set([v.name for v in scrap_item_dict.values()]) - set(deleted_docs)
|
||||
):
|
||||
frappe.qb.update(table).set(table.produced_qty, case_expr).where(
|
||||
(table.name.isin(final_list)) & (table.docstatus == 1)
|
||||
).run()
|
||||
@@ -847,7 +1041,7 @@ class SubcontractingInwardController:
|
||||
for item in scrap_items_list
|
||||
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",
|
||||
parent=scio,
|
||||
parenttype="Subcontracting Inward Order",
|
||||
@@ -862,7 +1056,9 @@ class SubcontractingInwardController:
|
||||
reference_name=frappe.get_value(
|
||||
"Work Order", self.work_order, "subcontracting_inward_order_item"
|
||||
),
|
||||
).insert()
|
||||
)
|
||||
doc.insert()
|
||||
doc.submit()
|
||||
|
||||
def cancel_stock_reservation_entries_for_inward(self):
|
||||
if self.purpose == "Receive from Customer":
|
||||
@@ -1022,3 +1218,17 @@ class SubcontractingInwardController:
|
||||
)
|
||||
|
||||
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",
|
||||
)
|
||||
|
||||
@@ -652,7 +652,11 @@ frappe.ui.form.on("Work Order Item", {
|
||||
required_qty: row.required_qty || 1,
|
||||
item_name: r.message.item_name,
|
||||
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,
|
||||
include_item_in_manufacturing: r.message.include_item_in_manufacturing,
|
||||
});
|
||||
|
||||
@@ -185,8 +185,6 @@ class WorkOrder(Document):
|
||||
|
||||
if not self.subcontracting_inward_order:
|
||||
self.validate_sales_order()
|
||||
else:
|
||||
self.validate_self_rm_warehouse()
|
||||
|
||||
self.set_default_warehouse()
|
||||
self.validate_warehouse_belongs_to_company()
|
||||
@@ -276,13 +274,100 @@ class WorkOrder(Document):
|
||||
):
|
||||
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(
|
||||
frappe.bold(self.source_warehouse),
|
||||
frappe.bold(rm_receipt_warehouse),
|
||||
get_link_to_form("Warehouse", self.source_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):
|
||||
for row in self.required_items:
|
||||
if not row.source_warehouse:
|
||||
@@ -356,15 +441,6 @@ class WorkOrder(Document):
|
||||
else:
|
||||
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):
|
||||
status = frappe.db.get_value("Sales Order", self.sales_order, "status")
|
||||
if status in ("Closed", "On Hold"):
|
||||
@@ -703,33 +779,40 @@ class WorkOrder(Document):
|
||||
|
||||
def set_qty_change(self):
|
||||
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()
|
||||
|
||||
data = frappe.get_all(
|
||||
"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"],
|
||||
)
|
||||
for d in data:
|
||||
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 (
|
||||
d.work_order_qty + (wo_item.required_qty if self._action == "submit" else 0)
|
||||
) == d.bom_qty and d.received_qty > d.bom_qty:
|
||||
wo_item
|
||||
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
|
||||
|
||||
def update_subcontracting_inward_order_received_items(self):
|
||||
if scio_item_name := self.get("subcontracting_inward_order_item"):
|
||||
scio_rm_data = frappe.get_all(
|
||||
"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"],
|
||||
)
|
||||
|
||||
@@ -1328,7 +1411,7 @@ class WorkOrder(Document):
|
||||
frappe.msgprint(
|
||||
_(
|
||||
"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,
|
||||
indicator="orange",
|
||||
)
|
||||
@@ -2179,14 +2262,13 @@ def make_stock_entry(
|
||||
stock_entry.from_bom = 1
|
||||
stock_entry.bom_no = work_order.bom_no
|
||||
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
|
||||
stock_entry.fg_completed_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:
|
||||
stock_entry.inspection_required = frappe.db.get_value("BOM", work_order.bom_no, "inspection_required")
|
||||
|
||||
|
||||
@@ -151,6 +151,17 @@ frappe.ui.form.on("Stock Entry", {
|
||||
if (!check_should_not_attach_bom_items(frm.doc.bom_no)) {
|
||||
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) {
|
||||
@@ -854,6 +865,10 @@ frappe.ui.form.on("Stock Entry Detail", {
|
||||
if (item.is_finished_item) {
|
||||
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) {
|
||||
let row = locals[cdt][cdn];
|
||||
|
||||
@@ -904,10 +904,9 @@ class StockEntry(StockController, SubcontractingInwardController):
|
||||
if d.s_warehouse or d.set_basic_rate_manually:
|
||||
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
|
||||
items.append(d.item_code)
|
||||
|
||||
elif d.is_finished_item:
|
||||
if self.purpose == "Manufacture":
|
||||
d.basic_rate = self.get_basic_rate_for_manufactured_item(
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
"is_scrap_item",
|
||||
"quality_inspection",
|
||||
"subcontracted_item",
|
||||
"against_fg",
|
||||
"section_break_8",
|
||||
"description",
|
||||
"column_break_10",
|
||||
@@ -113,7 +114,8 @@
|
||||
"label": "Target Warehouse",
|
||||
"oldfieldname": "t_warehouse",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Warehouse"
|
||||
"options": "Warehouse",
|
||||
"read_only_depends_on": "eval:parent.purpose === \"Receive from Customer\""
|
||||
},
|
||||
{
|
||||
"fieldname": "sec_break1",
|
||||
@@ -641,6 +643,16 @@
|
||||
"print_hide": 1,
|
||||
"read_only": 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,
|
||||
@@ -648,7 +660,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-10-14 14:10:38.373099",
|
||||
"modified": "2025-10-16 11:50:50.573443",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Stock Entry Detail",
|
||||
|
||||
@@ -16,6 +16,7 @@ class StockEntryDetail(Document):
|
||||
|
||||
actual_qty: DF.Float
|
||||
additional_cost: DF.Currency
|
||||
against_fg: DF.Link | None
|
||||
against_stock_entry: DF.Link | None
|
||||
allow_alternative_item: DF.Check
|
||||
allow_zero_valuation_rate: DF.Check
|
||||
|
||||
@@ -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", () => {
|
||||
return {
|
||||
filters: {
|
||||
|
||||
@@ -275,10 +275,13 @@ class SubcontractingInwardOrder(SubcontractingController):
|
||||
d.precision("qty"),
|
||||
)
|
||||
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_list.append(item_details)
|
||||
|
||||
@@ -66,6 +66,7 @@ class IntegrationTestSubcontractingInwardOrder(IntegrationTestCase):
|
||||
"transfer_qty": 5,
|
||||
"uom": "Nos",
|
||||
"conversion_factor": 1,
|
||||
"against_fg": scio.items[0].name,
|
||||
},
|
||||
)
|
||||
rm_in.submit()
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
"in_global_search": 1,
|
||||
"label": "Item Name",
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
@@ -185,7 +186,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-10-14 10:29:29.256455",
|
||||
"modified": "2025-10-18 18:04:04.204651",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Subcontracting",
|
||||
"name": "Subcontracting Inward Order Item",
|
||||
|
||||
@@ -22,7 +22,9 @@
|
||||
"column_break_16",
|
||||
"consumed_qty",
|
||||
"work_order_qty",
|
||||
"returned_qty"
|
||||
"returned_qty",
|
||||
"section_break_yhve",
|
||||
"rate"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -32,7 +34,8 @@
|
||||
"in_list_view": 1,
|
||||
"label": "Item Code",
|
||||
"options": "Item",
|
||||
"read_only": 1
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"columns": 2,
|
||||
@@ -70,7 +73,8 @@
|
||||
"fieldname": "reference_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Reference Name",
|
||||
"read_only": 1
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_13",
|
||||
@@ -116,7 +120,6 @@
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.returned_qty",
|
||||
"fieldname": "returned_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Returned Qty",
|
||||
@@ -128,7 +131,7 @@
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"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",
|
||||
"fieldtype": "Float",
|
||||
"label": "Work Order Qty",
|
||||
@@ -146,7 +149,6 @@
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.is_customer_provided_item",
|
||||
"fieldname": "warehouse",
|
||||
"fieldtype": "Link",
|
||||
"label": "Warehouse",
|
||||
@@ -166,11 +168,27 @@
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:!doc.bom_detail_no",
|
||||
"fieldname": "is_additional_item",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Additional Item",
|
||||
"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,
|
||||
@@ -178,7 +196,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-10-14 10:18:58.905093",
|
||||
"modified": "2025-10-21 23:44:18.302327",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Subcontracting",
|
||||
"name": "Subcontracting Inward Order Received Item",
|
||||
|
||||
@@ -19,12 +19,13 @@ class SubcontractingInwardOrderReceivedItem(Document):
|
||||
consumed_qty: DF.Float
|
||||
is_additional_item: DF.Check
|
||||
is_customer_provided_item: DF.Check
|
||||
main_item_code: DF.Link | None
|
||||
main_item_code: DF.Link
|
||||
parent: DF.Data
|
||||
parentfield: DF.Data
|
||||
parenttype: DF.Data
|
||||
rate: DF.Currency
|
||||
received_qty: DF.Float
|
||||
reference_name: DF.Data | None
|
||||
reference_name: DF.Data
|
||||
required_qty: DF.Float
|
||||
returned_qty: DF.Float
|
||||
rm_item_code: DF.Link
|
||||
|
||||
Reference in New Issue
Block a user