fix: auto fetch batch and serial no for draft stock transactions
(cherry picked from commit 88ab9be79c)
# Conflicts:
# erpnext/stock/get_item_details.py
This commit is contained in:
committed by
Mergify
parent
f954b241a8
commit
2f2554e9e5
@@ -777,7 +777,14 @@ class AccountsController(TransactionBase):
|
|||||||
ret = get_item_details(args, self, for_validate=for_validate, overwrite_warehouse=False)
|
ret = get_item_details(args, self, for_validate=for_validate, overwrite_warehouse=False)
|
||||||
for fieldname, value in ret.items():
|
for fieldname, value in ret.items():
|
||||||
if item.meta.get_field(fieldname) and value is not None:
|
if item.meta.get_field(fieldname) and value is not None:
|
||||||
if item.get(fieldname) is None or fieldname in force_item_fields:
|
if (
|
||||||
|
item.get(fieldname) is None
|
||||||
|
or fieldname in force_item_fields
|
||||||
|
or (
|
||||||
|
fieldname in ["serial_no", "batch_no"]
|
||||||
|
and item.get("use_serial_batch_fields")
|
||||||
|
)
|
||||||
|
):
|
||||||
item.set(fieldname, value)
|
item.set(fieldname, value)
|
||||||
|
|
||||||
elif fieldname in ["cost_center", "conversion_factor"] and not item.get(
|
elif fieldname in ["cost_center", "conversion_factor"] and not item.get(
|
||||||
|
|||||||
@@ -579,6 +579,8 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
child_doctype: item.doctype,
|
child_doctype: item.doctype,
|
||||||
child_docname: item.name,
|
child_docname: item.name,
|
||||||
is_old_subcontracting_flow: me.frm.doc.is_old_subcontracting_flow,
|
is_old_subcontracting_flow: me.frm.doc.is_old_subcontracting_flow,
|
||||||
|
use_serial_batch_fields: item.use_serial_batch_fields,
|
||||||
|
serial_and_batch_bundle: item.serial_and_batch_bundle,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# License: GNU General Public License v3. See license.txt
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
|
|
||||||
from collections import defaultdict
|
from collections import OrderedDict, defaultdict
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
@@ -449,11 +449,14 @@ def get_available_batches(kwargs):
|
|||||||
get_auto_batch_nos,
|
get_auto_batch_nos,
|
||||||
)
|
)
|
||||||
|
|
||||||
batchwise_qty = defaultdict(float)
|
batchwise_qty = OrderedDict()
|
||||||
|
|
||||||
batches = get_auto_batch_nos(kwargs)
|
batches = get_auto_batch_nos(kwargs)
|
||||||
for batch in batches:
|
for batch in batches:
|
||||||
batchwise_qty[batch.get("batch_no")] += batch.get("qty")
|
if batch.get("batch_no") not in batchwise_qty:
|
||||||
|
batchwise_qty[batch.get("batch_no")] = batch.get("qty")
|
||||||
|
else:
|
||||||
|
batchwise_qty[batch.get("batch_no")] += batch.get("qty")
|
||||||
|
|
||||||
return batchwise_qty
|
return batchwise_qty
|
||||||
|
|
||||||
|
|||||||
@@ -2339,6 +2339,77 @@ class TestDeliveryNote(FrappeTestCase):
|
|||||||
for d in bundle_data:
|
for d in bundle_data:
|
||||||
self.assertEqual(d.incoming_rate, serial_no_valuation[d.serial_no])
|
self.assertEqual(d.incoming_rate, serial_no_valuation[d.serial_no])
|
||||||
|
|
||||||
|
def test_auto_set_serial_batch_for_draft_dn(self):
|
||||||
|
frappe.db.set_single_value("Stock Settings", "auto_create_serial_and_batch_bundle_for_outward", 1)
|
||||||
|
frappe.db.set_single_value("Stock Settings", "pick_serial_and_batch_based_on", "FIFO")
|
||||||
|
|
||||||
|
batch_item = make_item(
|
||||||
|
"_Test Auto Set Serial Batch Draft DN",
|
||||||
|
properties={
|
||||||
|
"has_batch_no": 1,
|
||||||
|
"create_new_batch": 1,
|
||||||
|
"is_stock_item": 1,
|
||||||
|
"batch_number_series": "TAS-BASD-.#####",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
serial_item = make_item(
|
||||||
|
"_Test Auto Set Serial Batch Draft DN Serial Item",
|
||||||
|
properties={"has_serial_no": 1, "is_stock_item": 1, "serial_no_series": "TAS-SASD-.#####"},
|
||||||
|
)
|
||||||
|
|
||||||
|
batch_serial_item = make_item(
|
||||||
|
"_Test Auto Set Serial Batch Draft DN Batch Serial Item",
|
||||||
|
properties={
|
||||||
|
"has_batch_no": 1,
|
||||||
|
"has_serial_no": 1,
|
||||||
|
"is_stock_item": 1,
|
||||||
|
"create_new_batch": 1,
|
||||||
|
"batch_number_series": "TAS-BSD-.#####",
|
||||||
|
"serial_no_series": "TAS-SSD-.#####",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
for item in [batch_item, serial_item, batch_serial_item]:
|
||||||
|
make_stock_entry(item_code=item.name, target="_Test Warehouse - _TC", qty=5, basic_rate=100)
|
||||||
|
|
||||||
|
dn = create_delivery_note(
|
||||||
|
item_code=batch_item,
|
||||||
|
qty=5,
|
||||||
|
rate=500,
|
||||||
|
use_serial_batch_fields=1,
|
||||||
|
do_not_submit=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
for item in [serial_item, batch_serial_item]:
|
||||||
|
dn.append(
|
||||||
|
"items",
|
||||||
|
{
|
||||||
|
"item_code": item.name,
|
||||||
|
"qty": 5,
|
||||||
|
"rate": 500,
|
||||||
|
"base_rate": 500,
|
||||||
|
"item_name": item.name,
|
||||||
|
"uom": "Nos",
|
||||||
|
"stock_uom": "Nos",
|
||||||
|
"conversion_factor": 1,
|
||||||
|
"warehouse": dn.items[0].warehouse,
|
||||||
|
"use_serial_batch_fields": 1,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
dn.save()
|
||||||
|
for row in dn.items:
|
||||||
|
if row.item_code == batch_item.name:
|
||||||
|
self.assertTrue(row.batch_no)
|
||||||
|
|
||||||
|
if row.item_code == serial_item.name:
|
||||||
|
self.assertTrue(row.serial_no)
|
||||||
|
|
||||||
|
if row.item_code == batch_serial_item.name:
|
||||||
|
self.assertTrue(row.batch_no)
|
||||||
|
self.assertTrue(row.serial_no)
|
||||||
|
|
||||||
|
|
||||||
def create_delivery_note(**args):
|
def create_delivery_note(**args):
|
||||||
dn = frappe.new_doc("Delivery Note")
|
dn = frappe.new_doc("Delivery Note")
|
||||||
|
|||||||
@@ -115,8 +115,20 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru
|
|||||||
|
|
||||||
out.update(data)
|
out.update(data)
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
if args.transaction_date and item.lead_time_days:
|
if args.transaction_date and item.lead_time_days:
|
||||||
out.schedule_date = out.lead_time_date = add_days(args.transaction_date, item.lead_time_days)
|
out.schedule_date = out.lead_time_date = add_days(args.transaction_date, item.lead_time_days)
|
||||||
|
=======
|
||||||
|
if (
|
||||||
|
frappe.db.get_single_value("Stock Settings", "auto_create_serial_and_batch_bundle_for_outward")
|
||||||
|
and not ctx.get("serial_and_batch_bundle")
|
||||||
|
and (ctx.get("use_serial_batch_fields") or ctx.get("doctype") == "POS Invoice")
|
||||||
|
):
|
||||||
|
update_stock(ctx, out, doc)
|
||||||
|
|
||||||
|
if ctx.transaction_date and item.lead_time_days:
|
||||||
|
out.schedule_date = out.lead_time_date = add_days(ctx.transaction_date, item.lead_time_days)
|
||||||
|
>>>>>>> 88ab9be79c (fix: auto fetch batch and serial no for draft stock transactions)
|
||||||
|
|
||||||
if args.get("is_subcontracted"):
|
if args.get("is_subcontracted"):
|
||||||
out.bom = args.get("bom") or get_default_bom(args.item_code)
|
out.bom = args.get("bom") or get_default_bom(args.item_code)
|
||||||
@@ -155,9 +167,101 @@ def set_valuation_rate(out, args):
|
|||||||
out.update(get_valuation_rate(args.item_code, args.company, out.get("warehouse")))
|
out.update(get_valuation_rate(args.item_code, args.company, out.get("warehouse")))
|
||||||
|
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
def update_bin_details(args, out, doc):
|
def update_bin_details(args, out, doc):
|
||||||
if args.get("doctype") == "Material Request" and args.get("material_request_type") == "Material Transfer":
|
if args.get("doctype") == "Material Request" and args.get("material_request_type") == "Material Transfer":
|
||||||
out.update(get_bin_details(args.item_code, args.get("from_warehouse")))
|
out.update(get_bin_details(args.item_code, args.get("from_warehouse")))
|
||||||
|
=======
|
||||||
|
def update_stock(ctx, out, doc=None):
|
||||||
|
from erpnext.stock.doctype.batch.batch import get_available_batches
|
||||||
|
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos_for_outward
|
||||||
|
|
||||||
|
if (
|
||||||
|
(
|
||||||
|
ctx.get("doctype") in ["Delivery Note", "POS Invoice"]
|
||||||
|
or (ctx.get("doctype") == "Sales Invoice" and ctx.get("update_stock"))
|
||||||
|
)
|
||||||
|
and out.warehouse
|
||||||
|
and out.stock_qty > 0
|
||||||
|
):
|
||||||
|
kwargs = frappe._dict(
|
||||||
|
{
|
||||||
|
"item_code": ctx.item_code,
|
||||||
|
"warehouse": ctx.warehouse,
|
||||||
|
"based_on": frappe.db.get_single_value("Stock Settings", "pick_serial_and_batch_based_on"),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if ctx.get("ignore_serial_nos"):
|
||||||
|
kwargs["ignore_serial_nos"] = ctx.get("ignore_serial_nos")
|
||||||
|
|
||||||
|
qty = out.stock_qty
|
||||||
|
batches = []
|
||||||
|
if out.has_batch_no and not ctx.get("batch_no"):
|
||||||
|
batches = get_available_batches(kwargs)
|
||||||
|
if doc:
|
||||||
|
filter_batches(batches, doc)
|
||||||
|
|
||||||
|
for batch_no, batch_qty in batches.items():
|
||||||
|
if batch_qty >= qty:
|
||||||
|
out.update({"batch_no": batch_no, "actual_batch_qty": qty})
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
qty -= batch_qty
|
||||||
|
|
||||||
|
out.update({"batch_no": batch_no, "actual_batch_qty": batch_qty})
|
||||||
|
|
||||||
|
if out.has_serial_no and out.has_batch_no and has_incorrect_serial_nos(ctx, out):
|
||||||
|
kwargs["batches"] = [ctx.get("batch_no")] if ctx.get("batch_no") else [out.get("batch_no")]
|
||||||
|
serial_nos = get_serial_nos_for_outward(kwargs)
|
||||||
|
serial_nos = get_filtered_serial_nos(serial_nos, doc)
|
||||||
|
|
||||||
|
out["serial_no"] = "\n".join(serial_nos[: cint(out.stock_qty)])
|
||||||
|
|
||||||
|
elif out.has_serial_no and not ctx.get("serial_no"):
|
||||||
|
serial_nos = get_serial_nos_for_outward(kwargs)
|
||||||
|
serial_nos = get_filtered_serial_nos(serial_nos, doc)
|
||||||
|
|
||||||
|
out["serial_no"] = "\n".join(serial_nos[: cint(out.stock_qty)])
|
||||||
|
|
||||||
|
|
||||||
|
def has_incorrect_serial_nos(ctx, out):
|
||||||
|
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||||
|
|
||||||
|
if not ctx.get("serial_no"):
|
||||||
|
return True
|
||||||
|
|
||||||
|
serial_nos = get_serial_nos(ctx.get("serial_no"))
|
||||||
|
if len(serial_nos) != out.get("stock_qty"):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def filter_batches(batches, doc):
|
||||||
|
for row in doc.get("items"):
|
||||||
|
if row.get("batch_no") in batches:
|
||||||
|
batches[row.get("batch_no")] -= row.get("qty")
|
||||||
|
if batches[row.get("batch_no")] <= 0:
|
||||||
|
del batches[row.get("batch_no")]
|
||||||
|
|
||||||
|
|
||||||
|
def get_filtered_serial_nos(serial_nos, doc):
|
||||||
|
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||||
|
|
||||||
|
for row in doc.get("items"):
|
||||||
|
if row.get("serial_no"):
|
||||||
|
for serial_no in get_serial_nos(row.get("serial_no")):
|
||||||
|
if serial_no in serial_nos:
|
||||||
|
serial_nos.remove(serial_no)
|
||||||
|
|
||||||
|
return serial_nos
|
||||||
|
|
||||||
|
|
||||||
|
def update_bin_details(ctx: ItemDetailsCtx, out: ItemDetails, doc):
|
||||||
|
if ctx.doctype == "Material Request" and ctx.material_request_type == "Material Transfer":
|
||||||
|
out.update(get_bin_details(ctx.item_code, ctx.from_warehouse))
|
||||||
|
>>>>>>> 88ab9be79c (fix: auto fetch batch and serial no for draft stock transactions)
|
||||||
|
|
||||||
elif out.get("warehouse"):
|
elif out.get("warehouse"):
|
||||||
company = args.company if (doc and doc.get("doctype") == "Purchase Order") else None
|
company = args.company if (doc and doc.get("doctype") == "Purchase Order") else None
|
||||||
|
|||||||
Reference in New Issue
Block a user