From f3710ee58dfc31754eb192940a8e01528b1f9046 Mon Sep 17 00:00:00 2001 From: Dirk van der Laarse Date: Fri, 14 Nov 2025 15:15:37 +0000 Subject: [PATCH 1/2] fix: bring over fixes from bug/fix-general-bugs-2 --- posnext/overrides/pos_closing_entry.py | 31 +++-- posnext/overrides/pos_invoice_merge_log.py | 35 ++--- posnext/posnext/page/posnext/point_of_sale.py | 17 +-- posnext/public/js/pos_controller.js | 16 ++- posnext/public/js/pos_item_cart.js | 121 +++++++++++------- 5 files changed, 124 insertions(+), 96 deletions(-) diff --git a/posnext/overrides/pos_closing_entry.py b/posnext/overrides/pos_closing_entry.py index 88d03f9..0a91617 100644 --- a/posnext/overrides/pos_closing_entry.py +++ b/posnext/overrides/pos_closing_entry.py @@ -17,7 +17,7 @@ def get_pos_invoices(start, end, pos_profile, user): select name, timestamp(posting_date, posting_time) as "timestamp" from - `tabSales Invoice` + `tabPOS Invoice` where owner = %s and docstatus = 1 and pos_profile = %s """, @@ -30,7 +30,7 @@ def get_pos_invoices(start, end, pos_profile, user): data = [d for d in data if start_dt <= get_datetime(d.timestamp) <= end_dt] # need to get taxes and payments so can't avoid get_doc - data = [frappe.get_doc("Sales Invoice", d.name).as_dict() for d in data] + data = [frappe.get_doc("POS Invoice", d.name).as_dict() for d in data] return data @@ -49,31 +49,34 @@ def validate_pos_invoices(self): invalid_rows = [] for d in self.pos_transactions: invalid_row = {"idx": d.idx} - pos_invoice = frappe.db.get_values( - "Sales Invoice", + pos_invoice_data = frappe.db.get_values( + "POS Invoice", d.pos_invoice, ["pos_profile", "docstatus", "owner"], as_dict=1, )[0] - # if pos_invoice.consolidated_invoice: - # invalid_row.setdefault("msg", []).append( - # _("Sales Invoice is {}").format(frappe.bold("already consolidated")) - # ) - # invalid_rows.append(invalid_row) - # continue + + if not pos_invoice_data: + invalid_row.setdefault("msg", []).append( + _("POS Invoice {} not found").format(frappe.bold(d.pos_invoice)) + ) + invalid_rows.append(invalid_row) + continue + + pos_invoice = pos_invoice_data[0] if pos_invoice.pos_profile != self.pos_profile: invalid_row.setdefault("msg", []).append( - _("Sales Profile doesn't matches {}").format( + _("POS Profile doesn't matches {}").format( frappe.bold(self.pos_profile) ) ) if pos_invoice.docstatus != 1: invalid_row.setdefault("msg", []).append( - _("Sales Invoice is not {}").format(frappe.bold("submitted")) + _("POS Invoice is not {}").format(frappe.bold("submitted")) ) if pos_invoice.owner != self.user: invalid_row.setdefault("msg", []).append( - _("Sales Invoice isn't created by user {}").format( + _("POS Invoice isn't created by user {}").format( frappe.bold(self.owner) ) ) @@ -89,4 +92,4 @@ def validate_pos_invoices(self): for msg in row.get("msg"): error_list.append(_("Row #{}: {}").format(row.get("idx"), msg)) - frappe.throw(error_list, title=_("Invalid Sales Invoices"), as_list=True) + frappe.throw(error_list, title=_("Invalid POS Invoices"), as_list=True) diff --git a/posnext/overrides/pos_invoice_merge_log.py b/posnext/overrides/pos_invoice_merge_log.py index 4572beb..8de5cf7 100644 --- a/posnext/overrides/pos_invoice_merge_log.py +++ b/posnext/overrides/pos_invoice_merge_log.py @@ -19,7 +19,7 @@ def serial_and_batch_bundle_reference_for_pos_invoice(self): def on_cancel(self): pos_invoice_docs = [ - frappe.get_cached_doc("Sales Invoice", d.pos_invoice) + frappe.get_cached_doc("POS Invoice", d.pos_invoice) for d in self.pos_invoices ] @@ -28,28 +28,15 @@ def on_cancel(self): self.cancel_linked_invoices() def on_submit(self): - pos_invoice_docs = [ - frappe.get_cached_doc("Sales Invoice", d.pos_invoice) - for d in self.pos_invoices - ] - - returns = [d for d in pos_invoice_docs if d.get("is_return") == 1] - sales = [d for d in pos_invoice_docs if d.get("is_return") == 0] - - sales_invoice, credit_note = "", "" - if returns: - credit_note = self.process_merging_into_credit_note(returns) - - if sales: - sales_invoice = self.process_merging_into_sales_invoice(sales) - - self.save() # save consolidated_sales_invoice & consolidated_credit_note ref in merge log - self.update_pos_invoices(pos_invoice_docs, sales_invoice, credit_note) + # Set serial and batch bundle references first + self.serial_and_batch_bundle_reference_for_pos_invoice() + # Then call parent's on_submit which has all the consolidation logic + super().on_submit() def validate_pos_invoice_status(self): for d in self.pos_invoices: status, docstatus, is_return, return_against = frappe.db.get_value( - "Sales Invoice", + "POS Invoice", d.pos_invoice, ["status", "docstatus", "is_return", "return_against"], ) @@ -58,13 +45,13 @@ def validate_pos_invoice_status(self): bold_status = frappe.bold(status) if docstatus != 1: frappe.throw( - _("Row #{}: Sales Invoice {} is not submitted yet").format( + _("Row #{}: POS Invoice {} is not submitted yet").format( d.idx, bold_pos_invoice ) ) if status == "Consolidated": frappe.throw( - _("Row #{}: Sales Invoice {} has been {}").format( + _("Row #{}: POS Invoice {} has been {}").format( d.idx, bold_pos_invoice, bold_status ) ) @@ -75,7 +62,7 @@ def validate_pos_invoice_status(self): ): bold_return_against = frappe.bold(return_against) return_against_status = frappe.db.get_value( - "Sales Invoice", return_against, "status" + "POS Invoice", return_against, "status" ) if return_against_status != "Consolidated": # if return entry is not getting merged in the current pos closing and if it is not consolidated @@ -121,7 +108,7 @@ def split_invoices(invoices): _invoices = [] special_invoices = [] pos_return_docs = [ - frappe.get_cached_doc("Sales Invoice", d.pos_invoice) + frappe.get_cached_doc("POS Invoice", d.pos_invoice) for d in invoices if d.is_return and d.return_against ] @@ -183,7 +170,7 @@ def get_all_unconsolidated_invoices(): "docstatus": 1, } pos_invoices = frappe.db.get_all( - "Sales Invoice", + "POS Invoice", filters=filters, fields=[ "name as pos_invoice", diff --git a/posnext/posnext/page/posnext/point_of_sale.py b/posnext/posnext/page/posnext/point_of_sale.py index c10e730..169f874 100644 --- a/posnext/posnext/page/posnext/point_of_sale.py +++ b/posnext/posnext/page/posnext/point_of_sale.py @@ -405,7 +405,7 @@ def get_past_order_list(search_term, status, pos_profile=None, limit=20): "pos_profile": pos_profile, } invoices_by_customer = frappe.db.get_all( - "Sales Invoice", + "POS Invoice", filters=fltr1, fields=fields, page_length=limit, @@ -418,7 +418,7 @@ def get_past_order_list(search_term, status, pos_profile=None, limit=20): "pos_profile": pos_profile, } invoices_by_name = frappe.db.get_all( - "Sales Invoice", + "POS Invoice", filters=fltr2, fields=fields, page_length=limit, @@ -430,7 +430,7 @@ def get_past_order_list(search_term, status, pos_profile=None, limit=20): if pos_profile: fltr = {"status": status, "pos_profile": pos_profile} invoice_list = frappe.db.get_all( - "Sales Invoice", filters=fltr, fields=fields, page_length=limit + "POS Invoice", filters=fltr, fields=fields, page_length=limit ) return invoice_list @@ -527,7 +527,7 @@ def generate_pdf_and_save(docname, doctype, print_format=None): def make_sales_return(source_name, target_doc=None): from erpnext.controllers.sales_and_purchase_return import make_return_doc - return make_return_doc("Sales Invoice", source_name, target_doc) + return make_return_doc("POS Invoice", source_name, target_doc) @frappe.whitelist() @@ -536,10 +536,11 @@ def get_lcr(customer=None, item_code=None): if customer and item_code: d = frappe.db.sql( f""" - SELECT item.rate FROM `tabSales Invoice Item` item INNER JOIN `tabSales Invoice` SI ON SI.name=item.parent - WHERE SI.customer='{customer}' AND item.item_code='{item_code}' - ORDER BY SI.creation desc - LIMIT 1 + SELECT item.rate FROM `tabPOS Invoice Item` item + INNER JOIN `tabPOS Invoice` PI ON PI.name=item.parent + WHERE PI.customer='{customer}' AND item.item_code='{item_code}' + AND PI.docstatus = 1 + ORDER BY PI.creation desc """, as_dict=True, ) diff --git a/posnext/public/js/pos_controller.js b/posnext/public/js/pos_controller.js index c720226..a6c9a4c 100644 --- a/posnext/public/js/pos_controller.js +++ b/posnext/public/js/pos_controller.js @@ -15,7 +15,7 @@ posnext.PointOfSale.Controller = class { this.setup_form_events(); } setup_form_events() { - frappe.ui.form.on("Sales Invoice", { + frappe.ui.form.on("POS Invoice", { after_save: function (frm) { if (!frm.doc.pos_profile) return; @@ -516,7 +516,7 @@ posnext.PointOfSale.Controller = class { wrapper: this.$components_wrapper, events: { open_invoice_data: (name) => { - frappe.db.get_doc("Sales Invoice", name).then((doc) => { + frappe.db.get_doc("POS Invoice", name).then((doc) => { this.order_summary.load_summary_of(doc); }); }, @@ -542,7 +542,7 @@ posnext.PointOfSale.Controller = class { process_return: (name) => { this.recent_order_list.toggle_component(false); - frappe.db.get_doc("Sales Invoice", name).then((doc) => { + frappe.db.get_doc("POS Invoice", name).then((doc) => { frappe.run_serially([ () => this.make_return_invoice(doc), () => this.cart.load_invoice(), @@ -618,7 +618,7 @@ posnext.PointOfSale.Controller = class { } make_sales_invoice_frm() { - const doctype = "Sales Invoice"; + const doctype = "POS Invoice"; return new Promise((resolve) => { if (this.frm) { this.frm = this.get_new_frm(this.frm); @@ -639,7 +639,7 @@ posnext.PointOfSale.Controller = class { } get_new_frm(_frm) { - const doctype = "Sales Invoice"; + const doctype = "POS Invoice"; const page = $("
"); const frm = _frm || new frappe.ui.form.Form(doctype, page, false); const name = frappe.model.make_new_doc_and_get_name(doctype, true); @@ -809,6 +809,10 @@ posnext.PointOfSale.Controller = class { total_incoming_rate += parseFloat(item.valuation_rate) * item.qty; }); this.item_selector.update_total_incoming_rate(total_incoming_rate); + if (item_row) { + this.cart.update_totals_section(this.frm); + this.cart.update_item_html(item_row); + } return item_row; // eslint-disable-line no-unsafe-finally } @@ -961,7 +965,7 @@ posnext.PointOfSale.Controller = class { frappe.throw({ title: __("Not Available"), message: __( - "Serial No: {0} has already been transacted into another Sales Invoice.", + "Serial No: {0} has already been transacted into another POS Invoice.", [serial_no.bold()], ), }); diff --git a/posnext/public/js/pos_item_cart.js b/posnext/public/js/pos_item_cart.js index 4c19d20..7d1bc23 100644 --- a/posnext/public/js/pos_item_cart.js +++ b/posnext/public/js/pos_item_cart.js @@ -79,40 +79,74 @@ posnext.PointOfSale.ItemCart = class { init_cart_components() { var html = `
-
${__("Item Cart")}
-
-
${__("Item")}
-
${__("Qty")}
- `; +
${__("Item Cart")}
`; + + // Calculate flex for item name based on discount settings + let item_name_flex = 3.5; + if ( + this.custom_use_discount_percentage && + !this.custom_use_discount_amount + ) { + item_name_flex = 2.8; + } + if ( + this.custom_use_discount_amount && + !this.custom_use_discount_percentage + ) { + item_name_flex = 2.8; + } + if ( + this.custom_use_discount_amount && + this.custom_use_discount_percentage + ) { + item_name_flex = 2.5; + } + + // Item name header + html += `
${__( + "Item", + )}
`; + + // Wrap remaining headers in container matching item-qty-rate flex + const header_container_flex = this.custom_edit_rate ? 6 : 4; + html += `
`; + + // All other headers inside wrapper + html += `
${__("Qty")}
`; if (this.custom_show_uom_in_cart) { html += `
${__("UOM")}
`; } + if (this.show_batch_in_cart) { html += `
${__("Batch")}
`; } + if (this.custom_edit_rate) { html += `
${__("Rate")}
`; } + if (this.custom_use_discount_percentage) { html += `
${__( "Disc%", )}
`; } + if (this.custom_use_discount_amount) { html += `
${__( "Disc", )}
`; } + if (this.custom_show_incoming_rate) { html += `
${__( "Inc.Rate", )}
`; } + if (this.custom_show_logical_rack_in_cart) { - html += `
${__( - "Rack", - )}
`; + html += `
${__("Rack")}
`; } + if (this.custom_show_last_customer_rate) { html += `
${__( "LC Rate", @@ -121,8 +155,9 @@ posnext.PointOfSale.ItemCart = class { html += `
${__( "Amount", - )}
-
+ )}
`; + html += `
`; // Close wrapper div + html += `
@@ -1169,7 +1204,10 @@ posnext.PointOfSale.ItemCart = class { item_html += `
`; } - item_html += `
+ item_html += `
+ ${item_data.item_code} +
+
${item_data.item_name}
${get_description_html(item_data)} @@ -1488,46 +1526,41 @@ posnext.PointOfSale.ItemCart = class { return html; } } else { + // When custom_edit_rate is FALSE - show read-only view + let html = `
+
${item_data.qty || 0}
`; + + // Conditionally show UOM + if (me.custom_show_uom_in_cart) { + html += `
${ + item_data.uom || "" + }
`; + } + + // Conditionally show Batch + if (me.show_batch_in_cart) { + html += `
${ + item_data.batch_no || "" + }
`; + } + + // Always show rate/amount if ( item_data.rate && item_data.amount && item_data.rate !== item_data.amount ) { - return ` -
-
${ - item_data.qty || 0 - }
-
${ - item_data.uom - }
-
${item_data.batch}
-
-
${parseFloat( - item_data.amount, - ).toFixed(2)}
-
${parseFloat( - item_data.rate, - ).toFixed(2)}
-
-
`; + html += `
+
${parseFloat(item_data.amount).toFixed(2)}
+
${parseFloat(item_data.rate).toFixed(2)}
+
`; } else { - return ` -
-
${ - item_data.qty || 0 - }
-
${ - item_data.uom - }
-
${item_data.batch}
-
-
${parseFloat( - item_data.rate, - ).toFixed(2)}
-
-
`; + html += `
+
${parseFloat(item_data.rate).toFixed(2)}
+
`; } + html += `
`; + return html; } } From 5e46692f6019bb88ffeca6d0fd9fed42dfecb2fe Mon Sep 17 00:00:00 2001 From: Dirk van der Laarse Date: Fri, 14 Nov 2025 15:15:58 +0000 Subject: [PATCH 2/2] chore: bump to v0.1.1 --- posnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/posnext/__init__.py b/posnext/__init__.py index 3dc1f76..485f44a 100644 --- a/posnext/__init__.py +++ b/posnext/__init__.py @@ -1 +1 @@ -__version__ = "0.1.0" +__version__ = "0.1.1"