refactor: backport old subcontracting code
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
# For license information, please see license.txt
|
||||
|
||||
import copy
|
||||
import json
|
||||
from collections import defaultdict
|
||||
|
||||
import frappe
|
||||
@@ -14,13 +15,40 @@ from erpnext.stock.utils import get_incoming_rate
|
||||
|
||||
|
||||
class SubcontractingController(StockController):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SubcontractingController, self).__init__(*args, **kwargs)
|
||||
if self.get("is_old_subcontracting_flow"):
|
||||
self.subcontract_data = frappe._dict(
|
||||
{
|
||||
"order_doctype": "Purchase Order",
|
||||
"order_field": "purchase_order",
|
||||
"rm_detail_field": "po_detail",
|
||||
"receipt_supplied_items_field": "Purchase Receipt Item Supplied",
|
||||
"order_supplied_items_field": "Purchase Order Item Supplied",
|
||||
}
|
||||
)
|
||||
else:
|
||||
self.subcontract_data = frappe._dict(
|
||||
{
|
||||
"order_doctype": "Subcontracting Order",
|
||||
"order_field": "subcontracting_order",
|
||||
"rm_detail_field": "sco_rm_detail",
|
||||
"receipt_supplied_items_field": "Subcontracting Receipt Supplied Item",
|
||||
"order_supplied_items_field": "Subcontracting Order Supplied Item",
|
||||
}
|
||||
)
|
||||
|
||||
def before_validate(self):
|
||||
self.remove_empty_rows()
|
||||
self.set_items_conversion_factor()
|
||||
if self.doctype in ["Subcontracting Order", "Subcontracting Receipt"]:
|
||||
self.remove_empty_rows()
|
||||
self.set_items_conversion_factor()
|
||||
|
||||
def validate(self):
|
||||
self.validate_items()
|
||||
self.create_raw_materials_supplied()
|
||||
if self.doctype in ["Subcontracting Order", "Subcontracting Receipt"]:
|
||||
self.validate_items()
|
||||
self.create_raw_materials_supplied()
|
||||
else:
|
||||
super(SubcontractingController, self).validate()
|
||||
|
||||
def remove_empty_rows(self):
|
||||
for key in ["service_items", "items", "supplied_items"]:
|
||||
@@ -54,7 +82,10 @@ class SubcontractingController(StockController):
|
||||
|
||||
def __get_data_before_save(self):
|
||||
item_dict = {}
|
||||
if self.doctype == "Subcontracting Receipt" and self._doc_before_save:
|
||||
if (
|
||||
self.doctype in ["Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"]
|
||||
and self._doc_before_save
|
||||
):
|
||||
for row in self._doc_before_save.get("items"):
|
||||
item_dict[row.name] = (row.item_code, row.qty)
|
||||
|
||||
@@ -64,7 +95,7 @@ class SubcontractingController(StockController):
|
||||
self.__changed_name = []
|
||||
self.__reference_name = []
|
||||
|
||||
if self.doctype == "Subcontracting Order" or self.is_new():
|
||||
if self.doctype in ["Purchase Order", "Subcontracting Order"] or self.is_new():
|
||||
self.set(self.raw_material_table, [])
|
||||
return
|
||||
|
||||
@@ -93,36 +124,38 @@ class SubcontractingController(StockController):
|
||||
self.alternative_item_details = frappe._dict()
|
||||
self.__get_backflush_based_on()
|
||||
|
||||
def __get_subcontracting_orders(self):
|
||||
self.subcontracting_orders = []
|
||||
def __get_subcontract_orders(self):
|
||||
self.subcontract_orders = []
|
||||
|
||||
if self.doctype == "Subcontracting Order":
|
||||
if self.doctype in ["Purchase Order", "Subcontracting Order"]:
|
||||
return
|
||||
|
||||
self.subcontracting_orders = [
|
||||
item.subcontracting_order for item in self.items if item.subcontracting_order
|
||||
self.subcontract_orders = [
|
||||
item.get(self.subcontract_data.order_field)
|
||||
for item in self.items
|
||||
if item.get(self.subcontract_data.order_field)
|
||||
]
|
||||
|
||||
def __get_pending_qty_to_receive(self):
|
||||
"""Get qty to be received against the subcontracting order."""
|
||||
"""Get qty to be received against the subcontract order."""
|
||||
|
||||
self.qty_to_be_received = defaultdict(float)
|
||||
|
||||
if (
|
||||
self.doctype != "Subcontracting Order"
|
||||
self.doctype != self.subcontract_data.order_doctype
|
||||
and self.backflush_based_on != "BOM"
|
||||
and self.subcontracting_orders
|
||||
and self.subcontract_orders
|
||||
):
|
||||
for row in frappe.get_all(
|
||||
"Subcontracting Order Item",
|
||||
f"{self.subcontract_data.order_doctype} Item",
|
||||
fields=["item_code", "(qty - received_qty) as qty", "parent", "name"],
|
||||
filters={"docstatus": 1, "parent": ("in", self.subcontracting_orders)},
|
||||
filters={"docstatus": 1, "parent": ("in", self.subcontract_orders)},
|
||||
):
|
||||
|
||||
self.qty_to_be_received[(row.item_code, row.parent)] += row.qty
|
||||
|
||||
def __get_transferred_items(self):
|
||||
fields = ["`tabStock Entry`.`subcontracting_order`"]
|
||||
fields = [f"`tabStock Entry`.`{self.subcontract_data.order_field}`"]
|
||||
alias_dict = {
|
||||
"item_code": "rm_item_code",
|
||||
"subcontracted_item": "main_item_code",
|
||||
@@ -145,7 +178,7 @@ class SubcontractingController(StockController):
|
||||
"s_warehouse",
|
||||
"t_warehouse",
|
||||
"item_group",
|
||||
"sco_rm_detail",
|
||||
self.subcontract_data.rm_detail_field,
|
||||
]
|
||||
|
||||
if self.backflush_based_on == "BOM":
|
||||
@@ -157,7 +190,7 @@ class SubcontractingController(StockController):
|
||||
filters = [
|
||||
["Stock Entry", "docstatus", "=", 1],
|
||||
["Stock Entry", "purpose", "=", "Send to Subcontractor"],
|
||||
["Stock Entry", "subcontracting_order", "in", self.subcontracting_orders],
|
||||
["Stock Entry", self.subcontract_data.order_field, "in", self.subcontract_orders],
|
||||
]
|
||||
|
||||
return frappe.get_all("Stock Entry", fields=fields, filters=filters)
|
||||
@@ -168,21 +201,21 @@ class SubcontractingController(StockController):
|
||||
|
||||
def __get_received_items(self, doctype):
|
||||
fields = []
|
||||
self.sco_field = "subcontracting_order"
|
||||
|
||||
for field in ["name", self.sco_field, "parent"]:
|
||||
for field in ["name", self.subcontract_data.order_field, "parent"]:
|
||||
fields.append(f"`tab{doctype} Item`.`{field}`")
|
||||
|
||||
filters = [
|
||||
[doctype, "docstatus", "=", 1],
|
||||
[f"{doctype} Item", self.sco_field, "in", self.subcontracting_orders],
|
||||
[f"{doctype} Item", self.subcontract_data.order_field, "in", self.subcontract_orders],
|
||||
]
|
||||
if doctype == "Purchase Invoice":
|
||||
filters.append(["Purchase Invoice", "update_stock", "=", 1])
|
||||
|
||||
return frappe.get_all(f"{doctype}", fields=fields, filters=filters)
|
||||
|
||||
def __get_consumed_items(self, doctype, scr_items):
|
||||
def __get_consumed_items(self, doctype, receipt_items):
|
||||
return frappe.get_all(
|
||||
"Subcontracting Receipt Supplied Item",
|
||||
self.subcontract_data.receipt_supplied_items_field,
|
||||
fields=[
|
||||
"serial_no",
|
||||
"rm_item_code",
|
||||
@@ -191,26 +224,26 @@ class SubcontractingController(StockController):
|
||||
"consumed_qty",
|
||||
"main_item_code",
|
||||
],
|
||||
filters={"docstatus": 1, "reference_name": ("in", list(scr_items)), "parenttype": doctype},
|
||||
filters={"docstatus": 1, "reference_name": ("in", list(receipt_items)), "parenttype": doctype},
|
||||
)
|
||||
|
||||
def __update_consumed_materials(self, doctype, return_consumed_items=False):
|
||||
"""Deduct the consumed materials from the available materials."""
|
||||
|
||||
scr_items = self.__get_received_items(doctype)
|
||||
if not scr_items:
|
||||
receipt_items = self.__get_received_items(doctype)
|
||||
if not receipt_items:
|
||||
return ([], {}) if return_consumed_items else None
|
||||
|
||||
scr_items = {
|
||||
item.name: item.get(self.get("sco_field") or "subcontracting_order") for item in scr_items
|
||||
receipt_items = {
|
||||
item.name: item.get(self.subcontract_data.order_field) for item in receipt_items
|
||||
}
|
||||
consumed_materials = self.__get_consumed_items(doctype, scr_items.keys())
|
||||
consumed_materials = self.__get_consumed_items(doctype, receipt_items.keys())
|
||||
|
||||
if return_consumed_items:
|
||||
return (consumed_materials, scr_items)
|
||||
return (consumed_materials, receipt_items)
|
||||
|
||||
for row in consumed_materials:
|
||||
key = (row.rm_item_code, row.main_item_code, scr_items.get(row.reference_name))
|
||||
key = (row.rm_item_code, row.main_item_code, receipt_items.get(row.reference_name))
|
||||
if not self.available_materials.get(key):
|
||||
continue
|
||||
|
||||
@@ -226,16 +259,16 @@ class SubcontractingController(StockController):
|
||||
def get_available_materials(self):
|
||||
"""Get the available raw materials which has been transferred to the supplier.
|
||||
available_materials = {
|
||||
(item_code, subcontracted_item, subcontracting_order): {
|
||||
(item_code, subcontracted_item, subcontract_order): {
|
||||
'qty': 1, 'serial_no': [ABC], 'batch_no': {'batch1': 1}, 'data': item_details
|
||||
}
|
||||
}
|
||||
"""
|
||||
if not self.subcontracting_orders:
|
||||
if not self.subcontract_orders:
|
||||
return
|
||||
|
||||
for row in self.__get_transferred_items():
|
||||
key = (row.rm_item_code, row.main_item_code, row.subcontracting_order)
|
||||
key = (row.rm_item_code, row.main_item_code, row.get(self.subcontract_data.order_field))
|
||||
|
||||
if key not in self.available_materials:
|
||||
self.available_materials.setdefault(
|
||||
@@ -246,14 +279,16 @@ class SubcontractingController(StockController):
|
||||
"serial_no": [],
|
||||
"batch_no": defaultdict(float),
|
||||
"item_details": row,
|
||||
"sco_rm_details": [],
|
||||
f"{self.subcontract_data.rm_detail_field}s": [],
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
details = self.available_materials[key]
|
||||
details.qty += row.qty
|
||||
details.sco_rm_details.append(row.sco_rm_detail)
|
||||
details[f"{self.subcontract_data.rm_detail_field}s"].append(
|
||||
row.get(self.subcontract_data.rm_detail_field)
|
||||
)
|
||||
|
||||
if row.serial_no:
|
||||
details.serial_no.extend(get_serial_nos(row.serial_no))
|
||||
@@ -264,7 +299,11 @@ class SubcontractingController(StockController):
|
||||
self.__set_alternative_item_details(row)
|
||||
|
||||
self.__transferred_items = copy.deepcopy(self.available_materials)
|
||||
self.__update_consumed_materials("Subcontracting Receipt")
|
||||
if self.get("is_old_subcontracting_flow"):
|
||||
for doctype in ["Purchase Receipt", "Purchase Invoice"]:
|
||||
self.__update_consumed_materials(doctype)
|
||||
else:
|
||||
self.__update_consumed_materials("Subcontracting Receipt")
|
||||
|
||||
def __remove_changed_rows(self):
|
||||
if not self.__changed_name:
|
||||
@@ -317,7 +356,7 @@ class SubcontractingController(StockController):
|
||||
)
|
||||
|
||||
def __update_reserve_warehouse(self, row, item):
|
||||
if self.doctype == "Subcontracting Order":
|
||||
if self.doctype == self.subcontract_data.order_doctype:
|
||||
row.reserve_warehouse = self.set_reserve_warehouse or item.warehouse
|
||||
|
||||
def __set_alternative_item(self, bom_item):
|
||||
@@ -325,7 +364,7 @@ class SubcontractingController(StockController):
|
||||
bom_item.update(self.alternative_item_details[bom_item.rm_item_code])
|
||||
|
||||
def __set_serial_nos(self, item_row, rm_obj):
|
||||
key = (rm_obj.rm_item_code, item_row.item_code, item_row.subcontracting_order)
|
||||
key = (rm_obj.rm_item_code, item_row.item_code, item_row.get(self.subcontract_data.order_field))
|
||||
if self.available_materials.get(key) and self.available_materials[key]["serial_no"]:
|
||||
used_serial_nos = self.available_materials[key]["serial_no"][0 : cint(rm_obj.consumed_qty)]
|
||||
rm_obj.serial_no = "\n".join(used_serial_nos)
|
||||
@@ -340,7 +379,7 @@ class SubcontractingController(StockController):
|
||||
"consumed_qty": qty,
|
||||
"batch_no": batch_no,
|
||||
"required_qty": qty,
|
||||
"subcontracting_order": item_row.subcontracting_order,
|
||||
self.subcontract_data.order_field: item_row.get(self.subcontract_data.order_field),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -351,7 +390,7 @@ class SubcontractingController(StockController):
|
||||
rm_obj.consumed_qty = consumed_qty
|
||||
|
||||
def __set_batch_nos(self, bom_item, item_row, rm_obj, qty):
|
||||
key = (rm_obj.rm_item_code, item_row.item_code, item_row.subcontracting_order)
|
||||
key = (rm_obj.rm_item_code, item_row.item_code, item_row.get(self.subcontract_data.order_field))
|
||||
|
||||
if self.available_materials.get(key) and self.available_materials[key]["batch_no"]:
|
||||
new_rm_obj = None
|
||||
@@ -397,16 +436,18 @@ class SubcontractingController(StockController):
|
||||
)
|
||||
rm_obj.rate = get_incoming_rate(args)
|
||||
|
||||
if self.doctype == "Subcontracting Order":
|
||||
if self.doctype == self.subcontract_data.order_doctype:
|
||||
rm_obj.required_qty = qty
|
||||
rm_obj.amount = rm_obj.required_qty * rm_obj.rate
|
||||
else:
|
||||
rm_obj.consumed_qty = 0
|
||||
rm_obj.subcontracting_order = item_row.subcontracting_order
|
||||
setattr(
|
||||
rm_obj, self.subcontract_data.order_field, item_row.get(self.subcontract_data.order_field)
|
||||
)
|
||||
self.__set_batch_nos(bom_item, item_row, rm_obj, qty)
|
||||
|
||||
def __get_qty_based_on_material_transfer(self, item_row, transfer_item):
|
||||
key = (item_row.item_code, item_row.subcontracting_order)
|
||||
key = (item_row.item_code, item_row.get(self.subcontract_data.order_field))
|
||||
|
||||
if self.qty_to_be_received == item_row.qty:
|
||||
return transfer_item.qty
|
||||
@@ -427,13 +468,13 @@ class SubcontractingController(StockController):
|
||||
|
||||
has_supplied_items = True if self.get(self.raw_material_table) else False
|
||||
for row in self.items:
|
||||
if self.doctype != "Subcontracting Order" and (
|
||||
if self.doctype != self.subcontract_data.order_doctype and (
|
||||
(self.__changed_name and row.name not in self.__changed_name)
|
||||
or (has_supplied_items and not self.__changed_name)
|
||||
):
|
||||
continue
|
||||
|
||||
if self.doctype == "Subcontracting Order" or self.backflush_based_on == "BOM":
|
||||
if self.doctype == self.subcontract_data.order_doctype or self.backflush_based_on == "BOM":
|
||||
for bom_item in self.__get_materials_from_bom(
|
||||
row.item_code, row.bom, row.get("include_exploded_items")
|
||||
):
|
||||
@@ -445,17 +486,22 @@ class SubcontractingController(StockController):
|
||||
|
||||
elif self.backflush_based_on != "BOM":
|
||||
for key, transfer_item in self.available_materials.items():
|
||||
if (key[1], key[2]) == (row.item_code, row.subcontracting_order) and transfer_item.qty > 0:
|
||||
if (key[1], key[2]) == (
|
||||
row.item_code,
|
||||
row.get(self.subcontract_data.order_field),
|
||||
) and transfer_item.qty > 0:
|
||||
qty = self.__get_qty_based_on_material_transfer(row, transfer_item) or 0
|
||||
transfer_item.qty -= qty
|
||||
self.__add_supplied_item(row, transfer_item.get("item_details"), qty)
|
||||
|
||||
if self.qty_to_be_received:
|
||||
self.qty_to_be_received[(row.item_code, row.subcontracting_order)] -= row.qty
|
||||
self.qty_to_be_received[
|
||||
(row.item_code, row.get(self.subcontract_data.order_field))
|
||||
] -= row.qty
|
||||
|
||||
def __prepare_supplied_items(self):
|
||||
self.initialized_fields()
|
||||
self.__get_subcontracting_orders()
|
||||
self.__get_subcontract_orders()
|
||||
self.__get_pending_qty_to_receive()
|
||||
self.get_available_materials()
|
||||
self.__remove_changed_rows()
|
||||
@@ -465,8 +511,10 @@ class SubcontractingController(StockController):
|
||||
if row.get("batch_no") and row.get("batch_no") not in self.__transferred_items.get(key).get(
|
||||
"batch_no"
|
||||
):
|
||||
link = get_link_to_form("Subcontracting Order", row.subcontracting_order)
|
||||
msg = f'The Batch No {frappe.bold(row.get("batch_no"))} has not supplied against the Subcontracting Order {link}'
|
||||
link = get_link_to_form(
|
||||
self.subcontract_data.order_doctype, row.get(self.subcontract_data.order_field)
|
||||
)
|
||||
msg = f'The Batch No {frappe.bold(row.get("batch_no"))} has not supplied against the {self.subcontract_data.order_doctype} {link}'
|
||||
frappe.throw(_(msg), title=_("Incorrect Batch Consumed"))
|
||||
|
||||
def __validate_serial_no(self, row, key):
|
||||
@@ -476,16 +524,18 @@ class SubcontractingController(StockController):
|
||||
|
||||
if incorrect_sn:
|
||||
incorrect_sn = "\n".join(incorrect_sn)
|
||||
link = get_link_to_form("Subcontracting Order", row.subcontracting_order)
|
||||
msg = f"The Serial Nos {incorrect_sn} has not supplied against the Subcontracting Order {link}"
|
||||
link = get_link_to_form(
|
||||
self.subcontract_data.order_doctype, row.get(self.subcontract_data.order_field)
|
||||
)
|
||||
msg = f"The Serial Nos {incorrect_sn} has not supplied against the {self.subcontract_data.order_doctype} {link}"
|
||||
frappe.throw(_(msg), title=_("Incorrect Serial Number Consumed"))
|
||||
|
||||
def __validate_supplied_items(self):
|
||||
if self.doctype != "Subcontracting Receipt":
|
||||
if self.doctype not in ["Purchase Invoice", "Purchase Receipt", "Subcontracting Receipt"]:
|
||||
return
|
||||
|
||||
for row in self.get(self.raw_material_table):
|
||||
key = (row.rm_item_code, row.main_item_code, row.subcontracting_order)
|
||||
key = (row.rm_item_code, row.main_item_code, row.get(self.subcontract_data.order_field))
|
||||
if not self.__transferred_items or not self.__transferred_items.get(key):
|
||||
return
|
||||
|
||||
@@ -493,6 +543,9 @@ class SubcontractingController(StockController):
|
||||
self.__validate_serial_no(row, key)
|
||||
|
||||
def set_materials_for_subcontracted_items(self, raw_material_table):
|
||||
if self.doctype == "Purchase Invoice" and not self.update_stock:
|
||||
return
|
||||
|
||||
self.raw_material_table = raw_material_table
|
||||
self.__identify_change_in_item_table()
|
||||
self.__prepare_supplied_items()
|
||||
@@ -501,16 +554,16 @@ class SubcontractingController(StockController):
|
||||
def create_raw_materials_supplied(self, raw_material_table="supplied_items"):
|
||||
self.set_materials_for_subcontracted_items(raw_material_table)
|
||||
|
||||
if self.doctype == "Subcontracting Receipt":
|
||||
if self.doctype in ["Subcontracting Receipt", "Purchase Receipt", "Purchase Invoice"]:
|
||||
for item in self.get("items"):
|
||||
item.rm_supp_cost = 0.0
|
||||
|
||||
def __update_consumed_qty_in_sco(self, itemwise_consumed_qty):
|
||||
def __update_consumed_qty_in_subcontract_order(self, itemwise_consumed_qty):
|
||||
fields = ["main_item_code", "rm_item_code", "parent", "supplied_qty", "name"]
|
||||
filters = {"docstatus": 1, "parent": ("in", self.subcontracting_orders)}
|
||||
filters = {"docstatus": 1, "parent": ("in", self.subcontract_orders)}
|
||||
|
||||
for row in frappe.get_all(
|
||||
"Subcontracting Order Supplied Item", fields=fields, filters=filters, order_by="idx"
|
||||
self.subcontract_data.order_supplied_items_field, fields=fields, filters=filters, order_by="idx"
|
||||
):
|
||||
key = (row.rm_item_code, row.main_item_code, row.parent)
|
||||
consumed_qty = itemwise_consumed_qty.get(key, 0)
|
||||
@@ -520,22 +573,31 @@ class SubcontractingController(StockController):
|
||||
|
||||
itemwise_consumed_qty[key] -= consumed_qty
|
||||
frappe.db.set_value(
|
||||
"Subcontracting Order Supplied Item", row.name, "consumed_qty", consumed_qty
|
||||
self.subcontract_data.order_supplied_items_field, row.name, "consumed_qty", consumed_qty
|
||||
)
|
||||
|
||||
def set_consumed_qty_in_sco(self):
|
||||
# Update consumed qty back in the subcontracting order
|
||||
self.__get_subcontracting_orders()
|
||||
itemwise_consumed_qty = defaultdict(float)
|
||||
consumed_items, scr_items = self.__update_consumed_materials(
|
||||
"Subcontracting Receipt", return_consumed_items=True
|
||||
)
|
||||
def set_consumed_qty_in_subcontract_order(self):
|
||||
# Update consumed qty back in the subcontract order
|
||||
if self.doctype in ["Subcontracting Order", "Subcontracting Receipt"] or self.get(
|
||||
"is_old_subcontracting_flow"
|
||||
):
|
||||
self.__get_subcontract_orders()
|
||||
itemwise_consumed_qty = defaultdict(float)
|
||||
if self.get("is_old_subcontracting_flow"):
|
||||
doctypes = ["Purchase Receipt", "Purchase Invoice"]
|
||||
else:
|
||||
doctypes = ["Subcontracting Receipt"]
|
||||
|
||||
for row in consumed_items:
|
||||
key = (row.rm_item_code, row.main_item_code, scr_items.get(row.reference_name))
|
||||
itemwise_consumed_qty[key] += row.consumed_qty
|
||||
for doctype in doctypes:
|
||||
consumed_items, receipt_items = self.__update_consumed_materials(
|
||||
doctype, return_consumed_items=True
|
||||
)
|
||||
|
||||
self.__update_consumed_qty_in_sco(itemwise_consumed_qty)
|
||||
for row in consumed_items:
|
||||
key = (row.rm_item_code, row.main_item_code, receipt_items.get(row.reference_name))
|
||||
itemwise_consumed_qty[key] += row.consumed_qty
|
||||
|
||||
self.__update_consumed_qty_in_subcontract_order(itemwise_consumed_qty)
|
||||
|
||||
def update_ordered_and_reserved_qty(self):
|
||||
sco_map = {}
|
||||
@@ -618,10 +680,30 @@ class SubcontractingController(StockController):
|
||||
via_landed_cost_voucher=via_landed_cost_voucher,
|
||||
)
|
||||
|
||||
def get_supplied_items_cost(self, item_row_id):
|
||||
def get_supplied_items_cost(self, item_row_id, reset_outgoing_rate=True):
|
||||
supplied_items_cost = 0.0
|
||||
for item in self.get("supplied_items"):
|
||||
if item.reference_name == item_row_id:
|
||||
if (
|
||||
self.get("is_old_subcontracting_flow")
|
||||
and reset_outgoing_rate
|
||||
and frappe.get_cached_value("Item", item.rm_item_code, "is_stock_item")
|
||||
):
|
||||
rate = get_incoming_rate(
|
||||
{
|
||||
"item_code": item.rm_item_code,
|
||||
"warehouse": self.supplier_warehouse,
|
||||
"posting_date": self.posting_date,
|
||||
"posting_time": self.posting_time,
|
||||
"qty": -1 * item.consumed_qty,
|
||||
"serial_no": item.serial_no,
|
||||
"batch_no": item.batch_no,
|
||||
}
|
||||
)
|
||||
|
||||
if rate > 0:
|
||||
item.rate = rate
|
||||
|
||||
item.amount = flt(flt(item.consumed_qty) * flt(item.rate), item.precision("amount"))
|
||||
supplied_items_cost += item.amount
|
||||
|
||||
@@ -631,13 +713,25 @@ class SubcontractingController(StockController):
|
||||
if self.doctype == "Subcontracting Order":
|
||||
self.update_status()
|
||||
elif self.doctype == "Subcontracting Receipt":
|
||||
self.__get_subcontracting_orders
|
||||
self.__get_subcontract_orders
|
||||
|
||||
if self.subcontracting_orders:
|
||||
for sco in set(self.subcontracting_orders):
|
||||
if self.subcontract_orders:
|
||||
for sco in set(self.subcontract_orders):
|
||||
sco_doc = frappe.get_doc("Subcontracting Order", sco)
|
||||
sco_doc.update_status()
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_current_stock(self):
|
||||
if self.doctype in ["Purchase Receipt", "Subcontracting Receipt"]:
|
||||
for item in self.get("supplied_items"):
|
||||
if self.supplier_warehouse:
|
||||
actual_qty = frappe.db.get_value(
|
||||
"Bin",
|
||||
{"item_code": item.rm_item_code, "warehouse": self.supplier_warehouse},
|
||||
"actual_qty",
|
||||
)
|
||||
item.current_stock = flt(actual_qty) or 0
|
||||
|
||||
@property
|
||||
def sub_contracted_items(self):
|
||||
if not hasattr(self, "_sub_contracted_items"):
|
||||
@@ -650,3 +744,159 @@ class SubcontractingController(StockController):
|
||||
self._sub_contracted_items = [item.name for item in items]
|
||||
|
||||
return self._sub_contracted_items
|
||||
|
||||
|
||||
def get_item_details(items):
|
||||
item = frappe.qb.DocType("Item")
|
||||
item_list = (
|
||||
frappe.qb.from_(item)
|
||||
.select(item.item_code, item.description, item.allow_alternative_item)
|
||||
.where(item.name.isin(items))
|
||||
.run(as_dict=True)
|
||||
)
|
||||
|
||||
item_details = {}
|
||||
for item in item_list:
|
||||
item_details[item.item_code] = item
|
||||
|
||||
return item_details
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_rm_stock_entry(subcontract_order, rm_items, order_doctype="Subcontracting Order"):
|
||||
rm_items_list = rm_items
|
||||
|
||||
if isinstance(rm_items, str):
|
||||
rm_items_list = json.loads(rm_items)
|
||||
elif not rm_items:
|
||||
frappe.throw(_("No Items available for transfer"))
|
||||
|
||||
if rm_items_list:
|
||||
fg_items = list(set(item["item_code"] for item in rm_items_list))
|
||||
else:
|
||||
frappe.throw(_("No Items selected for transfer"))
|
||||
|
||||
if subcontract_order:
|
||||
subcontract_order = frappe.get_doc(order_doctype, subcontract_order)
|
||||
|
||||
if fg_items:
|
||||
items = tuple(set(item["rm_item_code"] for item in rm_items_list))
|
||||
item_wh = get_item_details(items)
|
||||
|
||||
stock_entry = frappe.new_doc("Stock Entry")
|
||||
stock_entry.purpose = "Send to Subcontractor"
|
||||
if order_doctype == "Purchase Order":
|
||||
stock_entry.purchase_order = subcontract_order.name
|
||||
else:
|
||||
stock_entry.subcontracting_order = subcontract_order.name
|
||||
stock_entry.supplier = subcontract_order.supplier
|
||||
stock_entry.supplier_name = subcontract_order.supplier_name
|
||||
stock_entry.supplier_address = subcontract_order.supplier_address
|
||||
stock_entry.address_display = subcontract_order.address_display
|
||||
stock_entry.company = subcontract_order.company
|
||||
stock_entry.to_warehouse = subcontract_order.supplier_warehouse
|
||||
stock_entry.set_stock_entry_type()
|
||||
|
||||
if order_doctype == "Purchase Order":
|
||||
rm_detail_field = "po_detail"
|
||||
else:
|
||||
rm_detail_field = "sco_rm_detail"
|
||||
|
||||
for item_code in fg_items:
|
||||
for rm_item_data in rm_items_list:
|
||||
if rm_item_data["item_code"] == item_code:
|
||||
rm_item_code = rm_item_data["rm_item_code"]
|
||||
items_dict = {
|
||||
rm_item_code: {
|
||||
rm_detail_field: rm_item_data.get("name"),
|
||||
"item_name": rm_item_data["item_name"],
|
||||
"description": item_wh.get(rm_item_code, {}).get("description", ""),
|
||||
"qty": rm_item_data["qty"],
|
||||
"from_warehouse": rm_item_data["warehouse"],
|
||||
"stock_uom": rm_item_data["stock_uom"],
|
||||
"serial_no": rm_item_data.get("serial_no"),
|
||||
"batch_no": rm_item_data.get("batch_no"),
|
||||
"main_item_code": rm_item_data["item_code"],
|
||||
"allow_alternative_item": item_wh.get(rm_item_code, {}).get("allow_alternative_item"),
|
||||
}
|
||||
}
|
||||
stock_entry.add_to_stock_entry_detail(items_dict)
|
||||
return stock_entry.as_dict()
|
||||
else:
|
||||
frappe.throw(_("No Items selected for transfer"))
|
||||
return subcontract_order.name
|
||||
|
||||
|
||||
def add_items_in_ste(
|
||||
ste_doc, row, qty, rm_details, rm_detail_field="sco_rm_detail", batch_no=None
|
||||
):
|
||||
item = ste_doc.append("items", row.item_details)
|
||||
|
||||
rm_detail = list(set(row.get(f"{rm_detail_field}s")).intersection(rm_details))
|
||||
item.update(
|
||||
{
|
||||
"qty": qty,
|
||||
"batch_no": batch_no,
|
||||
"basic_rate": row.item_details["rate"],
|
||||
rm_detail_field: rm_detail[0] if rm_detail else "",
|
||||
"s_warehouse": row.item_details["t_warehouse"],
|
||||
"t_warehouse": row.item_details["s_warehouse"],
|
||||
"item_code": row.item_details["rm_item_code"],
|
||||
"subcontracted_item": row.item_details["main_item_code"],
|
||||
"serial_no": "\n".join(row.serial_no) if row.serial_no else "",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def make_return_stock_entry_for_subcontract(
|
||||
available_materials, order_doc, rm_details, order_doctype="Subcontracting Order"
|
||||
):
|
||||
ste_doc = frappe.new_doc("Stock Entry")
|
||||
ste_doc.purpose = "Material Transfer"
|
||||
|
||||
if order_doctype == "Purchase Order":
|
||||
ste_doc.purchase_order = order_doc.name
|
||||
rm_detail_field = "po_detail"
|
||||
else:
|
||||
ste_doc.subcontracting_order = order_doc.name
|
||||
rm_detail_field = "sco_rm_detail"
|
||||
ste_doc.company = order_doc.company
|
||||
ste_doc.is_return = 1
|
||||
|
||||
for key, value in available_materials.items():
|
||||
if not value.qty:
|
||||
continue
|
||||
|
||||
if value.batch_no:
|
||||
for batch_no, qty in value.batch_no.items():
|
||||
if qty > 0:
|
||||
add_items_in_ste(ste_doc, value, value.qty, rm_details, rm_detail_field, batch_no)
|
||||
else:
|
||||
add_items_in_ste(ste_doc, value, value.qty, rm_details, rm_detail_field)
|
||||
|
||||
ste_doc.set_stock_entry_type()
|
||||
ste_doc.calculate_rate_and_amount()
|
||||
|
||||
return ste_doc
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_materials_from_supplier(
|
||||
subcontract_order, rm_details, order_doctype="Subcontracting Order"
|
||||
):
|
||||
if isinstance(rm_details, str):
|
||||
rm_details = json.loads(rm_details)
|
||||
|
||||
doc = frappe.get_cached_doc(order_doctype, subcontract_order)
|
||||
doc.initialized_fields()
|
||||
doc.subcontract_orders = [doc.name]
|
||||
doc.get_available_materials()
|
||||
|
||||
if not doc.available_materials:
|
||||
frappe.throw(
|
||||
_("Materials are already received against the {0} {1}").format(order_doctype, subcontract_order)
|
||||
)
|
||||
|
||||
return make_return_stock_entry_for_subcontract(
|
||||
doc.available_materials, doc, rm_details, order_doctype
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user