diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.py b/erpnext/selling/page/point_of_sale/point_of_sale.py index 999ddc23f0c..158ac1d049a 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.py +++ b/erpnext/selling/page/point_of_sale/point_of_sale.py @@ -17,45 +17,79 @@ from erpnext.stock.utils import scan_barcode def search_by_term(search_term, warehouse, price_list): result = search_for_serial_or_batch_or_barcode_number(search_term) or {} - item_code = result.get("item_code") or search_term - serial_no = result.get("serial_no") or "" - batch_no = result.get("batch_no") or "" - barcode = result.get("barcode") or "" + item_code = result.get("item_code", search_term) + serial_no = result.get("serial_no", "") + batch_no = result.get("batch_no", "") + barcode = result.get("barcode", "") - if result: - item_info = frappe.db.get_value( - "Item", - item_code, - [ - "name as item_code", - "item_name", - "description", - "stock_uom", - "image as item_image", - "is_stock_item", - ], - as_dict=1, - ) + if not result: + return - item_stock_qty, is_stock_item = get_stock_availability(item_code, warehouse) - price_list_rate, currency = frappe.db.get_value( - "Item Price", - {"price_list": price_list, "item_code": item_code}, - ["price_list_rate", "currency"], - ) or [None, None] + item_doc = frappe.get_doc("Item", item_code) - item_info.update( + if not item_doc: + return + + item = { + "barcode": barcode, + "batch_no": batch_no, + "description": item_doc.description, + "is_stock_item": item_doc.is_stock_item, + "item_code": item_doc.name, + "item_image": item_doc.image, + "item_name": item_doc.item_name, + "serial_no": serial_no, + "stock_uom": item_doc.stock_uom, + "uom": item_doc.stock_uom, + } + + if barcode: + barcode_info = next(filter(lambda x: x.barcode == barcode, item_doc.get("barcodes", [])), None) + if barcode_info and barcode_info.uom: + uom = next(filter(lambda x: x.uom == barcode_info.uom, item_doc.uoms), {}) + item.update( + { + "uom": barcode_info.uom, + "conversion_factor": uom.get("conversion_factor", 1), + } + ) + + item_stock_qty, is_stock_item = get_stock_availability(item_code, warehouse) + item_stock_qty = item_stock_qty // item.get("conversion_factor") + item.update({"actual_qty": item_stock_qty}) + + price = frappe.get_list( + doctype="Item Price", + filters={ + "price_list": price_list, + "item_code": item_code, + }, + fields=["uom", "stock_uom", "currency", "price_list_rate"], + ) + + def __sort(p): + p_uom = p.get("uom") + + if p_uom == item.get("uom"): + return 0 + elif p_uom == item.get("stock_uom"): + return 1 + else: + return 2 + + # sort by fallback preference. always pick exact uom match if available + price = sorted(price, key=__sort) + + if len(price) > 0: + p = price.pop(0) + item.update( { - "serial_no": serial_no, - "batch_no": batch_no, - "barcode": barcode, - "price_list_rate": price_list_rate, - "currency": currency, - "actual_qty": item_stock_qty, + "currency": p.get("currency"), + "price_list_rate": p.get("price_list_rate"), } ) - return {"items": [item_info]} + return {"items": [item]} @frappe.whitelist() @@ -121,33 +155,43 @@ def get_items(start, page_length, price_list, item_group, pos_profile, search_te as_dict=1, ) - if items_data: - items = [d.item_code for d in items_data] - item_prices_data = frappe.get_all( + # return (empty) list if there are no results + if not items_data: + return result + + for item in items_data: + uoms = frappe.get_doc("Item", item.item_code).get("uoms", []) + + item.actual_qty, _ = get_stock_availability(item.item_code, warehouse) + item.uom = item.stock_uom + + item_price = frappe.get_all( "Item Price", - fields=["item_code", "price_list_rate", "currency"], - filters={"price_list": price_list, "item_code": ["in", items]}, + fields=["price_list_rate", "currency", "uom"], + filters={ + "price_list": price_list, + "item_code": item.item_code, + "selling": True, + }, ) - item_prices = {} - for d in item_prices_data: - item_prices[d.item_code] = d + if not item_price: + result.append(item) - for item in items_data: - item_code = item.item_code - item_price = item_prices.get(item_code) or {} - item_stock_qty, is_stock_item = get_stock_availability(item_code, warehouse) + for price in item_price: + uom = next(filter(lambda x: x.uom == price.uom, uoms), {}) - row = {} - row.update(item) - row.update( + if price.uom != item.stock_uom and uom and uom.conversion_factor: + item.actual_qty = item.actual_qty // uom.conversion_factor + + result.append( { - "price_list_rate": item_price.get("price_list_rate"), - "currency": item_price.get("currency"), - "actual_qty": item_stock_qty, + **item, + "price_list_rate": price.get("price_list_rate"), + "currency": price.get("currency"), + "uom": price.uom or item.uom, } ) - result.append(row) return {"items": result} diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js index 595b9196e84..c442774d0f7 100644 --- a/erpnext/selling/page/point_of_sale/pos_controller.js +++ b/erpnext/selling/page/point_of_sale/pos_controller.js @@ -542,12 +542,12 @@ erpnext.PointOfSale.Controller = class { if (!this.frm.doc.customer) return this.raise_customer_selection_alert(); - const { item_code, batch_no, serial_no, rate } = item; + const { item_code, batch_no, serial_no, rate, uom } = item; if (!item_code) return; - const new_item = { item_code, batch_no, rate, [field]: value }; + const new_item = { item_code, batch_no, rate, uom, [field]: value }; if (serial_no) { await this.check_serial_no_availablilty(item_code, this.frm.doc.set_warehouse, serial_no); @@ -649,6 +649,7 @@ erpnext.PointOfSale.Controller = class { const is_stock_item = resp[1]; frappe.dom.unfreeze(); + const bold_uom = item_row.stock_uom.bold(); const bold_item_code = item_row.item_code.bold(); const bold_warehouse = warehouse.bold(); const bold_available_qty = available_qty.toString().bold() @@ -664,7 +665,7 @@ erpnext.PointOfSale.Controller = class { } } else if (is_stock_item && available_qty < qty_needed) { frappe.throw({ - message: __('Stock quantity not enough for Item Code: {0} under warehouse {1}. Available quantity {2}.', [bold_item_code, bold_warehouse, bold_available_qty]), + message: __('Stock quantity not enough for Item Code: {0} under warehouse {1}. Available quantity {2} {3}.', [bold_item_code, bold_warehouse, bold_available_qty, bold_uom]), indicator: 'orange' }); frappe.utils.play_sound("error"); diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js index e7dd211c0f4..12cc629776c 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_cart.js +++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js @@ -609,7 +609,7 @@ erpnext.PointOfSale.ItemCart = class { if (item_data.rate && item_data.amount && item_data.rate !== item_data.amount) { return `
-
${item_data.qty || 0}
+
${item_data.qty || 0} ${item_data.uom}
${format_currency(item_data.amount, currency)}
${format_currency(item_data.rate, currency)}
@@ -618,7 +618,7 @@ erpnext.PointOfSale.ItemCart = class { } else { return `
-
${item_data.qty || 0}
+
${item_data.qty || 0} ${item_data.uom}
${format_currency(item_data.rate, currency)}
diff --git a/erpnext/selling/page/point_of_sale/pos_item_selector.js b/erpnext/selling/page/point_of_sale/pos_item_selector.js index b5eb0489f9d..ec67bdfd9dd 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_selector.js +++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js @@ -78,7 +78,7 @@ erpnext.PointOfSale.ItemSelector = class { get_item_html(item) { const me = this; // eslint-disable-next-line no-unused-vars - const { item_image, serial_no, batch_no, barcode, actual_qty, stock_uom, price_list_rate } = item; + const { item_image, serial_no, batch_no, barcode, actual_qty, uom, price_list_rate } = item; const precision = flt(price_list_rate, 2) % 1 != 0 ? 2 : 0; let indicator_color; let qty_to_display = actual_qty; @@ -118,7 +118,7 @@ erpnext.PointOfSale.ItemSelector = class { return ( `
@@ -128,7 +128,7 @@ erpnext.PointOfSale.ItemSelector = class {
${frappe.ellipsis(item.item_name, 18)}
-
${format_currency(price_list_rate, item.currency, precision) || 0}
+
${format_currency(price_list_rate, item.currency, precision) || 0} / ${uom}
` ); diff --git a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js index 40165c3484f..be75bd64cfd 100644 --- a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js +++ b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js @@ -94,7 +94,7 @@ erpnext.PointOfSale.PastOrderSummary = class { get_item_html(doc, item_data) { return `
${item_data.item_name}
-
${item_data.qty || 0}
+
${item_data.qty || 0} ${item_data.uom}
${get_rate_discount_html()}
`;