feat: serial and batch bundle for Subcontracting
This commit is contained in:
parent
e6143abb8a
commit
5ddd55a8ae
@ -714,8 +714,11 @@ class StockController(AccountsController):
|
|||||||
message = self.prepare_over_receipt_message(rule, values)
|
message = self.prepare_over_receipt_message(rule, values)
|
||||||
frappe.throw(msg=message, title=_("Over Receipt"))
|
frappe.throw(msg=message, title=_("Over Receipt"))
|
||||||
|
|
||||||
def set_serial_and_batch_bundle(self):
|
def set_serial_and_batch_bundle(self, table_name=None):
|
||||||
for row in self.items:
|
if not table_name:
|
||||||
|
table_name = "items"
|
||||||
|
|
||||||
|
for row in self.get(table_name):
|
||||||
if row.serial_and_batch_bundle:
|
if row.serial_and_batch_bundle:
|
||||||
frappe.get_doc(
|
frappe.get_doc(
|
||||||
"Serial and Batch Bundle", row.serial_and_batch_bundle
|
"Serial and Batch Bundle", row.serial_and_batch_bundle
|
||||||
|
|||||||
@ -11,6 +11,9 @@ from frappe.model.mapper import get_mapped_doc
|
|||||||
from frappe.utils import cint, cstr, flt, get_link_to_form
|
from frappe.utils import cint, cstr, flt, get_link_to_form
|
||||||
|
|
||||||
from erpnext.controllers.stock_controller import StockController
|
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.doctype.serial_no.serial_no import get_serial_nos
|
||||||
from erpnext.stock.utils import get_incoming_rate
|
from erpnext.stock.utils import get_incoming_rate
|
||||||
|
|
||||||
@ -48,6 +51,7 @@ class SubcontractingController(StockController):
|
|||||||
if self.doctype in ["Subcontracting Order", "Subcontracting Receipt"]:
|
if self.doctype in ["Subcontracting Order", "Subcontracting Receipt"]:
|
||||||
self.validate_items()
|
self.validate_items()
|
||||||
self.create_raw_materials_supplied()
|
self.create_raw_materials_supplied()
|
||||||
|
self.set_serial_and_batch_bundle("supplied_items")
|
||||||
else:
|
else:
|
||||||
super(SubcontractingController, self).validate()
|
super(SubcontractingController, self).validate()
|
||||||
|
|
||||||
@ -169,7 +173,11 @@ class SubcontractingController(StockController):
|
|||||||
self.qty_to_be_received[(row.item_code, row.parent)] += row.qty
|
self.qty_to_be_received[(row.item_code, row.parent)] += row.qty
|
||||||
|
|
||||||
def __get_transferred_items(self):
|
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 = {
|
alias_dict = {
|
||||||
"item_code": "rm_item_code",
|
"item_code": "rm_item_code",
|
||||||
"subcontracted_item": "main_item_code",
|
"subcontracted_item": "main_item_code",
|
||||||
@ -234,9 +242,11 @@ class SubcontractingController(StockController):
|
|||||||
"serial_no",
|
"serial_no",
|
||||||
"rm_item_code",
|
"rm_item_code",
|
||||||
"reference_name",
|
"reference_name",
|
||||||
|
"serial_and_batch_bundle",
|
||||||
"batch_no",
|
"batch_no",
|
||||||
"consumed_qty",
|
"consumed_qty",
|
||||||
"main_item_code",
|
"main_item_code",
|
||||||
|
"parent as voucher_no",
|
||||||
],
|
],
|
||||||
filters={"docstatus": 1, "reference_name": ("in", list(receipt_items)), "parenttype": doctype},
|
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())
|
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:
|
if return_consumed_items:
|
||||||
return (consumed_materials, receipt_items)
|
return (consumed_materials, receipt_items)
|
||||||
|
|
||||||
@ -262,11 +279,26 @@ class SubcontractingController(StockController):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
self.available_materials[key]["qty"] -= row.consumed_qty
|
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:
|
if row.serial_no:
|
||||||
self.available_materials[key]["serial_no"] = list(
|
self.available_materials[key]["serial_no"] = list(
|
||||||
set(self.available_materials[key]["serial_no"]) - set(get_serial_nos(row.serial_no))
|
set(self.available_materials[key]["serial_no"]) - set(get_serial_nos(row.serial_no))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Will be deperecated in v16
|
||||||
if row.batch_no:
|
if row.batch_no:
|
||||||
self.available_materials[key]["batch_no"][row.batch_no] -= row.consumed_qty
|
self.available_materials[key]["batch_no"][row.batch_no] -= row.consumed_qty
|
||||||
|
|
||||||
@ -281,7 +313,16 @@ class SubcontractingController(StockController):
|
|||||||
if not self.subcontract_orders:
|
if not self.subcontract_orders:
|
||||||
return
|
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))
|
key = (row.rm_item_code, row.main_item_code, row.get(self.subcontract_data.order_field))
|
||||||
|
|
||||||
if key not in self.available_materials:
|
if key not in self.available_materials:
|
||||||
@ -310,6 +351,17 @@ class SubcontractingController(StockController):
|
|||||||
if row.batch_no:
|
if row.batch_no:
|
||||||
details.batch_no[row.batch_no] += row.qty
|
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.__set_alternative_item_details(row)
|
||||||
|
|
||||||
self.__transferred_items = copy.deepcopy(self.available_materials)
|
self.__transferred_items = copy.deepcopy(self.available_materials)
|
||||||
@ -327,6 +379,7 @@ class SubcontractingController(StockController):
|
|||||||
self.set(self.raw_material_table, [])
|
self.set(self.raw_material_table, [])
|
||||||
for item in self._doc_before_save.supplied_items:
|
for item in self._doc_before_save.supplied_items:
|
||||||
if item.reference_name in self.__changed_name:
|
if item.reference_name in self.__changed_name:
|
||||||
|
self.__remove_serial_and_batch_bundle(item)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if item.reference_name not in self.__reference_name:
|
if item.reference_name not in self.__reference_name:
|
||||||
@ -337,6 +390,10 @@ class SubcontractingController(StockController):
|
|||||||
|
|
||||||
i += 1
|
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):
|
def __get_materials_from_bom(self, item_code, bom_no, exploded_item=0):
|
||||||
doctype = "BOM Item" if not exploded_item else "BOM Explosion Item"
|
doctype = "BOM Item" if not exploded_item else "BOM Explosion Item"
|
||||||
fields = [f"`tab{doctype}`.`stock_qty` / `tabBOM`.`quantity` as qty_consumed_per_unit"]
|
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.required_qty = required_qty
|
||||||
rm_obj.consumed_qty = consumed_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))
|
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"]:
|
if (
|
||||||
new_rm_obj = None
|
not self.available_materials[key]["serial_no"] and not self.available_materials[key]["batch_no"]
|
||||||
for batch_no, batch_qty in self.available_materials[key]["batch_no"].items():
|
):
|
||||||
if batch_qty >= qty or (
|
return
|
||||||
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)
|
|
||||||
|
|
||||||
self.__set_batch_no_as_per_qty(item_row, rm_obj, batch_no, qty)
|
bundle = frappe.get_doc(
|
||||||
self.available_materials[key]["batch_no"][batch_no] -= qty
|
{
|
||||||
return
|
"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:
|
if self.available_materials.get(key) and self.available_materials[key]["serial_no"]:
|
||||||
qty -= batch_qty
|
self.__set_serial_nos_for_bundle(bundle, qty, key)
|
||||||
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 abs(qty) > 0 and not new_rm_obj:
|
elif self.available_materials.get(key) and self.available_materials[key]["batch_no"]:
|
||||||
self.__set_consumed_qty(rm_obj, qty)
|
self.__set_batch_nos_for_bundle(bundle, qty, key)
|
||||||
else:
|
|
||||||
self.__set_consumed_qty(rm_obj, qty, bom_item.required_qty or qty)
|
bundle.flags.ignore_links = True
|
||||||
self.__set_serial_nos(item_row, rm_obj)
|
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):
|
def __add_supplied_item(self, item_row, bom_item, qty):
|
||||||
bom_item.conversion_factor = item_row.conversion_factor
|
bom_item.conversion_factor = item_row.conversion_factor
|
||||||
rm_obj = self.append(self.raw_material_table, bom_item)
|
rm_obj = self.append(self.raw_material_table, bom_item)
|
||||||
rm_obj.reference_name = item_row.name
|
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":
|
if self.doctype == "Subcontracting Receipt":
|
||||||
args = frappe._dict(
|
args = frappe._dict(
|
||||||
{
|
{
|
||||||
@ -447,25 +550,21 @@ class SubcontractingController(StockController):
|
|||||||
"posting_date": self.posting_date,
|
"posting_date": self.posting_date,
|
||||||
"posting_time": self.posting_time,
|
"posting_time": self.posting_time,
|
||||||
"qty": -1 * flt(rm_obj.consumed_qty),
|
"qty": -1 * flt(rm_obj.consumed_qty),
|
||||||
"serial_no": rm_obj.serial_no,
|
"actual_qty": -1 * flt(rm_obj.consumed_qty),
|
||||||
"batch_no": rm_obj.batch_no,
|
|
||||||
"voucher_type": self.doctype,
|
"voucher_type": self.doctype,
|
||||||
"voucher_no": self.name,
|
"voucher_no": self.name,
|
||||||
|
"voucher_detail_no": item_row.name,
|
||||||
"company": self.company,
|
"company": self.company,
|
||||||
"allow_zero_valuation": 1,
|
"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.serial_and_batch_bundle = self.__set_serial_and_batch_bundle(item_row, rm_obj, qty)
|
||||||
rm_obj.required_qty = qty
|
|
||||||
rm_obj.amount = rm_obj.required_qty * rm_obj.rate
|
if rm_obj.serial_and_batch_bundle:
|
||||||
else:
|
args["serial_and_batch_bundle"] = rm_obj.serial_and_batch_bundle
|
||||||
rm_obj.consumed_qty = 0
|
|
||||||
setattr(
|
rm_obj.rate = bom_item.rate if self.backflush_based_on == "BOM" else get_incoming_rate(args)
|
||||||
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):
|
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))
|
key = (item_row.item_code, item_row.get(self.subcontract_data.order_field))
|
||||||
|
|||||||
@ -891,10 +891,11 @@ erpnext.SerialNoBatchBundleUpdate = class SerialNoBatchBundleUpdate {
|
|||||||
doc: this.frm.doc,
|
doc: this.frm.doc,
|
||||||
}
|
}
|
||||||
}).then(r => {
|
}).then(r => {
|
||||||
debugger
|
|
||||||
this.callback && this.callback(r.message);
|
this.callback && this.callback(r.message);
|
||||||
this.dialog.hide();
|
this.dialog.hide();
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
frappe.msgprint(__('Please save the document first'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,8 @@ from frappe.utils import flt
|
|||||||
|
|
||||||
|
|
||||||
class DeprecatedSerialNoValuation:
|
class DeprecatedSerialNoValuation:
|
||||||
|
# Will be deperecated in v16
|
||||||
|
|
||||||
def calculate_stock_value_from_deprecarated_ledgers(self):
|
def calculate_stock_value_from_deprecarated_ledgers(self):
|
||||||
serial_nos = list(
|
serial_nos = list(
|
||||||
filter(lambda x: x not in self.serial_no_incoming_rate and x, self.get_serial_nos())
|
filter(lambda x: x not in self.serial_no_incoming_rate and x, self.get_serial_nos())
|
||||||
|
|||||||
@ -19,12 +19,14 @@ class SerialandBatchBundle(Document):
|
|||||||
self.validate_serial_and_batch_no()
|
self.validate_serial_and_batch_no()
|
||||||
self.validate_duplicate_serial_and_batch_no()
|
self.validate_duplicate_serial_and_batch_no()
|
||||||
self.validate_voucher_no()
|
self.validate_voucher_no()
|
||||||
|
self.validate_serial_nos()
|
||||||
|
|
||||||
def before_save(self):
|
def before_save(self):
|
||||||
self.set_total_qty()
|
self.set_total_qty()
|
||||||
self.set_is_outward()
|
self.set_is_outward()
|
||||||
self.set_warehouse()
|
self.set_warehouse()
|
||||||
self.set_incoming_rate()
|
self.set_incoming_rate()
|
||||||
|
self.validate_qty_and_stock_value_difference()
|
||||||
|
|
||||||
if self.ledgers:
|
if self.ledgers:
|
||||||
self.set_avg_rate()
|
self.set_avg_rate()
|
||||||
@ -35,6 +37,17 @@ class SerialandBatchBundle(Document):
|
|||||||
else:
|
else:
|
||||||
self.set_incoming_rate_for_inward_transaction(row, save)
|
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):
|
def set_incoming_rate_for_outward_transaction(self, row=None, save=False):
|
||||||
sle = self.get_sle_for_outward_transaction(row)
|
sle = self.get_sle_for_outward_transaction(row)
|
||||||
if self.has_serial_no:
|
if self.has_serial_no:
|
||||||
@ -53,12 +66,12 @@ class SerialandBatchBundle(Document):
|
|||||||
|
|
||||||
for d in self.ledgers:
|
for d in self.ledgers:
|
||||||
if self.has_serial_no:
|
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:
|
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:
|
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:
|
if save:
|
||||||
d.db_set(
|
d.db_set(
|
||||||
@ -73,7 +86,7 @@ class SerialandBatchBundle(Document):
|
|||||||
"item_code": self.item_code,
|
"item_code": self.item_code,
|
||||||
"warehouse": self.warehouse,
|
"warehouse": self.warehouse,
|
||||||
"serial_and_batch_bundle": self.name,
|
"serial_and_batch_bundle": self.name,
|
||||||
"actual_qty": self.total_qty * -1,
|
"actual_qty": self.total_qty,
|
||||||
"company": self.company,
|
"company": self.company,
|
||||||
"serial_nos": [row.serial_no for row in self.ledgers if row.serial_no],
|
"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},
|
"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)
|
self.set_incoming_rate(save=True, row=row)
|
||||||
|
|
||||||
def validate_voucher_no(self):
|
def validate_voucher_no(self):
|
||||||
|
if self.is_new():
|
||||||
|
return
|
||||||
|
|
||||||
if not (self.voucher_type and self.voucher_no):
|
if not (self.voucher_type and self.voucher_no):
|
||||||
return
|
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):
|
def validate_quantity(self, row):
|
||||||
self.set_total_qty(save=True)
|
self.set_total_qty(save=True)
|
||||||
|
|
||||||
precision = row.precision
|
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(
|
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(
|
doc.append(
|
||||||
"ledgers",
|
"ledgers",
|
||||||
{
|
{
|
||||||
"qty": row.qty or 1.0,
|
"qty": (row.qty or 1.0) * (1 if type_of_transaction == "Inward" else -1),
|
||||||
"warehouse": warehouse,
|
"warehouse": warehouse,
|
||||||
"batch_no": row.batch_no,
|
"batch_no": row.batch_no,
|
||||||
"serial_no": row.serial_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]:
|
def get_voucher_wise_serial_batch_from_bundle(**kwargs) -> Dict[str, Dict]:
|
||||||
data = get_ledgers_from_serial_batch_bundle(**kwargs)
|
data = get_ledgers_from_serial_batch_bundle(**kwargs)
|
||||||
|
if not data:
|
||||||
|
return {}
|
||||||
|
|
||||||
group_by_voucher = {}
|
group_by_voucher = {}
|
||||||
|
|
||||||
for row in data:
|
for row in data:
|
||||||
key = (row.item_code, row.warehouse, row.voucher_no)
|
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:
|
if key not in group_by_voucher:
|
||||||
group_by_voucher.setdefault(
|
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]
|
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():
|
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 key in ["name", "item_code", "warehouse", "voucher_no", "company", "voucher_detail_no"]:
|
||||||
if isinstance(val, list):
|
if isinstance(val, list):
|
||||||
query = query.where(bundle_table[key].isin(val))
|
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)
|
query = query.where(serial_batch_table[key] == val)
|
||||||
|
|
||||||
return query.run(as_dict=True)
|
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)
|
||||||
|
|||||||
@ -1120,6 +1120,8 @@ erpnext.stock.select_batch_and_serial_no = (frm, item) => {
|
|||||||
frm.refresh_fields();
|
frm.refresh_fields();
|
||||||
frappe.model.set_value(item.doctype, item.name,
|
frappe.model.set_value(item.doctype, item.name,
|
||||||
"serial_and_batch_bundle", r.name);
|
"serial_and_batch_bundle", r.name);
|
||||||
|
|
||||||
|
frm.save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@ -779,7 +779,6 @@ class StockEntry(StockController):
|
|||||||
if reset_outgoing_rate:
|
if reset_outgoing_rate:
|
||||||
args = self.get_args_for_incoming_rate(d)
|
args = self.get_args_for_incoming_rate(d)
|
||||||
rate = get_incoming_rate(args, raise_error_if_no_rate)
|
rate = get_incoming_rate(args, raise_error_if_no_rate)
|
||||||
print(rate, "set rate for outgoing items")
|
|
||||||
if rate > 0:
|
if rate > 0:
|
||||||
d.basic_rate = rate
|
d.basic_rate = rate
|
||||||
|
|
||||||
@ -1223,6 +1222,14 @@ class StockEntry(StockController):
|
|||||||
if d.serial_and_batch_bundle and self.docstatus == 1:
|
if d.serial_and_batch_bundle and self.docstatus == 1:
|
||||||
self.copy_serial_and_batch_bundle(sle, d)
|
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)
|
sl_entries.append(sle)
|
||||||
|
|
||||||
def copy_serial_and_batch_bundle(self, sle, child):
|
def copy_serial_and_batch_bundle(self, sle, child):
|
||||||
@ -1240,9 +1247,17 @@ class StockEntry(StockController):
|
|||||||
bundle_doc.type_of_transaction = "Inward"
|
bundle_doc.type_of_transaction = "Inward"
|
||||||
|
|
||||||
for row in bundle_doc.ledgers:
|
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.warehouse = child.t_warehouse
|
||||||
row.is_outward = 0
|
row.is_outward = 0
|
||||||
|
|
||||||
|
bundle_doc.set_total_qty()
|
||||||
|
bundle_doc.set_avg_rate()
|
||||||
bundle_doc.flags.ignore_permissions = True
|
bundle_doc.flags.ignore_permissions = True
|
||||||
bundle_doc.submit()
|
bundle_doc.submit()
|
||||||
sle.serial_and_batch_bundle = bundle_doc.name
|
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:
|
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)
|
batchwise_serial_nos = get_batchwise_serial_nos(child.item_code, row)
|
||||||
for batch_no, qty in row.batches_to_be_consume.items():
|
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,
|
"batch_no": batch_no,
|
||||||
"serial_no": batchwise_serial_nos.get(batch_no).pop(0),
|
"serial_no": batchwise_serial_nos.get(batch_no).pop(0),
|
||||||
"warehouse": row.warehouse,
|
"warehouse": row.warehouse,
|
||||||
"qty": qty,
|
"qty": -1,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
elif row.serial_nos:
|
elif row.serial_nos:
|
||||||
|
doc.has_serial_no = 1
|
||||||
for serial_no in row.serial_nos:
|
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:
|
elif row.batches_to_be_consume:
|
||||||
|
doc.has_batch_no = 1
|
||||||
for batch_no, qty in row.batches_to_be_consume.items():
|
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
|
return doc.insert(ignore_permissions=True).name
|
||||||
|
|
||||||
|
|||||||
@ -20,13 +20,13 @@
|
|||||||
"serial_and_batch_bundle",
|
"serial_and_batch_bundle",
|
||||||
"batch_no",
|
"batch_no",
|
||||||
"column_break_11",
|
"column_break_11",
|
||||||
|
"current_serial_and_batch_bundle",
|
||||||
"serial_no",
|
"serial_no",
|
||||||
"section_break_3",
|
"section_break_3",
|
||||||
"current_qty",
|
"current_qty",
|
||||||
"current_amount",
|
"current_amount",
|
||||||
"column_break_9",
|
"column_break_9",
|
||||||
"current_valuation_rate",
|
"current_valuation_rate",
|
||||||
"current_serial_and_batch_bundle",
|
|
||||||
"current_serial_no",
|
"current_serial_no",
|
||||||
"section_break_14",
|
"section_break_14",
|
||||||
"quantity_difference",
|
"quantity_difference",
|
||||||
@ -192,7 +192,7 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "serial_and_batch_bundle",
|
"fieldname": "serial_and_batch_bundle",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Serial and Batch Bundle",
|
"label": "Serial / Batch Bundle",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "Serial and Batch Bundle",
|
"options": "Serial and Batch Bundle",
|
||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
|
|||||||
@ -6,7 +6,6 @@ from frappe import _, bold
|
|||||||
from frappe.model.naming import make_autoname
|
from frappe.model.naming import make_autoname
|
||||||
from frappe.query_builder.functions import Sum
|
from frappe.query_builder.functions import Sum
|
||||||
from frappe.utils import cint, flt, now
|
from frappe.utils import cint, flt, now
|
||||||
from pypika import Case
|
|
||||||
|
|
||||||
from erpnext.stock.deprecated_serial_batch import (
|
from erpnext.stock.deprecated_serial_batch import (
|
||||||
DeprecatedBatchNoValuation,
|
DeprecatedBatchNoValuation,
|
||||||
@ -209,13 +208,18 @@ class SerialBatchBundle:
|
|||||||
frappe.db.bulk_insert("Serial and Batch Ledger", fields=fields, values=set(ledgers))
|
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):
|
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(
|
sn_doc.append(
|
||||||
"ledgers",
|
"ledgers",
|
||||||
{
|
{
|
||||||
"batch_no": batch_no,
|
"batch_no": batch_no,
|
||||||
"qty": self.sle.actual_qty,
|
"qty": self.sle.actual_qty,
|
||||||
"incoming_rate": incoming_rate,
|
"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(
|
frappe.db.set_value(
|
||||||
"Serial and Batch Bundle",
|
"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": ""},
|
{"is_cancelled": 1, "voucher_no": ""},
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -303,22 +307,24 @@ class SerialBatchBundle:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def post_process(self):
|
def post_process(self):
|
||||||
if not self.sle.is_cancelled:
|
if self.item_details.has_serial_no == 1:
|
||||||
if self.item_details.has_serial_no == 1:
|
self.set_warehouse_and_status_in_serial_nos()
|
||||||
self.set_warehouse_and_status_in_serial_nos()
|
|
||||||
|
|
||||||
if self.item_details.has_serial_no == 1 and self.item_details.has_batch_no == 1:
|
if (
|
||||||
self.set_batch_no_in_serial_nos()
|
self.sle.actual_qty > 0
|
||||||
else:
|
and self.item_details.has_serial_no == 1
|
||||||
pass
|
and self.item_details.has_batch_no == 1
|
||||||
# self.set_data_based_on_last_sle()
|
):
|
||||||
|
self.set_batch_no_in_serial_nos()
|
||||||
|
|
||||||
def set_warehouse_and_status_in_serial_nos(self):
|
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
|
warehouse = self.warehouse if self.sle.actual_qty > 0 else None
|
||||||
|
|
||||||
sn_table = frappe.qb.DocType("Serial No")
|
if not serial_nos:
|
||||||
serial_nos = get_serial_nos(self.sle.serial_and_batch_bundle, check_outward=False)
|
return
|
||||||
|
|
||||||
|
sn_table = frappe.qb.DocType("Serial No")
|
||||||
(
|
(
|
||||||
frappe.qb.update(sn_table)
|
frappe.qb.update(sn_table)
|
||||||
.set(sn_table.warehouse, warehouse)
|
.set(sn_table.warehouse, warehouse)
|
||||||
@ -330,7 +336,7 @@ class SerialBatchBundle:
|
|||||||
ledgers = frappe.get_all(
|
ledgers = frappe.get_all(
|
||||||
"Serial and Batch Ledger",
|
"Serial and Batch Ledger",
|
||||||
fields=["serial_no", "batch_no"],
|
fields=["serial_no", "batch_no"],
|
||||||
filters={"parent": self.serial_and_batch_bundle},
|
filters={"parent": self.sle.serial_and_batch_bundle},
|
||||||
)
|
)
|
||||||
|
|
||||||
batch_serial_nos = {}
|
batch_serial_nos = {}
|
||||||
@ -391,7 +397,7 @@ class SerialNoBundleValuation(DeprecatedSerialNoValuation):
|
|||||||
TIMESTAMP(
|
TIMESTAMP(
|
||||||
parent.posting_date, parent.posting_time
|
parent.posting_date, parent.posting_time
|
||||||
)
|
)
|
||||||
), child.name
|
), child.name, child.serial_no, child.warehouse
|
||||||
FROM
|
FROM
|
||||||
`tabSerial and Batch Bundle` as parent,
|
`tabSerial and Batch Bundle` as parent,
|
||||||
`tabSerial and Batch Ledger` as child
|
`tabSerial and Batch Ledger` as child
|
||||||
@ -417,14 +423,18 @@ class SerialNoBundleValuation(DeprecatedSerialNoValuation):
|
|||||||
return frappe.db.sql(
|
return frappe.db.sql(
|
||||||
f"""
|
f"""
|
||||||
SELECT
|
SELECT
|
||||||
serial_no, incoming_rate
|
ledger.serial_no, ledger.incoming_rate, ledger.warehouse
|
||||||
FROM
|
FROM
|
||||||
`tabSerial and Batch Ledger` AS ledger,
|
`tabSerial and Batch Ledger` AS ledger,
|
||||||
({subquery}) AS SubQuery
|
({subquery}) AS SubQuery
|
||||||
WHERE
|
WHERE
|
||||||
ledger.name = SubQuery.name
|
ledger.name = SubQuery.name
|
||||||
|
AND ledger.serial_no = SubQuery.serial_no
|
||||||
|
AND ledger.warehouse = SubQuery.warehouse
|
||||||
GROUP BY
|
GROUP BY
|
||||||
ledger.serial_no
|
ledger.serial_no
|
||||||
|
Order By
|
||||||
|
ledger.creation
|
||||||
""",
|
""",
|
||||||
as_dict=1,
|
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)
|
return is_rejected(self.sle.voucher_type, self.sle.voucher_detail_no, self.sle.warehouse)
|
||||||
|
|
||||||
def get_incoming_rate(self):
|
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):
|
def is_rejected(voucher_type, voucher_detail_no, warehouse):
|
||||||
@ -517,7 +527,7 @@ class BatchNoBundleValuation(DeprecatedBatchNoValuation):
|
|||||||
.select(
|
.select(
|
||||||
child.batch_no,
|
child.batch_no,
|
||||||
Sum(child.stock_value_difference).as_("incoming_rate"),
|
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(
|
.where(
|
||||||
(child.batch_no.isin(batch_nos))
|
(child.batch_no.isin(batch_nos))
|
||||||
@ -544,7 +554,7 @@ class BatchNoBundleValuation(DeprecatedBatchNoValuation):
|
|||||||
def set_stock_value_difference(self):
|
def set_stock_value_difference(self):
|
||||||
self.stock_value_change = 0
|
self.stock_value_change = 0
|
||||||
for batch_no, ledger in self.batch_nos.items():
|
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
|
self.stock_value_change += stock_value_change
|
||||||
frappe.db.set_value(
|
frappe.db.set_value(
|
||||||
"Serial and Batch Ledger", ledger.name, "stock_value_difference", stock_value_change
|
"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
|
self.wh_data.qty_after_transaction += self.sle.actual_qty
|
||||||
|
|
||||||
def get_incoming_rate(self):
|
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))
|
||||||
|
|
||||||
|
|
||||||
class GetAvailableSerialBatchBundle:
|
|
||||||
def __init__(self) -> None:
|
|
||||||
pass
|
|
||||||
|
|||||||
@ -7,6 +7,7 @@ frappe.provide('erpnext.buying');
|
|||||||
|
|
||||||
frappe.ui.form.on('Subcontracting Receipt', {
|
frappe.ui.form.on('Subcontracting Receipt', {
|
||||||
setup: (frm) => {
|
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.cannot_add_rows = true;
|
||||||
frm.get_field('supplied_items').grid.only_sortable();
|
frm.get_field('supplied_items').grid.only_sortable();
|
||||||
|
|
||||||
|
|||||||
@ -105,7 +105,12 @@ class SubcontractingReceipt(SubcontractingController):
|
|||||||
self.update_status()
|
self.update_status()
|
||||||
|
|
||||||
def on_cancel(self):
|
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_status_updater_args()
|
||||||
self.update_prevdoc_status()
|
self.update_prevdoc_status()
|
||||||
self.update_stock_ledger()
|
self.update_stock_ledger()
|
||||||
|
|||||||
@ -33,6 +33,7 @@
|
|||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
|
"columns": 2,
|
||||||
"fieldname": "main_item_code",
|
"fieldname": "main_item_code",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
@ -41,6 +42,7 @@
|
|||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"columns": 2,
|
||||||
"fieldname": "rm_item_code",
|
"fieldname": "rm_item_code",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
@ -77,14 +79,16 @@
|
|||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"columns": 1,
|
||||||
"fieldname": "required_qty",
|
"fieldname": "required_qty",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
|
"in_list_view": 1,
|
||||||
"label": "Required Qty",
|
"label": "Required Qty",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"columns": 2,
|
"columns": 1,
|
||||||
"fieldname": "consumed_qty",
|
"fieldname": "consumed_qty",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
@ -102,6 +106,7 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "rate",
|
"fieldname": "rate",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
|
"in_list_view": 1,
|
||||||
"label": "Rate",
|
"label": "Rate",
|
||||||
"options": "Company:company:default_currency",
|
"options": "Company:company:default_currency",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
@ -124,7 +129,6 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "current_stock",
|
"fieldname": "current_stock",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Current Stock",
|
"label": "Current Stock",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
@ -188,25 +192,25 @@
|
|||||||
"default": "0",
|
"default": "0",
|
||||||
"fieldname": "available_qty_for_consumption",
|
"fieldname": "available_qty_for_consumption",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Available Qty For Consumption",
|
"label": "Available Qty For Consumption",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"columns": 2,
|
||||||
"fieldname": "serial_and_batch_bundle",
|
"fieldname": "serial_and_batch_bundle",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Serial and Batch Bundle",
|
"in_list_view": 1,
|
||||||
|
"label": "Serial / Batch Bundle",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "Serial and Batch Bundle",
|
"options": "Serial and Batch Bundle",
|
||||||
"print_hide": 1,
|
"print_hide": 1
|
||||||
"read_only": 1
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-03-12 14:11:48.816699",
|
"modified": "2023-03-15 13:55:08.132626",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Subcontracting",
|
"module": "Subcontracting",
|
||||||
"name": "Subcontracting Receipt Supplied Item",
|
"name": "Subcontracting Receipt Supplied Item",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user