Merge pull request #35977 from rohitwaghchaure/reserve-pos-invoice-batches
fix: reserve the pos invoice batches
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.js b/erpnext/accounts/doctype/pos_invoice/pos_invoice.js
index cced375..32e267f 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.js
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.js
@@ -20,7 +20,7 @@
onload(doc) {
super.onload();
- this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice Merge Log', 'POS Closing Entry'];
+ this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice Merge Log', 'POS Closing Entry', 'Serial and Batch Bundle'];
if(doc.__islocal && doc.is_pos && frappe.get_route_str() !== 'point-of-sale') {
this.frm.script_manager.trigger("is_pos");
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
index bf393c0..4b2fcec 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
@@ -93,7 +93,7 @@
)
def on_cancel(self):
- self.ignore_linked_doctypes = "Payment Ledger Entry"
+ self.ignore_linked_doctypes = ["Payment Ledger Entry", "Serial and Batch Bundle"]
# run on cancel method of selling controller
super(SalesInvoice, self).on_cancel()
if not self.is_return and self.loyalty_program:
diff --git a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
index f842a16..0fce61f 100644
--- a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
@@ -767,6 +767,39 @@
)
self.assertEqual(rounded_total, 400)
+ def test_pos_batch_reservation(self):
+ from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
+ get_auto_batch_nos,
+ )
+ from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
+ create_batch_item_with_batch,
+ )
+
+ create_batch_item_with_batch("_BATCH ITEM Test For Reserve", "TestBatch-RS 02")
+ make_stock_entry(
+ target="_Test Warehouse - _TC",
+ item_code="_BATCH ITEM Test For Reserve",
+ qty=20,
+ basic_rate=100,
+ batch_no="TestBatch-RS 02",
+ )
+
+ pos_inv1 = create_pos_invoice(
+ item="_BATCH ITEM Test For Reserve", rate=300, qty=15, batch_no="TestBatch-RS 02"
+ )
+ pos_inv1.save()
+ pos_inv1.submit()
+
+ batches = get_auto_batch_nos(
+ frappe._dict(
+ {"item_code": "_BATCH ITEM Test For Reserve", "warehouse": "_Test Warehouse - _TC"}
+ )
+ )
+
+ for batch in batches:
+ if batch.batch_no == "TestBatch-RS 02" and batch.warehouse == "_Test Warehouse - _TC":
+ self.assertEqual(batch.qty, 5)
+
def test_pos_batch_item_qty_validation(self):
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
BatchNegativeStockError,
diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
index 2776a74..75b6ec7 100644
--- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
+++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
@@ -1241,59 +1241,125 @@
return list(set(ignore_serial_nos) - set(returned_serial_nos))
+def get_reserved_batches_for_pos(kwargs):
+ pos_batches = frappe._dict()
+ pos_invoices = frappe.get_all(
+ "POS Invoice",
+ fields=[
+ "`tabPOS Invoice Item`.batch_no",
+ "`tabPOS Invoice`.is_return",
+ "`tabPOS Invoice Item`.warehouse",
+ "`tabPOS Invoice Item`.name as child_docname",
+ "`tabPOS Invoice`.name as parent_docname",
+ "`tabPOS Invoice Item`.serial_and_batch_bundle",
+ ],
+ filters=[
+ ["POS Invoice", "consolidated_invoice", "is", "not set"],
+ ["POS Invoice", "docstatus", "=", 1],
+ ["POS Invoice Item", "item_code", "=", kwargs.item_code],
+ ["POS Invoice", "name", "!=", kwargs.ignore_voucher_no],
+ ],
+ )
+
+ ids = [
+ pos_invoice.serial_and_batch_bundle
+ for pos_invoice in pos_invoices
+ if pos_invoice.serial_and_batch_bundle
+ ]
+
+ if not ids:
+ return []
+
+ if ids:
+ for d in get_serial_batch_ledgers(kwargs.item_code, docstatus=1, name=ids):
+ if d.batch_no not in pos_batches:
+ pos_batches[d.batch_no] = frappe._dict(
+ {
+ "qty": d.qty,
+ "warehouse": d.warehouse,
+ }
+ )
+ else:
+ pos_batches[d.batch_no].qty += d.qty
+
+ for row in pos_invoices:
+ if not row.batch_no:
+ continue
+
+ if row.batch_no in pos_batches:
+ pos_batches[row.batch_no] -= row.qty * -1 if row.is_return else row.qty
+ else:
+ pos_batches[row.batch_no] = frappe._dict(
+ {
+ "qty": (row.qty * -1 if row.is_return else row.qty),
+ "warehouse": row.warehouse,
+ }
+ )
+
+ return pos_batches
+
+
def get_auto_batch_nos(kwargs):
available_batches = get_available_batches(kwargs)
qty = flt(kwargs.qty)
+ pos_invoice_batches = get_reserved_batches_for_pos(kwargs)
stock_ledgers_batches = get_stock_ledgers_batches(kwargs)
- if stock_ledgers_batches:
- update_available_batches(available_batches, stock_ledgers_batches)
+ if stock_ledgers_batches or pos_invoice_batches:
+ update_available_batches(available_batches, stock_ledgers_batches, pos_invoice_batches)
available_batches = list(filter(lambda x: x.qty > 0, available_batches))
-
if not qty:
return available_batches
+ return get_qty_based_available_batches(available_batches, qty)
+
+
+def get_qty_based_available_batches(available_batches, qty):
batches = []
for batch in available_batches:
- if qty > 0:
- batch_qty = flt(batch.qty)
- if qty > batch_qty:
- batches.append(
- frappe._dict(
- {
- "batch_no": batch.batch_no,
- "qty": batch_qty,
- "warehouse": batch.warehouse,
- }
- )
+ if qty <= 0:
+ break
+
+ batch_qty = flt(batch.qty)
+ if qty > batch_qty:
+ batches.append(
+ frappe._dict(
+ {
+ "batch_no": batch.batch_no,
+ "qty": batch_qty,
+ "warehouse": batch.warehouse,
+ }
)
- qty -= batch_qty
- else:
- batches.append(
- frappe._dict(
- {
- "batch_no": batch.batch_no,
- "qty": qty,
- "warehouse": batch.warehouse,
- }
- )
+ )
+ qty -= batch_qty
+ else:
+ batches.append(
+ frappe._dict(
+ {
+ "batch_no": batch.batch_no,
+ "qty": qty,
+ "warehouse": batch.warehouse,
+ }
)
- qty = 0
+ )
+ qty = 0
return batches
-def update_available_batches(available_batches, reserved_batches):
- for batch_no, data in reserved_batches.items():
- batch_not_exists = True
- for batch in available_batches:
- if batch.batch_no == batch_no:
- batch.qty += data.qty
- batch_not_exists = False
+def update_available_batches(available_batches, reserved_batches=None, pos_invoice_batches=None):
+ for batches in [reserved_batches, pos_invoice_batches]:
+ if batches:
+ for batch_no, data in batches.items():
+ batch_not_exists = True
+ for batch in available_batches:
+ if batch.batch_no == batch_no and batch.warehouse == data.warehouse:
+ batch.qty += data.qty
+ batch_not_exists = False
- if batch_not_exists:
- available_batches.append(data)
+ if batch_not_exists:
+ available_batches.append(data)
def get_available_batches(kwargs):