fix: stock reco test case for serial and batch bundle

This commit is contained in:
Rohit Waghchaure
2023-05-27 19:18:03 +05:30
parent f4cfc589c6
commit 42b229435c
12 changed files with 258 additions and 70 deletions

View File

@@ -88,7 +88,6 @@ class TestAssetRepair(unittest.TestCase):
self.assertEqual(stock_entry.items[0].qty, asset_repair.stock_items[0].consumed_quantity) self.assertEqual(stock_entry.items[0].qty, asset_repair.stock_items[0].consumed_quantity)
def test_serialized_item_consumption(self): def test_serialized_item_consumption(self):
from erpnext.stock.doctype.serial_no.serial_no import SerialNoRequiredError
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
stock_entry = make_serialized_item() stock_entry = make_serialized_item()

View File

@@ -53,7 +53,6 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate {
fieldtype: 'Data', fieldtype: 'Data',
fieldname: 'scan_serial_no', fieldname: 'scan_serial_no',
label: __('Scan Serial No'), label: __('Scan Serial No'),
options: 'Serial No',
get_query: () => { get_query: () => {
return { return {
filters: this.get_serial_no_filters() filters: this.get_serial_no_filters()
@@ -71,10 +70,9 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate {
if (this.item.has_batch_no) { if (this.item.has_batch_no) {
fields.push({ fields.push({
fieldtype: 'Link', fieldtype: 'Data',
fieldname: 'scan_batch_no', fieldname: 'scan_batch_no',
label: __('Scan Batch No'), label: __('Scan Batch No'),
options: 'Batch',
get_query: () => { get_query: () => {
return { return {
filters: { filters: {
@@ -104,6 +102,8 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate {
if (this.item?.outward) { if (this.item?.outward) {
fields = [...this.get_filter_fields(), ...fields]; fields = [...this.get_filter_fields(), ...fields];
} else {
fields = [...fields, ...this.get_attach_field()];
} }
fields.push({ fields.push({
@@ -121,6 +121,73 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate {
return fields; return fields;
} }
get_attach_field() {
let label = this.item?.has_serial_no ? __('Serial Nos') : __('Batch Nos');
let primary_label = this.bundle
? __('Update') : __('Add');
if (this.item?.has_serial_no && this.item?.has_batch_no) {
label = __('Serial Nos / Batch Nos');
}
return [
{
fieldtype: 'Section Break',
label: __('{0} {1} via CSV File', [primary_label, label])
},
{
fieldtype: 'Button',
fieldname: 'download_csv',
label: __('Download CSV Template'),
click: () => this.download_csv_file()
},
{
fieldtype: 'Column Break',
},
{
fieldtype: 'Attach',
fieldname: 'attach_serial_batch_csv',
label: __('Attach CSV File'),
onchange: () => this.upload_csv_file()
}
]
}
download_csv_file() {
let csvFileData = ['Serial No'];
if (this.item.has_serial_no && this.item.has_batch_no) {
csvFileData = ['Serial No', 'Batch No', 'Quantity'];
} else if (this.item.has_batch_no) {
csvFileData = ['Batch No', 'Quantity'];
}
const method = `/api/method/erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.download_blank_csv_template?content=${encodeURIComponent(JSON.stringify(csvFileData))}`;
const w = window.open(frappe.urllib.get_full_url(method));
if (!w) {
frappe.msgprint(__("Please enable pop-ups"));
}
}
upload_csv_file() {
const file_path = this.dialog.get_value("attach_serial_batch_csv")
frappe.call({
method: 'erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.upload_csv_file',
args: {
item_code: this.item.item_code,
file_path: file_path
},
callback: (r) => {
if (r.message.serial_nos && r.message.serial_nos.length) {
this.set_data(r.message.serial_nos);
} else if (r.message.batch_nos && r.message.batch_nos.length) {
this.set_data(r.message.batch_nos);
}
}
});
}
get_filter_fields() { get_filter_fields() {
return [ return [
{ {
@@ -213,10 +280,6 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate {
get_auto_data() { get_auto_data() {
const { qty, based_on } = this.dialog.get_values(); const { qty, based_on } = this.dialog.get_values();
if (!qty) {
frappe.throw(__('Please enter Qty to Fetch'));
}
if (!based_on) { if (!based_on) {
based_on = 'FIFO'; based_on = 'FIFO';
} }

View File

@@ -168,7 +168,12 @@ class Batch(Document):
@frappe.whitelist() @frappe.whitelist()
def get_batch_qty( def get_batch_qty(
batch_no=None, warehouse=None, item_code=None, posting_date=None, posting_time=None batch_no=None,
warehouse=None,
item_code=None,
posting_date=None,
posting_time=None,
ignore_voucher_nos=None,
): ):
"""Returns batch actual qty if warehouse is passed, """Returns batch actual qty if warehouse is passed,
or returns dict of qty by warehouse if warehouse is None or returns dict of qty by warehouse if warehouse is None
@@ -191,6 +196,7 @@ def get_batch_qty(
"posting_date": posting_date, "posting_date": posting_date,
"posting_time": posting_time, "posting_time": posting_time,
"batch_no": batch_no, "batch_no": batch_no,
"ignore_voucher_nos": ignore_voucher_nos,
} }
) )

View File

@@ -21,6 +21,7 @@ from frappe.utils import (
parse_json, parse_json,
today, today,
) )
from frappe.utils.csvutils import build_csv_response
from erpnext.stock.serial_batch_bundle import BatchNoValuation, SerialNoValuation from erpnext.stock.serial_batch_bundle import BatchNoValuation, SerialNoValuation
from erpnext.stock.serial_batch_bundle import get_serial_nos as get_serial_nos_from_bundle from erpnext.stock.serial_batch_bundle import get_serial_nos as get_serial_nos_from_bundle
@@ -152,15 +153,15 @@ class SerialandBatchBundle(Document):
if self.has_serial_no: if self.has_serial_no:
sn_obj = SerialNoValuation( sn_obj = SerialNoValuation(
sle=sle, sle=sle,
warehouse=self.item_code, item_code=self.item_code,
item_code=self.warehouse, warehouse=self.warehouse,
) )
else: else:
sn_obj = BatchNoValuation( sn_obj = BatchNoValuation(
sle=sle, sle=sle,
warehouse=self.item_code, item_code=self.item_code,
item_code=self.warehouse, warehouse=self.warehouse,
) )
for d in self.entries: for d in self.entries:
@@ -657,6 +658,31 @@ class SerialandBatchBundle(Document):
self.set("entries", batch_nos) self.set("entries", batch_nos)
@frappe.whitelist()
def download_blank_csv_template(content):
csv_data = []
if isinstance(content, str):
content = parse_json(content)
csv_data.append(content)
csv_data.append([])
csv_data.append([])
filename = "serial_and_batch_bundle"
build_csv_response(csv_data, filename)
@frappe.whitelist()
def upload_csv_file(item_code, file_path):
serial_nos, batch_nos = [], []
serial_nos, batch_nos = get_serial_batch_from_csv(item_code, file_path)
return {
"serial_nos": serial_nos,
"batch_nos": batch_nos,
}
def get_serial_batch_from_csv(item_code, file_path): def get_serial_batch_from_csv(item_code, file_path):
file_path = frappe.get_site_path() + file_path file_path = frappe.get_site_path() + file_path
serial_nos = [] serial_nos = []
@@ -669,7 +695,6 @@ def get_serial_batch_from_csv(item_code, file_path):
if serial_nos: if serial_nos:
make_serial_nos(item_code, serial_nos) make_serial_nos(item_code, serial_nos)
print(batch_nos)
if batch_nos: if batch_nos:
make_batch_nos(item_code, batch_nos) make_batch_nos(item_code, batch_nos)
@@ -938,7 +963,7 @@ def update_serial_batch_no_ledgers(entries, child_row, parent_doc) -> object:
doc.append( doc.append(
"entries", "entries",
{ {
"qty": 1 if doc.type_of_transaction == "Inward" else -1, "qty": d.get("qty") * (1 if doc.type_of_transaction == "Inward" else -1),
"warehouse": d.get("warehouse"), "warehouse": d.get("warehouse"),
"batch_no": d.get("batch_no"), "batch_no": d.get("batch_no"),
"serial_no": d.get("serial_no"), "serial_no": d.get("serial_no"),
@@ -1272,6 +1297,9 @@ def get_available_batches(kwargs):
else: else:
query = query.orderby(batch_table.creation) query = query.orderby(batch_table.creation)
if kwargs.get("ignore_voucher_nos"):
query = query.where(stock_ledger_entry.voucher_no.notin(kwargs.get("ignore_voucher_nos")))
data = query.run(as_dict=True) data = query.run(as_dict=True)
data = list(filter(lambda x: x.qty > 0, data)) data = list(filter(lambda x: x.qty > 0, data))

View File

@@ -8,7 +8,41 @@ from erpnext.stock.serial_batch_bundle import get_batch_nos, get_serial_nos
class TestSerialandBatchBundle(FrappeTestCase): class TestSerialandBatchBundle(FrappeTestCase):
pass def test_inward_serial_batch_bundle(self):
pass
def test_outward_serial_batch_bundle(self):
pass
def test_old_batch_valuation(self):
pass
def test_old_batch_batchwise_valuation(self):
pass
def test_old_serial_no_valuation(self):
pass
def test_batch_not_belong_to_serial_no(self):
pass
def test_serial_no_not_exists(self):
pass
def test_serial_no_item(self):
pass
def test_serial_no_not_required(self):
pass
def test_serial_no_required(self):
pass
def test_batch_no_not_required(self):
pass
def test_batch_no_required(self):
pass
def get_batch_from_bundle(bundle): def get_batch_from_bundle(bundle):

View File

@@ -22,38 +22,10 @@ class SerialNoCannotCannotChangeError(ValidationError):
pass pass
class SerialNoNotRequiredError(ValidationError):
pass
class SerialNoRequiredError(ValidationError):
pass
class SerialNoQtyError(ValidationError):
pass
class SerialNoItemError(ValidationError):
pass
class SerialNoWarehouseError(ValidationError): class SerialNoWarehouseError(ValidationError):
pass pass
class SerialNoBatchError(ValidationError):
pass
class SerialNoNotExistsError(ValidationError):
pass
class SerialNoDuplicateError(ValidationError):
pass
class SerialNo(StockController): class SerialNo(StockController):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(SerialNo, self).__init__(*args, **kwargs) super(SerialNo, self).__init__(*args, **kwargs)
@@ -69,6 +41,15 @@ class SerialNo(StockController):
) )
self.set_maintenance_status() self.set_maintenance_status()
self.validate_warehouse()
def validate_warehouse(self):
if not self.get("__islocal"):
item_code, warehouse = frappe.db.get_value("Serial No", self.name, ["item_code", "warehouse"])
if not self.via_stock_ledger and item_code != self.item_code:
frappe.throw(_("Item Code cannot be changed for Serial No."), SerialNoCannotCannotChangeError)
if not self.via_stock_ledger and warehouse != self.warehouse:
frappe.throw(_("Warehouse cannot be changed for Serial No."), SerialNoCannotCannotChangeError)
def set_maintenance_status(self): def set_maintenance_status(self):
if not self.warranty_expiry_date and not self.amc_expiry_date: if not self.warranty_expiry_date and not self.amc_expiry_date:

View File

@@ -744,8 +744,11 @@ frappe.ui.form.on('Stock Entry Detail', {
no_batch_serial_number_value = !d.batch_no; no_batch_serial_number_value = !d.batch_no;
} }
if (no_batch_serial_number_value && !frappe.flags.hide_serial_batch_dialog) { if (no_batch_serial_number_value && !frappe.flags.hide_serial_batch_dialog && !frappe.flags.dialog_set) {
frappe.flags.dialog_set = true;
erpnext.stock.select_batch_and_serial_no(frm, d); erpnext.stock.select_batch_and_serial_no(frm, d);
} else {
frappe.flags.dialog_set = false;
} }
} }
} }

View File

@@ -181,21 +181,25 @@ class StockReconciliation(StockController):
bundle_doc.flags.ignore_permissions = True bundle_doc.flags.ignore_permissions = True
bundle_doc.save() bundle_doc.save()
item.serial_and_batch_bundle = bundle_doc.name item.serial_and_batch_bundle = bundle_doc.name
elif item.serial_and_batch_bundle: elif item.serial_and_batch_bundle and not item.qty and not item.valuation_rate:
pass bundle_doc = frappe.get_doc("Serial and Batch Bundle", item.serial_and_batch_bundle)
item.qty = bundle_doc.total_qty
item.valuation_rate = bundle_doc.avg_rate
def remove_items_with_no_change(self): def remove_items_with_no_change(self):
"""Remove items if qty or rate is not changed""" """Remove items if qty or rate is not changed"""
self.difference_amount = 0.0 self.difference_amount = 0.0
def _changed(item): def _changed(item):
if item.current_serial_and_batch_bundle:
self.calculate_difference_amount(item, frappe._dict({}))
return True
item_dict = get_stock_balance_for( item_dict = get_stock_balance_for(
item.item_code, item.warehouse, self.posting_date, self.posting_time, batch_no=item.batch_no item.item_code, item.warehouse, self.posting_date, self.posting_time, batch_no=item.batch_no
) )
if item.current_serial_and_batch_bundle:
return True
if (item.qty is None or item.qty == item_dict.get("qty")) and ( if (item.qty is None or item.qty == item_dict.get("qty")) and (
item.valuation_rate is None or item.valuation_rate == item_dict.get("rate") item.valuation_rate is None or item.valuation_rate == item_dict.get("rate")
): ):
@@ -210,11 +214,7 @@ class StockReconciliation(StockController):
item.current_qty = item_dict.get("qty") item.current_qty = item_dict.get("qty")
item.current_valuation_rate = item_dict.get("rate") item.current_valuation_rate = item_dict.get("rate")
self.difference_amount += flt(item.qty, item.precision("qty")) * flt( self.calculate_difference_amount(item, item_dict)
item.valuation_rate or item_dict.get("rate"), item.precision("valuation_rate")
) - flt(item_dict.get("qty"), item.precision("qty")) * flt(
item_dict.get("rate"), item.precision("valuation_rate")
)
return True return True
items = list(filter(lambda d: _changed(d), self.items)) items = list(filter(lambda d: _changed(d), self.items))
@@ -231,6 +231,13 @@ class StockReconciliation(StockController):
item.idx = i + 1 item.idx = i + 1
frappe.msgprint(_("Removed items with no change in quantity or value.")) frappe.msgprint(_("Removed items with no change in quantity or value."))
def calculate_difference_amount(self, item, item_dict):
self.difference_amount += flt(item.qty, item.precision("qty")) * flt(
item.valuation_rate or item_dict.get("rate"), item.precision("valuation_rate")
) - flt(item_dict.get("qty"), item.precision("qty")) * flt(
item_dict.get("rate"), item.precision("valuation_rate")
)
def validate_data(self): def validate_data(self):
def _get_msg(row_num, msg): def _get_msg(row_num, msg):
return _("Row # {0}:").format(row_num + 1) + " " + msg return _("Row # {0}:").format(row_num + 1) + " " + msg
@@ -643,7 +650,14 @@ class StockReconciliation(StockController):
sl_entries = [] sl_entries = []
for row in self.items: for row in self.items:
if not (row.item_code == item_code and row.batch_no == batch_no): if (
not (row.item_code == item_code and row.batch_no == batch_no)
and not row.serial_and_batch_bundle
):
continue
if row.current_serial_and_batch_bundle:
self.recalculate_qty_for_serial_and_batch_bundle(row)
continue continue
current_qty = get_batch_qty_for_stock_reco( current_qty = get_batch_qty_for_stock_reco(
@@ -677,6 +691,27 @@ class StockReconciliation(StockController):
if sl_entries: if sl_entries:
self.make_sl_entries(sl_entries) self.make_sl_entries(sl_entries)
def recalculate_qty_for_serial_and_batch_bundle(self, row):
doc = frappe.get_doc("Serial and Batch Bundle", row.current_serial_and_batch_bundle)
precision = doc.entries[0].precision("qty")
for d in doc.entries:
qty = (
get_batch_qty(
d.batch_no,
doc.warehouse,
posting_date=doc.posting_date,
posting_time=doc.posting_time,
ignore_voucher_nos=[doc.voucher_no],
)
or 0
) * -1
if flt(d.qty, precision) == flt(qty, precision):
continue
d.db_set("qty", qty)
def get_batch_qty_for_stock_reco( def get_batch_qty_for_stock_reco(
item_code, warehouse, batch_no, posting_date, posting_time, voucher_no item_code, warehouse, batch_no, posting_date, posting_time, voucher_no

View File

@@ -694,10 +694,12 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin):
item_code=item_code, posting_time="09:00:00", target=warehouse, qty=100, basic_rate=700 item_code=item_code, posting_time="09:00:00", target=warehouse, qty=100, basic_rate=700
) )
batch_no = get_batch_from_bundle(se1.items[0].serial_and_batch_bundle)
# Removed 50 Qty, Balace Qty 50 # Removed 50 Qty, Balace Qty 50
se2 = make_stock_entry( se2 = make_stock_entry(
item_code=item_code, item_code=item_code,
batch_no=se1.items[0].batch_no, batch_no=batch_no,
posting_time="10:00:00", posting_time="10:00:00",
source=warehouse, source=warehouse,
qty=50, qty=50,
@@ -709,15 +711,23 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin):
item_code=item_code, item_code=item_code,
posting_time="11:00:00", posting_time="11:00:00",
warehouse=warehouse, warehouse=warehouse,
batch_no=se1.items[0].batch_no, batch_no=batch_no,
qty=100, qty=100,
rate=100, rate=100,
) )
sle = frappe.get_all(
"Stock Ledger Entry",
filters={"is_cancelled": 0, "voucher_no": stock_reco.name, "actual_qty": ("<", 0)},
fields=["actual_qty"],
)
self.assertEqual(flt(sle[0].actual_qty), flt(-50.0))
# Removed 50 Qty, Balace Qty 50 # Removed 50 Qty, Balace Qty 50
make_stock_entry( make_stock_entry(
item_code=item_code, item_code=item_code,
batch_no=se1.items[0].batch_no, batch_no=batch_no,
posting_time="12:00:00", posting_time="12:00:00",
source=warehouse, source=warehouse,
qty=50, qty=50,
@@ -741,12 +751,20 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin):
sle = frappe.get_all( sle = frappe.get_all(
"Stock Ledger Entry", "Stock Ledger Entry",
filters={"item_code": item_code, "warehouse": warehouse, "is_cancelled": 0}, filters={"item_code": item_code, "warehouse": warehouse, "is_cancelled": 0},
fields=["qty_after_transaction"], fields=["qty_after_transaction", "actual_qty", "voucher_type", "voucher_no"],
order_by="posting_time desc, creation desc", order_by="posting_time desc, creation desc",
) )
self.assertEqual(flt(sle[0].qty_after_transaction), flt(50.0)) self.assertEqual(flt(sle[0].qty_after_transaction), flt(50.0))
sle = frappe.get_all(
"Stock Ledger Entry",
filters={"is_cancelled": 0, "voucher_no": stock_reco.name, "actual_qty": ("<", 0)},
fields=["actual_qty"],
)
self.assertEqual(flt(sle[0].actual_qty), flt(-100.0))
def test_update_stock_reconciliation_while_reposting(self): def test_update_stock_reconciliation_while_reposting(self):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
@@ -914,7 +932,7 @@ def create_stock_reconciliation(**args):
"do_not_submit": True, "do_not_submit": True,
} }
) )
) ).name
sr.append( sr.append(
"items", "items",

View File

@@ -17,6 +17,7 @@
"amount", "amount",
"allow_zero_valuation_rate", "allow_zero_valuation_rate",
"serial_no_and_batch_section", "serial_no_and_batch_section",
"add_serial_batch_bundle",
"serial_and_batch_bundle", "serial_and_batch_bundle",
"batch_no", "batch_no",
"column_break_11", "column_break_11",
@@ -203,11 +204,16 @@
"label": "Current Serial / Batch Bundle", "label": "Current Serial / Batch Bundle",
"options": "Serial and Batch Bundle", "options": "Serial and Batch Bundle",
"read_only": 1 "read_only": 1
},
{
"fieldname": "add_serial_batch_bundle",
"fieldtype": "Button",
"label": "Add Serial / Batch No"
} }
], ],
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2023-05-09 18:42:19.224916", "modified": "2023-05-27 17:35:31.026852",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Stock Reconciliation Item", "name": "Stock Reconciliation Item",

View File

@@ -78,6 +78,12 @@ class SerialBatchBundle:
self.set_serial_and_batch_bundle(sn_doc) self.set_serial_and_batch_bundle(sn_doc)
def validate_actual_qty(self, sn_doc):
precision = sn_doc.precision("total_qty")
if flt(sn_doc.total_qty, precision) != flt(self.sle.actual_qty, precision):
msg = f"Total qty {flt(sn_doc.total_qty, precision)} of Serial and Batch Bundle {sn_doc.name} is not equal to Actual Qty {flt(self.sle.actual_qty, precision)} in the {self.sle.voucher_type} {self.sle.voucher_no}"
frappe.throw(_(msg))
def validate_item(self): def validate_item(self):
msg = "" msg = ""
if self.sle.actual_qty > 0: if self.sle.actual_qty > 0:
@@ -214,6 +220,8 @@ class SerialBatchBundle:
def submit_serial_and_batch_bundle(self): def submit_serial_and_batch_bundle(self):
doc = frappe.get_doc("Serial and Batch Bundle", self.sle.serial_and_batch_bundle) doc = frappe.get_doc("Serial and Batch Bundle", self.sle.serial_and_batch_bundle)
self.validate_actual_qty(doc)
doc.flags.ignore_voucher_validation = True doc.flags.ignore_voucher_validation = True
doc.submit() doc.submit()
@@ -426,9 +434,6 @@ class BatchNoValuation(DeprecatedBatchNoValuation):
) )
else: else:
entries = self.get_batch_no_ledgers() entries = self.get_batch_no_ledgers()
if frappe.flags.add_breakpoint:
breakpoint()
self.batch_avg_rate = defaultdict(float) self.batch_avg_rate = defaultdict(float)
self.available_qty = defaultdict(float) self.available_qty = defaultdict(float)
self.stock_value_differece = defaultdict(float) self.stock_value_differece = defaultdict(float)

View File

@@ -676,7 +676,7 @@ class update_entries_after(object):
if ( if (
sle.voucher_type == "Stock Reconciliation" sle.voucher_type == "Stock Reconciliation"
and sle.batch_no and (sle.batch_no or (sle.has_batch_no and sle.serial_and_batch_bundle))
and sle.voucher_detail_no and sle.voucher_detail_no
and sle.actual_qty < 0 and sle.actual_qty < 0
): ):
@@ -734,9 +734,17 @@ class update_entries_after(object):
self.update_outgoing_rate_on_transaction(sle) self.update_outgoing_rate_on_transaction(sle)
def reset_actual_qty_for_stock_reco(self, sle): def reset_actual_qty_for_stock_reco(self, sle):
current_qty = frappe.get_cached_value( if sle.serial_and_batch_bundle:
"Stock Reconciliation Item", sle.voucher_detail_no, "current_qty" current_qty = frappe.get_cached_value(
) "Serial and Batch Bundle", sle.serial_and_batch_bundle, "total_qty"
)
if current_qty is not None:
current_qty = abs(current_qty)
else:
current_qty = frappe.get_cached_value(
"Stock Reconciliation Item", sle.voucher_detail_no, "current_qty"
)
if current_qty: if current_qty:
sle.actual_qty = current_qty * -1 sle.actual_qty = current_qty * -1
@@ -1524,7 +1532,7 @@ def update_qty_in_future_sle(args, allow_negative_stock=False):
next_stock_reco_detail = get_next_stock_reco(args) next_stock_reco_detail = get_next_stock_reco(args)
if next_stock_reco_detail: if next_stock_reco_detail:
detail = next_stock_reco_detail[0] detail = next_stock_reco_detail[0]
if detail.batch_no: if detail.batch_no or (detail.serial_and_batch_bundle and detail.has_batch_no):
regenerate_sle_for_batch_stock_reco(detail) regenerate_sle_for_batch_stock_reco(detail)
# add condition to update SLEs before this date & time # add condition to update SLEs before this date & time
@@ -1602,7 +1610,9 @@ def get_next_stock_reco(kwargs):
sle.voucher_no, sle.voucher_no,
sle.item_code, sle.item_code,
sle.batch_no, sle.batch_no,
sle.serial_and_batch_bundle,
sle.actual_qty, sle.actual_qty,
sle.has_batch_no,
) )
.where( .where(
(sle.item_code == kwargs.get("item_code")) (sle.item_code == kwargs.get("item_code"))