Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
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


Expand Down Expand Up @@ -179,6 +183,94 @@ def test_pos_closing_for_required_accounting_dimension_in_pos_profile(self):
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"
Expand Down
19 changes: 14 additions & 5 deletions erpnext/accounts/doctype/pos_invoice/pos_invoice.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,9 @@ def on_submit(self):
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
Expand Down Expand Up @@ -283,10 +285,11 @@ def delink_serial_and_batch_bundle(self):
{"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)

Expand Down Expand Up @@ -355,10 +358,16 @@ def validate_serialised_or_batched_item(self):
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:
Expand Down
9 changes: 6 additions & 3 deletions erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
Original file line number Diff line number Diff line change
Expand Up @@ -780,8 +780,6 @@ def test_pos_batch_reservation(self):
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"})
)
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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",
Expand Down Expand Up @@ -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",
Expand All @@ -853,7 +851,7 @@
],
"istable": 1,
"links": [],
"modified": "2024-02-25 15:50:17.140269",
"modified": "2024-05-07 15:56:53.343317",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Invoice Item",
Expand All @@ -863,4 +861,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ def on_cancel(self):
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):
Expand Down Expand Up @@ -191,6 +192,7 @@ def merge_pos_invoice_into(self, invoice, data):
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
Expand Down Expand Up @@ -312,6 +314,12 @@ def update_pos_invoices(self, invoice_docs, sales_invoice="", credit_note=""):
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:
Expand Down
1 change: 1 addition & 0 deletions erpnext/selling/page/point_of_sale/pos_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,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);
Expand Down
4 changes: 3 additions & 1 deletion erpnext/selling/page/point_of_sale/pos_item_details.js
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down Expand Up @@ -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,
});
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -935,6 +935,9 @@ def on_cancel(self):
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"""
Expand Down Expand Up @@ -1718,6 +1721,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=[
Expand All @@ -1731,7 +1735,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:
Expand Down
17 changes: 17 additions & 0 deletions erpnext/stock/serial_batch_bundle.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,9 @@ def validate_item_and_warehouse(self):
frappe.throw(_(msg))

def delink_serial_and_batch_bundle(self):
if self.is_pos_transaction():
return

update_values = {
"serial_and_batch_bundle": "",
}
Expand Down Expand Up @@ -295,8 +298,22 @@ def post_process(self):
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)
Expand Down
Loading