Skip to content

Commit

Permalink
Merge pull request #45258 from frappe/mergify/bp/version-15-hotfix/pr…
Browse files Browse the repository at this point in the history
…-45241

fix: auto fetch batch and serial no for draft stock transactions (backport #45241)
  • Loading branch information
rohitwaghchaure authored Jan 14, 2025
2 parents d508ea2 + af21bca commit 1be8051
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 4 deletions.
9 changes: 8 additions & 1 deletion erpnext/controllers/accounts_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -777,7 +777,14 @@ def set_missing_item_details(self, for_validate=False):
ret = get_item_details(args, 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(
Expand Down
2 changes: 2 additions & 0 deletions erpnext/public/js/controllers/transaction.js
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,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,
}
},

Expand Down
9 changes: 6 additions & 3 deletions erpnext/stock/doctype/batch/batch.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 _
Expand Down Expand Up @@ -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

Expand Down
67 changes: 67 additions & 0 deletions erpnext/stock/doctype/delivery_note/test_delivery_note.py
Original file line number Diff line number Diff line change
Expand Up @@ -2339,6 +2339,73 @@ def test_delivery_note_return_valuation_with_use_serial_batch_field(self):
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)


def create_delivery_note(**args):
dn = frappe.new_doc("Delivery Note")
Expand Down
93 changes: 93 additions & 0 deletions erpnext/stock/get_item_details.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,13 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru

out.update(data)

if (
frappe.db.get_single_value("Stock Settings", "auto_create_serial_and_batch_bundle_for_outward")
and not args.get("serial_and_batch_bundle")
and (args.get("use_serial_batch_fields") or args.get("doctype") == "POS Invoice")
):
update_stock(args, out, doc)

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)

Expand Down Expand Up @@ -168,6 +175,92 @@ def update_bin_details(args, out, doc):
out.update(bin_details)


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 process_args(args):
if isinstance(args, str):
args = json.loads(args)
Expand Down

0 comments on commit 1be8051

Please sign in to comment.