fix: serial and batch bundle for POS Invoice (#41491)

This commit is contained in:
rohitwaghchaure
2024-07-18 19:54:00 +05:30
committed by GitHub
parent e1b9b432c3
commit e5dfc5e545
9 changed files with 148 additions and 14 deletions

View File

@@ -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"

View File

@@ -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:

View File

@@ -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:

View File

@@ -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",

View File

@@ -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:

View File

@@ -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);

View File

@@ -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,
});
}
});

View File

@@ -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:

View File

@@ -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)