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:
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user