fix: serial and batch bundle for POS Invoice (#41491)
This commit is contained in:
@@ -17,6 +17,10 @@ from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_inv
|
||||
from erpnext.accounts.doctype.pos_opening_entry.test_pos_opening_entry import create_opening_entry
|
||||
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
|
||||
from erpnext.selling.page.point_of_sale.point_of_sale import get_items
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle import (
|
||||
get_batch_from_bundle,
|
||||
)
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||
|
||||
|
||||
@@ -179,6 +183,94 @@ class TestPOSClosingEntry(unittest.TestCase):
|
||||
accounting_dimension_department.save()
|
||||
disable_dimension()
|
||||
|
||||
def test_merging_into_sales_invoice_for_batched_item(self):
|
||||
frappe.flags.print_message = False
|
||||
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import (
|
||||
init_user_and_profile,
|
||||
)
|
||||
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import (
|
||||
consolidate_pos_invoices,
|
||||
)
|
||||
from erpnext.stock.doctype.batch.batch import get_batch_qty
|
||||
|
||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||
item_doc = make_item(
|
||||
"_Test Item With Batch FOR POS Merge Test",
|
||||
properties={
|
||||
"is_stock_item": 1,
|
||||
"has_batch_no": 1,
|
||||
"batch_number_series": "BATCH-PM-POS-MERGE-.####",
|
||||
"create_new_batch": 1,
|
||||
},
|
||||
)
|
||||
|
||||
item_code = item_doc.name
|
||||
se = make_stock_entry(
|
||||
target="_Test Warehouse - _TC",
|
||||
item_code=item_code,
|
||||
qty=10,
|
||||
basic_rate=100,
|
||||
use_serial_batch_fields=0,
|
||||
)
|
||||
batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle)
|
||||
|
||||
test_user, pos_profile = init_user_and_profile()
|
||||
opening_entry = create_opening_entry(pos_profile, test_user.name)
|
||||
|
||||
pos_inv = create_pos_invoice(
|
||||
item_code=item_code, qty=5, rate=300, use_serial_batch_fields=1, batch_no=batch_no
|
||||
)
|
||||
pos_inv2 = create_pos_invoice(
|
||||
item_code=item_code, qty=5, rate=300, use_serial_batch_fields=1, batch_no=batch_no
|
||||
)
|
||||
|
||||
batch_qty = frappe.db.get_value("Batch", batch_no, "batch_qty")
|
||||
self.assertEqual(batch_qty, 10)
|
||||
|
||||
batch_qty_with_pos = get_batch_qty(batch_no, "_Test Warehouse - _TC", item_code)
|
||||
self.assertEqual(batch_qty_with_pos, 0.0)
|
||||
|
||||
pcv_doc = make_closing_entry_from_opening(opening_entry)
|
||||
pcv_doc.submit()
|
||||
|
||||
piv_merge = frappe.db.get_value("POS Invoice Merge Log", {"pos_closing_entry": pcv_doc.name}, "name")
|
||||
|
||||
self.assertTrue(piv_merge)
|
||||
piv_merge_doc = frappe.get_doc("POS Invoice Merge Log", piv_merge)
|
||||
self.assertTrue(piv_merge_doc.pos_invoices[0].pos_invoice)
|
||||
self.assertTrue(piv_merge_doc.pos_invoices[1].pos_invoice)
|
||||
|
||||
pos_inv.load_from_db()
|
||||
self.assertTrue(pos_inv.consolidated_invoice)
|
||||
pos_inv2.load_from_db()
|
||||
self.assertTrue(pos_inv2.consolidated_invoice)
|
||||
|
||||
batch_qty = frappe.db.get_value("Batch", batch_no, "batch_qty")
|
||||
self.assertEqual(batch_qty, 0.0)
|
||||
|
||||
batch_qty_with_pos = get_batch_qty(batch_no, "_Test Warehouse - _TC", item_code)
|
||||
self.assertEqual(batch_qty_with_pos, 0.0)
|
||||
|
||||
frappe.flags.print_message = True
|
||||
|
||||
pcv_doc.reload()
|
||||
pcv_doc.cancel()
|
||||
|
||||
batch_qty = frappe.db.get_value("Batch", batch_no, "batch_qty")
|
||||
self.assertEqual(batch_qty, 10)
|
||||
|
||||
batch_qty_with_pos = get_batch_qty(batch_no, "_Test Warehouse - _TC", item_code)
|
||||
self.assertEqual(batch_qty_with_pos, 0.0)
|
||||
|
||||
pos_inv.reload()
|
||||
pos_inv2.reload()
|
||||
|
||||
pos_inv.cancel()
|
||||
pos_inv2.cancel()
|
||||
|
||||
batch_qty_with_pos = get_batch_qty(batch_no, "_Test Warehouse - _TC", item_code)
|
||||
self.assertEqual(batch_qty_with_pos, 10.0)
|
||||
|
||||
|
||||
def init_user_and_profile(**args):
|
||||
user = "test@example.com"
|
||||
|
||||
@@ -229,7 +229,9 @@ class POSInvoice(SalesInvoice):
|
||||
self.check_phone_payments()
|
||||
self.set_status(update=True)
|
||||
self.make_bundle_for_sales_purchase_return()
|
||||
self.submit_serial_batch_bundle()
|
||||
for table_name in ["items", "packed_items"]:
|
||||
self.make_bundle_using_old_serial_batch_fields(table_name)
|
||||
self.submit_serial_batch_bundle(table_name)
|
||||
|
||||
if self.coupon_code:
|
||||
from erpnext.accounts.doctype.pricing_rule.utils import update_coupon_code_count
|
||||
@@ -283,10 +285,11 @@ class POSInvoice(SalesInvoice):
|
||||
{"is_cancelled": 1, "voucher_no": ""},
|
||||
)
|
||||
|
||||
frappe.get_doc("Serial and Batch Bundle", row.serial_and_batch_bundle).cancel()
|
||||
row.db_set("serial_and_batch_bundle", None)
|
||||
|
||||
def submit_serial_batch_bundle(self):
|
||||
for item in self.items:
|
||||
def submit_serial_batch_bundle(self, table_name):
|
||||
for item in self.get(table_name):
|
||||
if item.serial_and_batch_bundle:
|
||||
doc = frappe.get_doc("Serial and Batch Bundle", item.serial_and_batch_bundle)
|
||||
|
||||
@@ -355,10 +358,16 @@ class POSInvoice(SalesInvoice):
|
||||
error_msg = []
|
||||
for d in self.get("items"):
|
||||
error_msg = ""
|
||||
if d.get("has_serial_no") and not d.serial_and_batch_bundle:
|
||||
if d.get("has_serial_no") and (
|
||||
(not d.use_serial_batch_fields and not d.serial_and_batch_bundle)
|
||||
or (d.use_serial_batch_fields and not d.serial_no)
|
||||
):
|
||||
error_msg = f"Row #{d.idx}: Please select Serial No. for item {bold(d.item_code)}"
|
||||
|
||||
elif d.get("has_batch_no") and not d.serial_and_batch_bundle:
|
||||
elif d.get("has_batch_no") and (
|
||||
(not d.use_serial_batch_fields and not d.serial_and_batch_bundle)
|
||||
or (d.use_serial_batch_fields and not d.batch_no)
|
||||
):
|
||||
error_msg = f"Row #{d.idx}: Please select Batch No. for item {bold(d.item_code)}"
|
||||
|
||||
if error_msg:
|
||||
|
||||
@@ -780,8 +780,6 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
pos_inv1.submit()
|
||||
pos_inv1.reload()
|
||||
|
||||
self.assertFalse(pos_inv1.items[0].serial_and_batch_bundle)
|
||||
|
||||
batches = get_auto_batch_nos(
|
||||
frappe._dict({"item_code": "_BATCH ITEM Test For Reserve", "warehouse": "_Test Warehouse - _TC"})
|
||||
)
|
||||
@@ -957,7 +955,7 @@ def create_pos_invoice(**args):
|
||||
pos_inv.set_missing_values()
|
||||
|
||||
bundle_id = None
|
||||
if args.get("batch_no") or args.get("serial_no"):
|
||||
if not args.use_serial_batch_fields and (args.get("batch_no") or args.get("serial_no")):
|
||||
type_of_transaction = args.type_of_transaction or "Outward"
|
||||
|
||||
if pos_inv.is_return:
|
||||
@@ -998,6 +996,9 @@ def create_pos_invoice(**args):
|
||||
"expense_account": args.expense_account or "Cost of Goods Sold - _TC",
|
||||
"cost_center": args.cost_center or "_Test Cost Center - _TC",
|
||||
"serial_and_batch_bundle": bundle_id,
|
||||
"use_serial_batch_fields": args.use_serial_batch_fields,
|
||||
"serial_no": args.serial_no if args.use_serial_batch_fields else None,
|
||||
"batch_no": args.batch_no if args.use_serial_batch_fields else None,
|
||||
}
|
||||
# append in pos invoice items without item_code by checking flag without_item_code
|
||||
if args.without_item_code:
|
||||
@@ -1023,6 +1024,8 @@ def create_pos_invoice(**args):
|
||||
pos_inv.insert()
|
||||
if not args.do_not_submit:
|
||||
pos_inv.submit()
|
||||
if args.use_serial_batch_fields:
|
||||
pos_inv.reload()
|
||||
else:
|
||||
pos_inv.payment_schedule = []
|
||||
else:
|
||||
|
||||
@@ -634,7 +634,6 @@
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 1",
|
||||
"fieldname": "batch_no",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 1,
|
||||
"label": "Batch No",
|
||||
"options": "Batch",
|
||||
"print_hide": 1
|
||||
@@ -655,7 +654,6 @@
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 1",
|
||||
"fieldname": "serial_no",
|
||||
"fieldtype": "Text",
|
||||
"hidden": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Serial No",
|
||||
"oldfieldname": "serial_no",
|
||||
@@ -827,7 +825,7 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 1",
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.serial_and_batch_bundle",
|
||||
"fieldname": "serial_and_batch_bundle",
|
||||
"fieldtype": "Link",
|
||||
"label": "Serial and Batch Bundle",
|
||||
@@ -853,7 +851,7 @@
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-27 13:10:15.336778",
|
||||
"modified": "2024-05-07 15:56:53.343317",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Invoice Item",
|
||||
|
||||
@@ -131,6 +131,7 @@ class POSInvoiceMergeLog(Document):
|
||||
pos_invoice_docs = [frappe.get_cached_doc("POS Invoice", d.pos_invoice) for d in self.pos_invoices]
|
||||
|
||||
self.update_pos_invoices(pos_invoice_docs)
|
||||
self.serial_and_batch_bundle_reference_for_pos_invoice()
|
||||
self.cancel_linked_invoices()
|
||||
|
||||
def process_merging_into_sales_invoice(self, data):
|
||||
@@ -191,6 +192,7 @@ class POSInvoiceMergeLog(Document):
|
||||
for i in items:
|
||||
if (
|
||||
i.item_code == item.item_code
|
||||
and not i.serial_and_batch_bundle
|
||||
and not i.serial_no
|
||||
and not i.batch_no
|
||||
and i.uom == item.uom
|
||||
@@ -312,6 +314,12 @@ class POSInvoiceMergeLog(Document):
|
||||
doc.set_status(update=True)
|
||||
doc.save()
|
||||
|
||||
def serial_and_batch_bundle_reference_for_pos_invoice(self):
|
||||
for d in self.pos_invoices:
|
||||
pos_invoice = frappe.get_doc("POS Invoice", d.pos_invoice)
|
||||
for table_name in ["items", "packed_items"]:
|
||||
pos_invoice.set_serial_and_batch_bundle(table_name)
|
||||
|
||||
def cancel_linked_invoices(self):
|
||||
for si_name in [self.consolidated_invoice, self.consolidated_credit_note]:
|
||||
if not si_name:
|
||||
|
||||
@@ -593,6 +593,7 @@ erpnext.PointOfSale.Controller = class {
|
||||
new_item["serial_no"] = serial_no;
|
||||
}
|
||||
|
||||
new_item["use_serial_batch_fields"] = 1;
|
||||
if (field === "serial_no") new_item["qty"] = value.split(`\n`).length || 0;
|
||||
|
||||
item_row = this.frm.add_child("items", new_item);
|
||||
|
||||
@@ -101,7 +101,8 @@ erpnext.PointOfSale.ItemDetails = class {
|
||||
|
||||
const serialized = item_row.has_serial_no;
|
||||
const batched = item_row.has_batch_no;
|
||||
const no_bundle_selected = !item_row.serial_and_batch_bundle;
|
||||
const no_bundle_selected =
|
||||
!item_row.serial_and_batch_bundle && !item_row.serial_no && !item_row.batch_no;
|
||||
|
||||
if ((serialized && no_bundle_selected) || (batched && no_bundle_selected)) {
|
||||
frappe.show_alert({
|
||||
@@ -403,6 +404,7 @@ erpnext.PointOfSale.ItemDetails = class {
|
||||
frappe.model.set_value(item_row.doctype, item_row.name, {
|
||||
serial_and_batch_bundle: r.name,
|
||||
qty: Math.abs(r.total_qty),
|
||||
use_serial_batch_fields: 0,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -935,6 +935,9 @@ class SerialandBatchBundle(Document):
|
||||
self.validate_voucher_no_docstatus()
|
||||
|
||||
def validate_voucher_no_docstatus(self):
|
||||
if self.voucher_type == "POS Invoice":
|
||||
return
|
||||
|
||||
if frappe.db.get_value(self.voucher_type, self.voucher_no, "docstatus") == 1:
|
||||
msg = f"""The {self.voucher_type} {bold(self.voucher_no)}
|
||||
is in submitted state, please cancel it first"""
|
||||
@@ -1722,6 +1725,7 @@ def get_reserved_batches_for_pos(kwargs) -> dict:
|
||||
"`tabPOS Invoice Item`.warehouse",
|
||||
"`tabPOS Invoice Item`.name as child_docname",
|
||||
"`tabPOS Invoice`.name as parent_docname",
|
||||
"`tabPOS Invoice Item`.use_serial_batch_fields",
|
||||
"`tabPOS Invoice Item`.serial_and_batch_bundle",
|
||||
],
|
||||
filters=[
|
||||
@@ -1735,7 +1739,7 @@ def get_reserved_batches_for_pos(kwargs) -> dict:
|
||||
ids = [
|
||||
pos_invoice.serial_and_batch_bundle
|
||||
for pos_invoice in pos_invoices
|
||||
if pos_invoice.serial_and_batch_bundle
|
||||
if pos_invoice.serial_and_batch_bundle and not pos_invoice.use_serial_batch_fields
|
||||
]
|
||||
|
||||
if ids:
|
||||
|
||||
@@ -246,6 +246,9 @@ class SerialBatchBundle:
|
||||
frappe.throw(_(msg))
|
||||
|
||||
def delink_serial_and_batch_bundle(self):
|
||||
if self.is_pos_transaction():
|
||||
return
|
||||
|
||||
update_values = {
|
||||
"serial_and_batch_bundle": "",
|
||||
}
|
||||
@@ -295,8 +298,22 @@ class SerialBatchBundle:
|
||||
self.cancel_serial_and_batch_bundle()
|
||||
|
||||
def cancel_serial_and_batch_bundle(self):
|
||||
if self.is_pos_transaction():
|
||||
return
|
||||
|
||||
frappe.get_cached_doc("Serial and Batch Bundle", self.sle.serial_and_batch_bundle).cancel()
|
||||
|
||||
def is_pos_transaction(self):
|
||||
if (
|
||||
self.sle.voucher_type == "Sales Invoice"
|
||||
and self.sle.serial_and_batch_bundle
|
||||
and frappe.get_cached_value(
|
||||
"Serial and Batch Bundle", self.sle.serial_and_batch_bundle, "voucher_type"
|
||||
)
|
||||
== "POS Invoice"
|
||||
):
|
||||
return True
|
||||
|
||||
def submit_serial_and_batch_bundle(self):
|
||||
doc = frappe.get_doc("Serial and Batch Bundle", self.sle.serial_and_batch_bundle)
|
||||
self.validate_actual_qty(doc)
|
||||
|
||||
Reference in New Issue
Block a user