mirror of
https://github.com/frappe/erpnext.git
synced 2026-02-21 18:36:30 +00:00
fix: inward same serial / batches in disassembly which were used
(cherry picked from commit 95e6c72539)
# Conflicts:
# erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
# erpnext/stock/doctype/stock_entry/stock_entry.py
This commit is contained in:
committed by
Mergify
parent
3a2d7d18a3
commit
cfbd71693b
@@ -229,7 +229,8 @@ frappe.ui.form.on("Work Order", {
|
|||||||
if (
|
if (
|
||||||
frm.doc.docstatus === 1 &&
|
frm.doc.docstatus === 1 &&
|
||||||
["Closed", "Completed"].includes(frm.doc.status) &&
|
["Closed", "Completed"].includes(frm.doc.status) &&
|
||||||
frm.doc.produced_qty > 0
|
frm.doc.produced_qty > 0 &&
|
||||||
|
frm.doc.produced_qty > frm.doc.disassembled_qty
|
||||||
) {
|
) {
|
||||||
frm.add_custom_button(
|
frm.add_custom_button(
|
||||||
__("Disassemble Order"),
|
__("Disassemble Order"),
|
||||||
@@ -406,7 +407,6 @@ frappe.ui.form.on("Work Order", {
|
|||||||
work_order_id: frm.doc.name,
|
work_order_id: frm.doc.name,
|
||||||
purpose: "Disassemble",
|
purpose: "Disassemble",
|
||||||
qty: data.qty,
|
qty: data.qty,
|
||||||
target_warehouse: data.target_warehouse,
|
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then((stock_entry) => {
|
.then((stock_entry) => {
|
||||||
@@ -863,24 +863,6 @@ erpnext.work_order = {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
if (purpose === "Disassemble") {
|
|
||||||
fields.push({
|
|
||||||
fieldtype: "Link",
|
|
||||||
options: "Warehouse",
|
|
||||||
fieldname: "target_warehouse",
|
|
||||||
label: __("Target Warehouse"),
|
|
||||||
default: frm.doc.source_warehouse || frm.doc.wip_warehouse,
|
|
||||||
get_query() {
|
|
||||||
return {
|
|
||||||
filters: {
|
|
||||||
company: frm.doc.company,
|
|
||||||
is_group: 0,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
frm.qty_prompt = frappe.prompt(
|
frm.qty_prompt = frappe.prompt(
|
||||||
fields,
|
fields,
|
||||||
|
|||||||
@@ -2532,6 +2532,9 @@ def get_voucher_wise_serial_batch_from_bundle(**kwargs) -> dict[str, dict]:
|
|||||||
child_row = group_by_voucher[key]
|
child_row = group_by_voucher[key]
|
||||||
if row.serial_no:
|
if row.serial_no:
|
||||||
child_row["serial_nos"].append(row.serial_no)
|
child_row["serial_nos"].append(row.serial_no)
|
||||||
|
child_row["item_row"].qty = len(child_row["serial_nos"]) * (
|
||||||
|
-1 if row.type_of_transaction == "Outward" else 1
|
||||||
|
)
|
||||||
|
|
||||||
if row.batch_no:
|
if row.batch_no:
|
||||||
child_row["batch_nos"][row.batch_no] += row.qty
|
child_row["batch_nos"][row.batch_no] += row.qty
|
||||||
@@ -2652,8 +2655,13 @@ def get_ledgers_from_serial_batch_bundle(**kwargs) -> list[frappe._dict]:
|
|||||||
serial_batch_table.incoming_rate,
|
serial_batch_table.incoming_rate,
|
||||||
bundle_table.voucher_detail_no,
|
bundle_table.voucher_detail_no,
|
||||||
bundle_table.voucher_no,
|
bundle_table.voucher_no,
|
||||||
|
<<<<<<< HEAD
|
||||||
bundle_table.posting_date,
|
bundle_table.posting_date,
|
||||||
bundle_table.posting_time,
|
bundle_table.posting_time,
|
||||||
|
=======
|
||||||
|
bundle_table.posting_datetime,
|
||||||
|
bundle_table.type_of_transaction,
|
||||||
|
>>>>>>> 95e6c72539 (fix: inward same serial / batches in disassembly which were used)
|
||||||
)
|
)
|
||||||
.where(
|
.where(
|
||||||
(bundle_table.docstatus == 1)
|
(bundle_table.docstatus == 1)
|
||||||
|
|||||||
@@ -243,8 +243,75 @@ class StockEntry(StockController):
|
|||||||
|
|
||||||
self.validate_same_source_target_warehouse_during_material_transfer()
|
self.validate_same_source_target_warehouse_during_material_transfer()
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
self.validate_closed_subcontracting_order()
|
self.validate_closed_subcontracting_order()
|
||||||
|
=======
|
||||||
|
self.validate_closed_subcontracting_order()
|
||||||
|
self.validate_subcontract_order()
|
||||||
|
|
||||||
|
super().validate_subcontracting_inward()
|
||||||
|
|
||||||
|
def set_serial_batch_for_disassembly(self):
|
||||||
|
if self.purpose != "Disassemble":
|
||||||
|
return
|
||||||
|
|
||||||
|
available_materials = get_available_materials(self.work_order, self)
|
||||||
|
for row in self.items:
|
||||||
|
warehouse = row.s_warehouse or row.t_warehouse
|
||||||
|
materials = available_materials.get((row.item_code, warehouse))
|
||||||
|
if not materials:
|
||||||
|
continue
|
||||||
|
|
||||||
|
batches = defaultdict(float)
|
||||||
|
serial_nos = []
|
||||||
|
qty = row.transfer_qty
|
||||||
|
for batch_no, batch_qty in materials.batch_details.items():
|
||||||
|
if qty <= 0:
|
||||||
|
break
|
||||||
|
|
||||||
|
batch_qty = abs(batch_qty)
|
||||||
|
if batch_qty <= qty:
|
||||||
|
batches[batch_no] = batch_qty
|
||||||
|
qty -= batch_qty
|
||||||
|
else:
|
||||||
|
batches[batch_no] = qty
|
||||||
|
qty = 0
|
||||||
|
|
||||||
|
if materials.serial_nos:
|
||||||
|
serial_nos = materials.serial_nos[: int(row.transfer_qty)]
|
||||||
|
|
||||||
|
if not serial_nos and not batches:
|
||||||
|
continue
|
||||||
|
|
||||||
|
bundle_doc = SerialBatchCreation(
|
||||||
|
{
|
||||||
|
"item_code": row.item_code,
|
||||||
|
"warehouse": warehouse,
|
||||||
|
"posting_datetime": get_combine_datetime(self.posting_date, self.posting_time),
|
||||||
|
"voucher_type": self.doctype,
|
||||||
|
"voucher_no": self.name,
|
||||||
|
"voucher_detail_no": row.name,
|
||||||
|
"qty": row.transfer_qty,
|
||||||
|
"type_of_transaction": "Inward" if row.t_warehouse else "Outward",
|
||||||
|
"company": self.company,
|
||||||
|
"do_not_submit": True,
|
||||||
|
}
|
||||||
|
).make_serial_and_batch_bundle(serial_nos=serial_nos, batch_nos=batches)
|
||||||
|
|
||||||
|
row.serial_and_batch_bundle = bundle_doc.name
|
||||||
|
row.use_serial_batch_fields = 0
|
||||||
|
|
||||||
|
row.db_set(
|
||||||
|
{
|
||||||
|
"serial_and_batch_bundle": bundle_doc.name,
|
||||||
|
"use_serial_batch_fields": 0,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def on_submit(self):
|
||||||
|
self.set_serial_batch_for_disassembly()
|
||||||
|
>>>>>>> 95e6c72539 (fix: inward same serial / batches in disassembly which were used)
|
||||||
self.make_bundle_using_old_serial_batch_fields()
|
self.make_bundle_using_old_serial_batch_fields()
|
||||||
self.update_disassembled_order()
|
self.update_disassembled_order()
|
||||||
self.update_stock_ledger()
|
self.update_stock_ledger()
|
||||||
@@ -1856,7 +1923,13 @@ class StockEntry(StockController):
|
|||||||
|
|
||||||
s_warehouse = frappe.db.get_value("Work Order", self.work_order, "fg_warehouse")
|
s_warehouse = frappe.db.get_value("Work Order", self.work_order, "fg_warehouse")
|
||||||
|
|
||||||
items_dict = get_bom_items_as_dict(self.bom_no, self.company, disassemble_qty)
|
items_dict = get_bom_items_as_dict(
|
||||||
|
self.bom_no,
|
||||||
|
self.company,
|
||||||
|
disassemble_qty,
|
||||||
|
fetch_exploded=self.use_multi_level_bom,
|
||||||
|
fetch_qty_in_stock_uom=False,
|
||||||
|
)
|
||||||
|
|
||||||
for row in items:
|
for row in items:
|
||||||
child_row = self.append("items", {})
|
child_row = self.append("items", {})
|
||||||
@@ -1874,7 +1947,7 @@ class StockEntry(StockController):
|
|||||||
child_row.qty = disassemble_qty
|
child_row.qty = disassemble_qty
|
||||||
|
|
||||||
child_row.s_warehouse = (self.from_warehouse or s_warehouse) if row.is_finished_item else ""
|
child_row.s_warehouse = (self.from_warehouse or s_warehouse) if row.is_finished_item else ""
|
||||||
child_row.t_warehouse = self.to_warehouse if not row.is_finished_item else ""
|
child_row.t_warehouse = row.s_warehouse
|
||||||
child_row.is_finished_item = 0 if row.is_finished_item else 1
|
child_row.is_finished_item = 0 if row.is_finished_item else 1
|
||||||
|
|
||||||
def get_items_from_manufacture_entry(self):
|
def get_items_from_manufacture_entry(self):
|
||||||
@@ -1893,6 +1966,8 @@ class StockEntry(StockController):
|
|||||||
"`tabStock Entry Detail`.`is_finished_item`",
|
"`tabStock Entry Detail`.`is_finished_item`",
|
||||||
"`tabStock Entry Detail`.`batch_no`",
|
"`tabStock Entry Detail`.`batch_no`",
|
||||||
"`tabStock Entry Detail`.`serial_no`",
|
"`tabStock Entry Detail`.`serial_no`",
|
||||||
|
"`tabStock Entry Detail`.`s_warehouse`",
|
||||||
|
"`tabStock Entry Detail`.`t_warehouse`",
|
||||||
"`tabStock Entry Detail`.`use_serial_batch_fields`",
|
"`tabStock Entry Detail`.`use_serial_batch_fields`",
|
||||||
],
|
],
|
||||||
filters=[
|
filters=[
|
||||||
@@ -3259,8 +3334,8 @@ def get_items_from_subcontract_order(source_name, target_doc=None):
|
|||||||
return target_doc
|
return target_doc
|
||||||
|
|
||||||
|
|
||||||
def get_available_materials(work_order) -> dict:
|
def get_available_materials(work_order, stock_entry_doc=None) -> dict:
|
||||||
data = get_stock_entry_data(work_order)
|
data = get_stock_entry_data(work_order, stock_entry_doc=stock_entry_doc)
|
||||||
|
|
||||||
available_materials = {}
|
available_materials = {}
|
||||||
for row in data:
|
for row in data:
|
||||||
@@ -3268,6 +3343,9 @@ def get_available_materials(work_order) -> dict:
|
|||||||
if row.purpose != "Material Transfer for Manufacture":
|
if row.purpose != "Material Transfer for Manufacture":
|
||||||
key = (row.item_code, row.s_warehouse)
|
key = (row.item_code, row.s_warehouse)
|
||||||
|
|
||||||
|
if stock_entry_doc and stock_entry_doc.purpose == "Disassemble":
|
||||||
|
key = (row.item_code, row.s_warehouse or row.warehouse)
|
||||||
|
|
||||||
if key not in available_materials:
|
if key not in available_materials:
|
||||||
available_materials.setdefault(
|
available_materials.setdefault(
|
||||||
key,
|
key,
|
||||||
@@ -3278,7 +3356,9 @@ def get_available_materials(work_order) -> dict:
|
|||||||
|
|
||||||
item_data = available_materials[key]
|
item_data = available_materials[key]
|
||||||
|
|
||||||
if row.purpose == "Material Transfer for Manufacture":
|
if row.purpose == "Material Transfer for Manufacture" or (
|
||||||
|
stock_entry_doc and stock_entry_doc.purpose == "Disassemble" and row.purpose == "Manufacture"
|
||||||
|
):
|
||||||
item_data.qty += row.qty
|
item_data.qty += row.qty
|
||||||
if row.batch_no:
|
if row.batch_no:
|
||||||
item_data.batch_details[row.batch_no] += row.qty
|
item_data.batch_details[row.batch_no] += row.qty
|
||||||
@@ -3318,7 +3398,7 @@ def get_available_materials(work_order) -> dict:
|
|||||||
return available_materials
|
return available_materials
|
||||||
|
|
||||||
|
|
||||||
def get_stock_entry_data(work_order):
|
def get_stock_entry_data(work_order, stock_entry_doc=None):
|
||||||
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
|
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
|
||||||
get_voucher_wise_serial_batch_from_bundle,
|
get_voucher_wise_serial_batch_from_bundle,
|
||||||
)
|
)
|
||||||
@@ -3350,19 +3430,35 @@ def get_stock_entry_data(work_order):
|
|||||||
(stock_entry.name == stock_entry_detail.parent)
|
(stock_entry.name == stock_entry_detail.parent)
|
||||||
& (stock_entry.work_order == work_order)
|
& (stock_entry.work_order == work_order)
|
||||||
& (stock_entry.docstatus == 1)
|
& (stock_entry.docstatus == 1)
|
||||||
& (stock_entry_detail.s_warehouse.isnotnull())
|
|
||||||
& (
|
|
||||||
stock_entry.purpose.isin(
|
|
||||||
[
|
|
||||||
"Manufacture",
|
|
||||||
"Material Consumption for Manufacture",
|
|
||||||
"Material Transfer for Manufacture",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
.orderby(stock_entry.creation, stock_entry_detail.item_code, stock_entry_detail.idx)
|
.orderby(stock_entry.creation, stock_entry_detail.item_code, stock_entry_detail.idx)
|
||||||
).run(as_dict=1)
|
)
|
||||||
|
|
||||||
|
if stock_entry_doc and stock_entry_doc.purpose == "Disassemble":
|
||||||
|
data = data.where(
|
||||||
|
stock_entry.purpose.isin(
|
||||||
|
[
|
||||||
|
"Disassemble",
|
||||||
|
"Manufacture",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
data = data.where(stock_entry.name != stock_entry_doc.name)
|
||||||
|
else:
|
||||||
|
data = data.where(
|
||||||
|
stock_entry.purpose.isin(
|
||||||
|
[
|
||||||
|
"Manufacture",
|
||||||
|
"Material Consumption for Manufacture",
|
||||||
|
"Material Transfer for Manufacture",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
data = data.where(stock_entry_detail.s_warehouse.isnotnull())
|
||||||
|
|
||||||
|
data = data.run(as_dict=1)
|
||||||
|
|
||||||
if not data:
|
if not data:
|
||||||
return []
|
return []
|
||||||
@@ -3375,6 +3471,9 @@ def get_stock_entry_data(work_order):
|
|||||||
if row.purpose != "Material Transfer for Manufacture":
|
if row.purpose != "Material Transfer for Manufacture":
|
||||||
key = (row.item_code, row.s_warehouse, row.name)
|
key = (row.item_code, row.s_warehouse, row.name)
|
||||||
|
|
||||||
|
if stock_entry_doc and stock_entry_doc.purpose == "Disassemble":
|
||||||
|
key = (row.item_code, row.s_warehouse or row.warehouse, row.name)
|
||||||
|
|
||||||
if bundle_data.get(key):
|
if bundle_data.get(key):
|
||||||
row.update(bundle_data.get(key))
|
row.update(bundle_data.get(key))
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user