Merge pull request #45241 from rohitwaghchaure/fixed-support-28979

fix: auto fetch batch and serial no for draft stock transactions
This commit is contained in:
rohitwaghchaure
2025-01-14 12:35:17 +05:30
committed by GitHub
5 changed files with 180 additions and 4 deletions

View File

@@ -779,7 +779,14 @@ class AccountsController(TransactionBase):
ret = get_item_details(ctx, self, for_validate=for_validate, overwrite_warehouse=False)
for fieldname, value in ret.items():
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)
elif fieldname in ["cost_center", "conversion_factor"] and not item.get(

View File

@@ -601,6 +601,8 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
child_doctype: item.doctype,
child_docname: item.name,
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,
}
},

View File

@@ -2,7 +2,7 @@
# License: GNU General Public License v3. See license.txt
from collections import defaultdict
from collections import OrderedDict, defaultdict
import frappe
from frappe import _
@@ -449,11 +449,14 @@ def get_available_batches(kwargs):
get_auto_batch_nos,
)
batchwise_qty = defaultdict(float)
batchwise_qty = OrderedDict()
batches = get_auto_batch_nos(kwargs)
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

View File

@@ -2375,6 +2375,77 @@ class TestDeliveryNote(IntegrationTestCase):
for d in bundle_data:
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):
dn = frappe.new_doc("Delivery Note")

View File

@@ -134,6 +134,13 @@ def get_item_details(
out.update(data)
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)
@@ -174,6 +181,92 @@ def set_valuation_rate(out: ItemDetails | dict, ctx: ItemDetailsCtx):
out.update(get_valuation_rate(ctx.item_code, ctx.company, out.get("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))