feat: serial and batch bundle for Subcontracting

This commit is contained in:
Rohit Waghchaure 2023-03-16 12:58:48 +05:30
parent e6143abb8a
commit 5ddd55a8ae
12 changed files with 320 additions and 89 deletions

View File

@ -714,8 +714,11 @@ class StockController(AccountsController):
message = self.prepare_over_receipt_message(rule, values)
frappe.throw(msg=message, title=_("Over Receipt"))
def set_serial_and_batch_bundle(self):
for row in self.items:
def set_serial_and_batch_bundle(self, table_name=None):
if not table_name:
table_name = "items"
for row in self.get(table_name):
if row.serial_and_batch_bundle:
frappe.get_doc(
"Serial and Batch Bundle", row.serial_and_batch_bundle

View File

@ -11,6 +11,9 @@ from frappe.model.mapper import get_mapped_doc
from frappe.utils import cint, cstr, flt, get_link_to_form
from erpnext.controllers.stock_controller import StockController
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
get_voucher_wise_serial_batch_from_bundle,
)
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
from erpnext.stock.utils import get_incoming_rate
@ -48,6 +51,7 @@ class SubcontractingController(StockController):
if self.doctype in ["Subcontracting Order", "Subcontracting Receipt"]:
self.validate_items()
self.create_raw_materials_supplied()
self.set_serial_and_batch_bundle("supplied_items")
else:
super(SubcontractingController, self).validate()
@ -169,7 +173,11 @@ class SubcontractingController(StockController):
self.qty_to_be_received[(row.item_code, row.parent)] += row.qty
def __get_transferred_items(self):
fields = [f"`tabStock Entry`.`{self.subcontract_data.order_field}`"]
fields = [
f"`tabStock Entry`.`{self.subcontract_data.order_field}`",
"`tabStock Entry`.`name` as voucher_no",
]
alias_dict = {
"item_code": "rm_item_code",
"subcontracted_item": "main_item_code",
@ -234,9 +242,11 @@ class SubcontractingController(StockController):
"serial_no",
"rm_item_code",
"reference_name",
"serial_and_batch_bundle",
"batch_no",
"consumed_qty",
"main_item_code",
"parent as voucher_no",
],
filters={"docstatus": 1, "reference_name": ("in", list(receipt_items)), "parenttype": doctype},
)
@ -253,6 +263,13 @@ class SubcontractingController(StockController):
}
consumed_materials = self.__get_consumed_items(doctype, receipt_items.keys())
voucher_nos = [d.voucher_no for d in consumed_materials if d.voucher_no]
voucher_bundle_data = get_voucher_wise_serial_batch_from_bundle(
voucher_no=voucher_nos,
is_outward=1,
get_subcontracted_item=("Subcontracting Receipt Supplied Item", "main_item_code"),
)
if return_consumed_items:
return (consumed_materials, receipt_items)
@ -262,11 +279,26 @@ class SubcontractingController(StockController):
continue
self.available_materials[key]["qty"] -= row.consumed_qty
bundle_key = (row.rm_item_code, row.main_item_code, self.supplier_warehouse, row.voucher_no)
consumed_bundles = voucher_bundle_data.get(bundle_key, frappe._dict())
if consumed_bundles.serial_nos:
self.available_materials[key]["serial_no"] = list(
set(self.available_materials[key]["serial_no"]) - set(consumed_bundles.serial_nos)
)
if consumed_bundles.batch_nos:
for batch_no, qty in consumed_bundles.batch_nos.items():
self.available_materials[key]["batch_no"][batch_no] -= abs(qty)
# Will be deperecated in v16
if row.serial_no:
self.available_materials[key]["serial_no"] = list(
set(self.available_materials[key]["serial_no"]) - set(get_serial_nos(row.serial_no))
)
# Will be deperecated in v16
if row.batch_no:
self.available_materials[key]["batch_no"][row.batch_no] -= row.consumed_qty
@ -281,7 +313,16 @@ class SubcontractingController(StockController):
if not self.subcontract_orders:
return
for row in self.__get_transferred_items():
transferred_items = self.__get_transferred_items()
voucher_nos = [row.voucher_no for row in transferred_items]
voucher_bundle_data = get_voucher_wise_serial_batch_from_bundle(
voucher_no=voucher_nos,
is_outward=0,
get_subcontracted_item=("Stock Entry Detail", "subcontracted_item"),
)
for row in transferred_items:
key = (row.rm_item_code, row.main_item_code, row.get(self.subcontract_data.order_field))
if key not in self.available_materials:
@ -310,6 +351,17 @@ class SubcontractingController(StockController):
if row.batch_no:
details.batch_no[row.batch_no] += row.qty
if voucher_bundle_data:
bundle_key = (row.rm_item_code, row.main_item_code, row.t_warehouse, row.voucher_no)
bundle_data = voucher_bundle_data.get(bundle_key, frappe._dict())
if bundle_data.serial_nos:
details.serial_no.extend(bundle_data.serial_nos)
if bundle_data.batch_nos:
for batch_no, qty in bundle_data.batch_nos.items():
details.batch_no[batch_no] += qty
self.__set_alternative_item_details(row)
self.__transferred_items = copy.deepcopy(self.available_materials)
@ -327,6 +379,7 @@ class SubcontractingController(StockController):
self.set(self.raw_material_table, [])
for item in self._doc_before_save.supplied_items:
if item.reference_name in self.__changed_name:
self.__remove_serial_and_batch_bundle(item)
continue
if item.reference_name not in self.__reference_name:
@ -337,6 +390,10 @@ class SubcontractingController(StockController):
i += 1
def __remove_serial_and_batch_bundle(self, item):
if item.serial_and_batch_bundle:
frappe.delete_doc("Serial and Batch Bundle", item.serial_and_batch_bundle, force=True)
def __get_materials_from_bom(self, item_code, bom_no, exploded_item=0):
doctype = "BOM Item" if not exploded_item else "BOM Explosion Item"
fields = [f"`tab{doctype}`.`stock_qty` / `tabBOM`.`quantity` as qty_consumed_per_unit"]
@ -403,42 +460,88 @@ class SubcontractingController(StockController):
rm_obj.required_qty = required_qty
rm_obj.consumed_qty = consumed_qty
def __set_batch_nos(self, bom_item, item_row, rm_obj, qty):
def __set_serial_and_batch_bundle(self, item_row, rm_obj, qty):
key = (rm_obj.rm_item_code, item_row.item_code, item_row.get(self.subcontract_data.order_field))
if not self.available_materials.get(key):
return
if self.available_materials.get(key) and self.available_materials[key]["batch_no"]:
new_rm_obj = None
for batch_no, batch_qty in self.available_materials[key]["batch_no"].items():
if batch_qty >= qty or (
rm_obj.consumed_qty == 0
and self.backflush_based_on == "BOM"
and len(self.available_materials[key]["batch_no"]) == 1
):
if rm_obj.consumed_qty == 0:
self.__set_consumed_qty(rm_obj, qty)
if (
not self.available_materials[key]["serial_no"] and not self.available_materials[key]["batch_no"]
):
return
self.__set_batch_no_as_per_qty(item_row, rm_obj, batch_no, qty)
self.available_materials[key]["batch_no"][batch_no] -= qty
return
bundle = frappe.get_doc(
{
"doctype": "Serial and Batch Bundle",
"company": self.company,
"item_code": rm_obj.rm_item_code,
"warehouse": self.supplier_warehouse,
"posting_date": self.posting_date,
"posting_time": self.posting_time,
"voucher_type": "Subcontracting Receipt",
"voucher_no": self.name,
"type_of_transaction": "Outward",
}
)
elif qty > 0 and batch_qty > 0:
qty -= batch_qty
new_rm_obj = self.append(self.raw_material_table, bom_item)
new_rm_obj.reference_name = item_row.name
self.__set_batch_no_as_per_qty(item_row, new_rm_obj, batch_no, batch_qty)
self.available_materials[key]["batch_no"][batch_no] = 0
if self.available_materials.get(key) and self.available_materials[key]["serial_no"]:
self.__set_serial_nos_for_bundle(bundle, qty, key)
if abs(qty) > 0 and not new_rm_obj:
self.__set_consumed_qty(rm_obj, qty)
else:
self.__set_consumed_qty(rm_obj, qty, bom_item.required_qty or qty)
self.__set_serial_nos(item_row, rm_obj)
elif self.available_materials.get(key) and self.available_materials[key]["batch_no"]:
self.__set_batch_nos_for_bundle(bundle, qty, key)
bundle.flags.ignore_links = True
bundle.flags.ignore_mandatory = True
bundle.save(ignore_permissions=True)
return bundle.name
def __set_batch_nos_for_bundle(self, bundle, qty, key):
bundle.has_batch_no = 1
for batch_no, batch_qty in self.available_materials[key]["batch_no"].items():
qty_to_consumed = 0
if qty > 0:
if batch_qty >= qty:
qty_to_consumed = qty
else:
qty_to_consumed = batch_qty
qty -= qty_to_consumed
if qty_to_consumed > 0:
bundle.append("ledgers", {"batch_no": batch_no, "qty": qty_to_consumed * -1})
def __set_serial_nos_for_bundle(self, bundle, qty, key):
bundle.has_serial_no = 1
used_serial_nos = self.available_materials[key]["serial_no"][0 : cint(qty)]
# Removed the used serial nos from the list
for sn in used_serial_nos:
batch_no = ""
if self.available_materials[key]["batch_no"]:
bundle.has_batch_no = 1
batch_no = frappe.get_cached_value("Serial No", sn, "batch_no")
if batch_no:
self.available_materials[key]["batch_no"][batch_no] -= 1
bundle.append("ledgers", {"serial_no": sn, "batch_no": batch_no, "qty": -1})
self.available_materials[key]["serial_no"].remove(sn)
def __add_supplied_item(self, item_row, bom_item, qty):
bom_item.conversion_factor = item_row.conversion_factor
rm_obj = self.append(self.raw_material_table, bom_item)
rm_obj.reference_name = item_row.name
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 = qty
rm_obj.required_qty = bom_item.required_qty or qty
setattr(
rm_obj, self.subcontract_data.order_field, item_row.get(self.subcontract_data.order_field)
)
if self.doctype == "Subcontracting Receipt":
args = frappe._dict(
{
@ -447,25 +550,21 @@ class SubcontractingController(StockController):
"posting_date": self.posting_date,
"posting_time": self.posting_time,
"qty": -1 * flt(rm_obj.consumed_qty),
"serial_no": rm_obj.serial_no,
"batch_no": rm_obj.batch_no,
"actual_qty": -1 * flt(rm_obj.consumed_qty),
"voucher_type": self.doctype,
"voucher_no": self.name,
"voucher_detail_no": item_row.name,
"company": self.company,
"allow_zero_valuation": 1,
}
)
rm_obj.rate = bom_item.rate if self.backflush_based_on == "BOM" else get_incoming_rate(args)
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
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)
rm_obj.serial_and_batch_bundle = self.__set_serial_and_batch_bundle(item_row, rm_obj, qty)
if rm_obj.serial_and_batch_bundle:
args["serial_and_batch_bundle"] = rm_obj.serial_and_batch_bundle
rm_obj.rate = bom_item.rate if self.backflush_based_on == "BOM" else get_incoming_rate(args)
def __get_qty_based_on_material_transfer(self, item_row, transfer_item):
key = (item_row.item_code, item_row.get(self.subcontract_data.order_field))

View File

@ -891,10 +891,11 @@ erpnext.SerialNoBatchBundleUpdate = class SerialNoBatchBundleUpdate {
doc: this.frm.doc,
}
}).then(r => {
debugger
this.callback && this.callback(r.message);
this.dialog.hide();
})
} else {
frappe.msgprint(__('Please save the document first'));
}
}

View File

@ -4,6 +4,8 @@ from frappe.utils import flt
class DeprecatedSerialNoValuation:
# Will be deperecated in v16
def calculate_stock_value_from_deprecarated_ledgers(self):
serial_nos = list(
filter(lambda x: x not in self.serial_no_incoming_rate and x, self.get_serial_nos())

View File

@ -19,12 +19,14 @@ class SerialandBatchBundle(Document):
self.validate_serial_and_batch_no()
self.validate_duplicate_serial_and_batch_no()
self.validate_voucher_no()
self.validate_serial_nos()
def before_save(self):
self.set_total_qty()
self.set_is_outward()
self.set_warehouse()
self.set_incoming_rate()
self.validate_qty_and_stock_value_difference()
if self.ledgers:
self.set_avg_rate()
@ -35,6 +37,17 @@ class SerialandBatchBundle(Document):
else:
self.set_incoming_rate_for_inward_transaction(row, save)
def validate_qty_and_stock_value_difference(self):
if self.type_of_transaction != "Outward":
return
for d in self.ledgers:
if d.qty and d.qty > 0:
d.qty *= -1
if d.stock_value_difference and d.stock_value_difference > 0:
d.stock_value_difference *= -1
def set_incoming_rate_for_outward_transaction(self, row=None, save=False):
sle = self.get_sle_for_outward_transaction(row)
if self.has_serial_no:
@ -53,12 +66,12 @@ class SerialandBatchBundle(Document):
for d in self.ledgers:
if self.has_serial_no:
d.incoming_rate = sn_obj.serial_no_incoming_rate.get(d.serial_no, 0.0)
d.incoming_rate = abs(sn_obj.serial_no_incoming_rate.get(d.serial_no, 0.0))
else:
d.incoming_rate = sn_obj.batch_avg_rate.get(d.batch_no)
d.incoming_rate = abs(sn_obj.batch_avg_rate.get(d.batch_no))
if self.has_batch_no:
d.stock_value_difference = flt(d.qty) * flt(d.incoming_rate) * -1
d.stock_value_difference = flt(d.qty) * flt(d.incoming_rate)
if save:
d.db_set(
@ -73,7 +86,7 @@ class SerialandBatchBundle(Document):
"item_code": self.item_code,
"warehouse": self.warehouse,
"serial_and_batch_bundle": self.name,
"actual_qty": self.total_qty * -1,
"actual_qty": self.total_qty,
"company": self.company,
"serial_nos": [row.serial_no for row in self.ledgers if row.serial_no],
"batch_nos": {row.batch_no: row for row in self.ledgers if row.batch_no},
@ -126,6 +139,9 @@ class SerialandBatchBundle(Document):
self.set_incoming_rate(save=True, row=row)
def validate_voucher_no(self):
if self.is_new():
return
if not (self.voucher_type and self.voucher_no):
return
@ -150,14 +166,22 @@ class SerialandBatchBundle(Document):
)
)
def validate_serial_nos(self):
if not self.has_serial_no:
return
def validate_quantity(self, row):
self.set_total_qty(save=True)
precision = row.precision
if abs(flt(self.total_qty, precision) - flt(row.qty, precision)) > 0.01:
qty_field = "qty"
if self.voucher_type in ["Subcontracting Receipt"]:
qty_field = "consumed_qty"
if abs(flt(self.total_qty, precision)) - abs(flt(row.get(qty_field), precision)) > 0.01:
frappe.throw(
_(
f"Total quantity {self.total_qty} in the Serial and Batch Bundle {self.name} does not match with the Item {row.item_code} in the {self.voucher_type} # {self.voucher_no}"
f"Total quantity {self.total_qty} in the Serial and Batch Bundle {self.name} does not match with the Item {self.item_code} in the {self.voucher_type} # {self.voucher_no}"
)
)
@ -368,7 +392,7 @@ def create_serial_batch_no_ledgers(ledgers, child_row, parent_doc) -> object:
doc.append(
"ledgers",
{
"qty": row.qty or 1.0,
"qty": (row.qty or 1.0) * (1 if type_of_transaction == "Inward" else -1),
"warehouse": warehouse,
"batch_no": row.batch_no,
"serial_no": row.serial_no,
@ -535,14 +559,24 @@ def get_available_batches(kwargs):
def get_voucher_wise_serial_batch_from_bundle(**kwargs) -> Dict[str, Dict]:
data = get_ledgers_from_serial_batch_bundle(**kwargs)
if not data:
return {}
group_by_voucher = {}
for row in data:
key = (row.item_code, row.warehouse, row.voucher_no)
if kwargs.get("get_subcontracted_item"):
# get_subcontracted_item = ("doctype", "field_name")
doctype, field_name = kwargs.get("get_subcontracted_item")
subcontracted_item_code = frappe.get_cached_value(doctype, row.voucher_detail_no, field_name)
key = (row.item_code, subcontracted_item_code, row.warehouse, row.voucher_no)
if key not in group_by_voucher:
group_by_voucher.setdefault(
key, {"serial_nos": [], "batch_nos": collections.defaultdict(float)}
key,
frappe._dict({"serial_nos": [], "batch_nos": collections.defaultdict(float), "item_row": row}),
)
child_row = group_by_voucher[key]
@ -579,6 +613,9 @@ def get_ledgers_from_serial_batch_bundle(**kwargs) -> List[frappe._dict]:
)
for key, val in kwargs.items():
if key in ["get_subcontracted_item"]:
continue
if key in ["name", "item_code", "warehouse", "voucher_no", "company", "voucher_detail_no"]:
if isinstance(val, list):
query = query.where(bundle_table[key].isin(val))
@ -593,3 +630,56 @@ def get_ledgers_from_serial_batch_bundle(**kwargs) -> List[frappe._dict]:
query = query.where(serial_batch_table[key] == val)
return query.run(as_dict=True)
def get_available_serial_nos(item_code, warehouse):
filters = {
"item_code": item_code,
"warehouse": ("is", "set"),
}
fields = ["name", "warehouse", "batch_no"]
if warehouse:
filters["warehouse"] = warehouse
return frappe.get_all("Serial No", filters=filters, fields=fields)
def get_available_batch_nos(item_code, warehouse):
sl_entries = get_stock_ledger_entries(item_code, warehouse)
batchwise_qty = collections.defaultdict(float)
precision = frappe.get_precision("Stock Ledger Entry", "qty")
for entry in sl_entries:
batchwise_qty[entry.batch_no] += flt(entry.qty, precision)
def get_stock_ledger_entries(item_code, warehouse):
stock_ledger_entry = frappe.qb.DocType("Stock Ledger Entry")
batch_ledger = frappe.qb.DocType("Serial and Batch Ledger")
return (
frappe.qb.from_(stock_ledger_entry)
.left_join(batch_ledger)
.on(stock_ledger_entry.serial_and_batch_bundle == batch_ledger.parent)
.select(
stock_ledger_entry.warehouse,
stock_ledger_entry.item_code,
Sum(
Case()
.when(stock_ledger_entry.serial_and_batch_bundle, batch_ledger.qty)
.else_(stock_ledger_entry.actual_qty)
.as_("qty")
),
Case()
.when(stock_ledger_entry.serial_and_batch_bundle, batch_ledger.batch_no)
.else_(stock_ledger_entry.batch_no)
.as_("batch_no"),
)
.where(
(stock_ledger_entry.item_code == item_code)
& (stock_ledger_entry.warehouse == warehouse)
& (stock_ledger_entry.is_cancelled == 0)
)
).run(as_dict=True)

View File

@ -1120,6 +1120,8 @@ erpnext.stock.select_batch_and_serial_no = (frm, item) => {
frm.refresh_fields();
frappe.model.set_value(item.doctype, item.name,
"serial_and_batch_bundle", r.name);
frm.save();
}
}
);

View File

@ -779,7 +779,6 @@ class StockEntry(StockController):
if reset_outgoing_rate:
args = self.get_args_for_incoming_rate(d)
rate = get_incoming_rate(args, raise_error_if_no_rate)
print(rate, "set rate for outgoing items")
if rate > 0:
d.basic_rate = rate
@ -1223,6 +1222,14 @@ class StockEntry(StockController):
if d.serial_and_batch_bundle and self.docstatus == 1:
self.copy_serial_and_batch_bundle(sle, d)
if d.serial_and_batch_bundle and self.docstatus == 2:
bundle_id = frappe.get_cached_value(
"Serial and Batch Bundle", {"voucher_detail_no": d.name, "is_cancelled": 0}, "name"
)
if d.serial_and_batch_bundle != bundle_id:
sle.serial_and_batch_bundle = bundle_id
sl_entries.append(sle)
def copy_serial_and_batch_bundle(self, sle, child):
@ -1240,9 +1247,17 @@ class StockEntry(StockController):
bundle_doc.type_of_transaction = "Inward"
for row in bundle_doc.ledgers:
if row.qty < 0:
row.qty = abs(row.qty)
if row.stock_value_difference < 0:
row.stock_value_difference = abs(row.stock_value_difference)
row.warehouse = child.t_warehouse
row.is_outward = 0
bundle_doc.set_total_qty()
bundle_doc.set_avg_rate()
bundle_doc.flags.ignore_permissions = True
bundle_doc.submit()
sle.serial_and_batch_bundle = bundle_doc.name
@ -2859,6 +2874,8 @@ def create_serial_and_batch_bundle(row, child):
)
if row.serial_nos and row.batches_to_be_consume:
doc.has_serial_no = 1
doc.has_batch_no = 1
batchwise_serial_nos = get_batchwise_serial_nos(child.item_code, row)
for batch_no, qty in row.batches_to_be_consume.items():
@ -2870,17 +2887,19 @@ def create_serial_and_batch_bundle(row, child):
"batch_no": batch_no,
"serial_no": batchwise_serial_nos.get(batch_no).pop(0),
"warehouse": row.warehouse,
"qty": qty,
"qty": -1,
},
)
elif row.serial_nos:
doc.has_serial_no = 1
for serial_no in row.serial_nos:
doc.append("ledgers", {"serial_no": serial_no, "warehouse": row.warehouse, "qty": 1})
doc.append("ledgers", {"serial_no": serial_no, "warehouse": row.warehouse, "qty": -1})
elif row.batches_to_be_consume:
doc.has_batch_no = 1
for batch_no, qty in row.batches_to_be_consume.items():
doc.append("ledgers", {"batch_no": batch_no, "warehouse": row.warehouse, "qty": qty})
doc.append("ledgers", {"batch_no": batch_no, "warehouse": row.warehouse, "qty": qty * -1})
return doc.insert(ignore_permissions=True).name

View File

@ -20,13 +20,13 @@
"serial_and_batch_bundle",
"batch_no",
"column_break_11",
"current_serial_and_batch_bundle",
"serial_no",
"section_break_3",
"current_qty",
"current_amount",
"column_break_9",
"current_valuation_rate",
"current_serial_and_batch_bundle",
"current_serial_no",
"section_break_14",
"quantity_difference",
@ -192,7 +192,7 @@
{
"fieldname": "serial_and_batch_bundle",
"fieldtype": "Link",
"label": "Serial and Batch Bundle",
"label": "Serial / Batch Bundle",
"no_copy": 1,
"options": "Serial and Batch Bundle",
"print_hide": 1

View File

@ -6,7 +6,6 @@ from frappe import _, bold
from frappe.model.naming import make_autoname
from frappe.query_builder.functions import Sum
from frappe.utils import cint, flt, now
from pypika import Case
from erpnext.stock.deprecated_serial_batch import (
DeprecatedBatchNoValuation,
@ -209,13 +208,18 @@ class SerialBatchBundle:
frappe.db.bulk_insert("Serial and Batch Ledger", fields=fields, values=set(ledgers))
def add_batch_no_to_bundle(self, sn_doc, batch_no, incoming_rate):
stock_value_difference = flt(self.sle.actual_qty) * flt(incoming_rate)
if self.sle.actual_qty < 0:
stock_value_difference *= -1
sn_doc.append(
"ledgers",
{
"batch_no": batch_no,
"qty": self.sle.actual_qty,
"incoming_rate": incoming_rate,
"stock_value_difference": flt(self.sle.actual_qty) * flt(incoming_rate),
"stock_value_difference": stock_value_difference,
},
)
@ -286,7 +290,7 @@ class SerialBatchBundle:
frappe.db.set_value(
"Serial and Batch Bundle",
self.sle.serial_and_batch_bundle,
{"voucher_no": self.sle.voucher_no, "voucher_type": self.sle.voucher_type},
{"is_cancelled": 1, "voucher_no": ""},
)
@ -303,22 +307,24 @@ class SerialBatchBundle:
return False
def post_process(self):
if not self.sle.is_cancelled:
if self.item_details.has_serial_no == 1:
self.set_warehouse_and_status_in_serial_nos()
if self.item_details.has_serial_no == 1:
self.set_warehouse_and_status_in_serial_nos()
if self.item_details.has_serial_no == 1 and self.item_details.has_batch_no == 1:
self.set_batch_no_in_serial_nos()
else:
pass
# self.set_data_based_on_last_sle()
if (
self.sle.actual_qty > 0
and self.item_details.has_serial_no == 1
and self.item_details.has_batch_no == 1
):
self.set_batch_no_in_serial_nos()
def set_warehouse_and_status_in_serial_nos(self):
serial_nos = get_serial_nos(self.sle.serial_and_batch_bundle, check_outward=False)
warehouse = self.warehouse if self.sle.actual_qty > 0 else None
sn_table = frappe.qb.DocType("Serial No")
serial_nos = get_serial_nos(self.sle.serial_and_batch_bundle, check_outward=False)
if not serial_nos:
return
sn_table = frappe.qb.DocType("Serial No")
(
frappe.qb.update(sn_table)
.set(sn_table.warehouse, warehouse)
@ -330,7 +336,7 @@ class SerialBatchBundle:
ledgers = frappe.get_all(
"Serial and Batch Ledger",
fields=["serial_no", "batch_no"],
filters={"parent": self.serial_and_batch_bundle},
filters={"parent": self.sle.serial_and_batch_bundle},
)
batch_serial_nos = {}
@ -391,7 +397,7 @@ class SerialNoBundleValuation(DeprecatedSerialNoValuation):
TIMESTAMP(
parent.posting_date, parent.posting_time
)
), child.name
), child.name, child.serial_no, child.warehouse
FROM
`tabSerial and Batch Bundle` as parent,
`tabSerial and Batch Ledger` as child
@ -417,14 +423,18 @@ class SerialNoBundleValuation(DeprecatedSerialNoValuation):
return frappe.db.sql(
f"""
SELECT
serial_no, incoming_rate
ledger.serial_no, ledger.incoming_rate, ledger.warehouse
FROM
`tabSerial and Batch Ledger` AS ledger,
({subquery}) AS SubQuery
WHERE
ledger.name = SubQuery.name
AND ledger.serial_no = SubQuery.serial_no
AND ledger.warehouse = SubQuery.warehouse
GROUP BY
ledger.serial_no
Order By
ledger.creation
""",
as_dict=1,
)
@ -468,7 +478,7 @@ class SerialNoBundleValuation(DeprecatedSerialNoValuation):
return is_rejected(self.sle.voucher_type, self.sle.voucher_detail_no, self.sle.warehouse)
def get_incoming_rate(self):
return flt(self.stock_value_change) / flt(self.sle.actual_qty)
return abs(flt(self.stock_value_change) / flt(self.sle.actual_qty))
def is_rejected(voucher_type, voucher_detail_no, warehouse):
@ -517,7 +527,7 @@ class BatchNoBundleValuation(DeprecatedBatchNoValuation):
.select(
child.batch_no,
Sum(child.stock_value_difference).as_("incoming_rate"),
Sum(Case().when(child.is_outward == 1, child.qty * -1).else_(child.qty)).as_("qty"),
Sum(child.qty).as_("qty"),
)
.where(
(child.batch_no.isin(batch_nos))
@ -544,7 +554,7 @@ class BatchNoBundleValuation(DeprecatedBatchNoValuation):
def set_stock_value_difference(self):
self.stock_value_change = 0
for batch_no, ledger in self.batch_nos.items():
stock_value_change = self.batch_avg_rate[batch_no] * ledger.qty * -1
stock_value_change = self.batch_avg_rate[batch_no] * ledger.qty
self.stock_value_change += stock_value_change
frappe.db.set_value(
"Serial and Batch Ledger", ledger.name, "stock_value_difference", stock_value_change
@ -564,9 +574,4 @@ class BatchNoBundleValuation(DeprecatedBatchNoValuation):
self.wh_data.qty_after_transaction += self.sle.actual_qty
def get_incoming_rate(self):
return flt(self.stock_value_change) / flt(self.sle.actual_qty)
class GetAvailableSerialBatchBundle:
def __init__(self) -> None:
pass
return abs(flt(self.stock_value_change) / flt(self.sle.actual_qty))

View File

@ -7,6 +7,7 @@ frappe.provide('erpnext.buying');
frappe.ui.form.on('Subcontracting Receipt', {
setup: (frm) => {
frm.ignore_doctypes_on_cancel_all = ['Serial and Batch Bundle'];
frm.get_field('supplied_items').grid.cannot_add_rows = true;
frm.get_field('supplied_items').grid.only_sortable();

View File

@ -105,7 +105,12 @@ class SubcontractingReceipt(SubcontractingController):
self.update_status()
def on_cancel(self):
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Repost Item Valuation")
self.ignore_linked_doctypes = (
"GL Entry",
"Stock Ledger Entry",
"Repost Item Valuation",
"Serial and Batch Bundle",
)
self.update_status_updater_args()
self.update_prevdoc_status()
self.update_stock_ledger()

View File

@ -33,6 +33,7 @@
],
"fields": [
{
"columns": 2,
"fieldname": "main_item_code",
"fieldtype": "Link",
"in_list_view": 1,
@ -41,6 +42,7 @@
"read_only": 1
},
{
"columns": 2,
"fieldname": "rm_item_code",
"fieldtype": "Link",
"in_list_view": 1,
@ -77,14 +79,16 @@
"fieldtype": "Column Break"
},
{
"columns": 1,
"fieldname": "required_qty",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Required Qty",
"print_hide": 1,
"read_only": 1
},
{
"columns": 2,
"columns": 1,
"fieldname": "consumed_qty",
"fieldtype": "Float",
"in_list_view": 1,
@ -102,6 +106,7 @@
{
"fieldname": "rate",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Rate",
"options": "Company:company:default_currency",
"read_only": 1
@ -124,7 +129,6 @@
{
"fieldname": "current_stock",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Current Stock",
"read_only": 1
},
@ -188,25 +192,25 @@
"default": "0",
"fieldname": "available_qty_for_consumption",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Available Qty For Consumption",
"print_hide": 1,
"read_only": 1
},
{
"columns": 2,
"fieldname": "serial_and_batch_bundle",
"fieldtype": "Link",
"label": "Serial and Batch Bundle",
"in_list_view": 1,
"label": "Serial / Batch Bundle",
"no_copy": 1,
"options": "Serial and Batch Bundle",
"print_hide": 1,
"read_only": 1
"print_hide": 1
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2023-03-12 14:11:48.816699",
"modified": "2023-03-15 13:55:08.132626",
"modified_by": "Administrator",
"module": "Subcontracting",
"name": "Subcontracting Receipt Supplied Item",