From 39d93f8b35dbad89707ebebf5187854144942c84 Mon Sep 17 00:00:00 2001 From: "Lafco CTO (laidan)" Date: Thu, 16 Apr 2026 21:41:37 -0700 Subject: [PATCH 1/3] fix(invoices): always recompute paid_amount from payment rows on update + submit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the paid_amount/base_paid_amount recomputation out of the is_return branch so regular Sales Invoices with POS payment rows also get the totals recalculated before save and again on submit. This prevents the invoice from being marked "Pay on Account" when payment rows exist. Restored from cddf4205 invoices.py delta only — i18n dump in the same local snapshot is intentionally NOT carried over (see LAF-6 plan). Co-Authored-By: Paperclip --- pos_next/api/invoices.py | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/pos_next/api/invoices.py b/pos_next/api/invoices.py index e56fd3fb..15093fe3 100644 --- a/pos_next/api/invoices.py +++ b/pos_next/api/invoices.py @@ -899,19 +899,22 @@ def update_invoice(data): # Set accounts for payment methods before saving _set_payment_accounts(invoice_doc.payments, invoice_doc.company) - # For return invoices, ensure payments are negative - if invoice_doc.get("is_return"): - # Return handling is primarily for Sales Invoice - if doctype == "Sales Invoice" and invoice_doc.get("payments"): + # Calculate paid_amount and base_paid_amount from payment rows + # This applies to both regular and return invoices (required before submission) + if doctype == "Sales Invoice" and invoice_doc.get("payments"): + # For return invoices, ensure payments are negative + if invoice_doc.get("is_return"): for payment in invoice_doc.payments: payment.amount = -abs(payment.amount) if payment.base_amount: payment.base_amount = -abs(payment.base_amount) - invoice_doc.paid_amount = flt(sum(p.amount for p in invoice_doc.payments)) - invoice_doc.base_paid_amount = flt( - sum(p.base_amount or 0 for p in invoice_doc.payments) - ) + # Calculate total paid amount from all payment rows + # This ensures the invoice is not marked as "Pay on Account" when payments exist + invoice_doc.paid_amount = flt(sum(p.amount for p in invoice_doc.payments)) + invoice_doc.base_paid_amount = flt( + sum(p.base_amount or 0 for p in invoice_doc.payments) + ) # Validate and track POS Coupon if coupon_code is provided coupon_code = data.get("coupon_code") @@ -1306,6 +1309,22 @@ def submit_invoice(invoice=None, data=None): if doctype == "Sales Invoice" and hasattr(invoice_doc, "payments"): _set_payment_accounts(invoice_doc.payments, invoice_doc.company) + # Calculate paid_amount and base_paid_amount from payment rows + # This ensures the invoice is not marked as "Pay on Account" when payments exist + if invoice_doc.get("payments"): + # For return invoices, ensure payments are negative + if invoice_doc.get("is_return"): + for payment in invoice_doc.payments: + payment.amount = -abs(payment.amount) + if payment.base_amount: + payment.base_amount = -abs(payment.base_amount) + + # Calculate total paid amount from all payment rows + invoice_doc.paid_amount = flt(sum(p.amount for p in invoice_doc.payments)) + invoice_doc.base_paid_amount = flt( + sum(p.base_amount or 0 for p in invoice_doc.payments) + ) + # Handle sales team (multiple sales persons) sales_team_data = invoice.get("sales_team") or data.get("sales_team") if sales_team_data and isinstance(sales_team_data, list): From 90fea270b2e7fcac0f1274c9b98b983cfb3e84e0 Mon Sep 17 00:00:00 2001 From: "Lafco CTO (laidan)" Date: Fri, 24 Apr 2026 04:16:45 -0700 Subject: [PATCH 2/3] feat(pos_settings): rename DocType to POS Next Settings for ERPNext v16 compat MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ERPNext v16 introduces a singleton DocType named 'POS Settings' (with fields like invoice_type, post_change_gl_entries). This collides with pos_next's existing per-profile (non-single) 'POS Settings' DocType and causes: - ERPNext v16 patch 1 (set_invoice_type_in_pos_settings) to hard-fail 'bench migrate' with ValidationError: Field invoice_type does not exist on POS Settings. - ERPNext v16 patch 22 (set_post_change_gl_entries_on_pos_settings) to silently write an orphan row to tabSingles against a non-single doctype. This patch renames pos_next's DocType from 'POS Settings' to 'POS Next Settings', freeing the canonical 'POS Settings' name for ERPNext v16 to install its singleton. All pos_next code, workspace links, whitelisted API dotted paths, and the frontend permission check are updated accordingly. Migration: pos_next.patches.v1_7_0.rename_pos_settings_to_pos_next_settings runs in pre_model_sync so it executes BEFORE ERPNext v16's DocType sync creates the new single 'POS Settings'. The patch is idempotent — safe on fresh v16 installs, v15 sites with pos_next's POS Settings, and already-renamed state. Downstream: the v16 path in overrides/sales_invoice.py can now call frappe.db.get_single_value('POS Settings', 'post_change_gl_entries') directly — the Singles-table Query-Builder fallback is no longer needed. Co-Authored-By: Paperclip --- POS/src/components/settings/POSSettings.vue | 1252 ++-- POS/src/composables/usePermissions.js | 94 +- POS/src/stores/posSettings.js | 276 +- pos_next/api/bootstrap.py | 29 +- pos_next/api/credit_sales.py | 270 +- pos_next/api/customers.py | 435 +- pos_next/api/invoices.py | 5320 ++++++++--------- pos_next/api/localization.py | 30 +- pos_next/api/pos_profile.py | 244 +- pos_next/api/sales_invoice_hooks.py | 22 +- pos_next/api/test_customers.py | 191 +- pos_next/api/wallet.py | 155 +- pos_next/overrides/sales_invoice.py | 23 +- pos_next/patches.txt | 1 + ...ename_pos_settings_to_pos_next_settings.py | 77 + .../__init__.py | 0 .../pos_next_settings.js} | 2 +- .../pos_next_settings.json} | 2 +- .../pos_next_settings.py} | 64 +- .../test_pos_next_settings.py} | 2 +- pos_next/pos_next/doctype/wallet/wallet.py | 79 +- .../pos_next/workspace/posnext/posnext.json | 4 +- pos_next/services/barcode.py | 291 +- 23 files changed, 4592 insertions(+), 4271 deletions(-) create mode 100644 pos_next/patches/v1_7_0/rename_pos_settings_to_pos_next_settings.py rename pos_next/pos_next/doctype/{pos_settings => pos_next_settings}/__init__.py (100%) rename pos_next/pos_next/doctype/{pos_settings/pos_settings.js => pos_next_settings/pos_next_settings.js} (95%) rename pos_next/pos_next/doctype/{pos_settings/pos_settings.json => pos_next_settings/pos_next_settings.json} (99%) rename pos_next/pos_next/doctype/{pos_settings/pos_settings.py => pos_next_settings/pos_next_settings.py} (71%) rename pos_next/pos_next/doctype/{pos_settings/test_pos_settings.py => pos_next_settings/test_pos_next_settings.py} (76%) diff --git a/POS/src/components/settings/POSSettings.vue b/POS/src/components/settings/POSSettings.vue index 89ca3e85..f4e7c4a3 100644 --- a/POS/src/components/settings/POSSettings.vue +++ b/POS/src/components/settings/POSSettings.vue @@ -8,21 +8,52 @@ >
-
+
-
+
- - - + + +
-

{{ __('POS Settings') }}

+

+ {{ __("POS Settings") }} +

- - + + {{ settings.pos_profile || posProfile }}

@@ -36,11 +67,21 @@ size="sm" > - {{ __('Refresh') }} + {{ __("Refresh") }}
@@ -69,49 +130,102 @@
-
-
-

{{ __('Loading settings...') }}

+
+
+

+ {{ __("Loading settings...") }} +

-
+
-
+
- - + +
-

{{ __('Stock Management') }}

-

{{ __('Configure warehouse and inventory settings') }}

+

+ {{ __("Stock Management") }} +

+

+ {{ + __( + "Configure warehouse and inventory settings" + ) + }} +

- - + + - {{ __('Stock Controls') }} + {{ + __("Stock Controls") + }}
@@ -119,49 +233,110 @@
- - + + -

{{ __('Warehouse Selection') }}

+

+ {{ __("Warehouse Selection") }} +

-
- - +
+ + -

{{ __('Loading warehouses...') }}

+

+ {{ __("Loading warehouses...") }} +

- - + + -

{{ __('Stock Validation Policy') }}

+

+ {{ __("Stock Validation Policy") }} +

- - + + -
@@ -171,17 +346,43 @@
- - + + -

{{ __('Background Stock Sync') }}

-
-
- {{ __('Active') }} +

+ {{ __("Background Stock Sync") }} +

+
+
+ {{ + __("Active") + }}
-
-
- {{ __('Inactive') }} +
+
+ {{ + __("Inactive") + }}
@@ -190,63 +391,147 @@ -
+
-
+
- - + + -
- + - -
-
+
- - + +
-

{{ __('Network Usage:') }}

-

{{ __('~15 KB per sync cycle') }}

-

{{ __('~{0} MB per hour', [Math.round((3600 / stockSyncIntervalSeconds) * 15 / 1024)]) }}

+

+ {{ __("Network Usage:") }} +

+

+ {{ __("~15 KB per sync cycle") }} +

+

+ {{ + __("~{0} MB per hour", [ + Math.round( + ((3600 / + stockSyncIntervalSeconds) * + 15) / + 1024 + ), + ]) + }} +

@@ -257,25 +542,58 @@
-
+
- - + +
-

{{ __('Sales Management') }}

-

{{ __('Configure pricing, discounts, and sales operations') }}

+

+ {{ __("Sales Management") }} +

+

+ {{ + __( + "Configure pricing, discounts, and sales operations" + ) + }} +

- - + + - {{ __('Sales Controls') }} + {{ + __("Sales Controls") + }}
@@ -283,16 +601,32 @@
- - + + -

{{ __('Pricing & Discounts') }}

+

+ {{ __("Pricing & Discounts") }} +

@@ -332,10 +676,22 @@
- - + + -

{{ __('Sales Operations') }}

+

+ {{ __("Sales Operations") }} +

-
+
- {{ qzConnecting ? __('Connecting to QZ Tray...') : qzConnected ? __('QZ Tray Connected') : __('QZ Tray Not Connected') }} + {{ + qzConnecting + ? __("Connecting to QZ Tray...") + : qzConnected + ? __("QZ Tray Connected") + : __("QZ Tray Not Connected") + }}
@@ -394,7 +777,14 @@ v-model="selectedPrinter" :label="__('Printer')" :options="printerOptions" - :description="qzPrinters.length === 0 && !loadingPrinters ? __('No printers found. Is QZ Tray running?') : ''" + :description=" + qzPrinters.length === 0 && + !loadingPrinters + ? __( + 'No printers found. Is QZ Tray running?' + ) + : '' + " />
@@ -420,49 +821,108 @@ qzCertStatus === 'trusted' ? 'bg-green-50 border-green-200' : qzCertStatus === 'untrusted' - ? 'bg-red-50 border-red-200' - : 'bg-amber-50 border-amber-200' + ? 'bg-red-50 border-red-200' + : 'bg-amber-50 border-amber-200', ]" >
- - + +
-
+

- {{ __('Silent Print Certificate') }} + {{ + __( + "Silent Print Certificate" + ) + }}

- - {{ __('Installed') }} + + {{ + __("Installed") + }} - - {{ __('Not Installed') }} + + {{ + __("Not Installed") + }} - - {{ __('Checking...') }} + + {{ + __("Checking...") + }}
@@ -471,70 +931,173 @@ v-if="qzCertStatus === 'trusted'" class="text-xs text-green-800 leading-relaxed mb-2" > - {{ __('Certificate is installed and signing is active. Print jobs will be sent silently without confirmation dialogs.') }} + {{ + __( + "Certificate is installed and signing is active. Print jobs will be sent silently without confirmation dialogs." + ) + }}

- {{ __('Certificate is not installed on this machine. Generate a certificate, download it, and import it into QZ Tray.') }} + {{ + __( + "Certificate is not installed on this machine. Generate a certificate, download it, and import it into QZ Tray." + ) + }}

- {{ __('To print without confirmation dialogs, generate a signing certificate and install it on each POS machine.') }} + {{ + __( + "To print without confirmation dialogs, generate a signing certificate and install it on each POS machine." + ) + }}

-
+
-

- {{ __('Download the certificate and import it into QZ Tray, then restart QZ Tray.') }} + {{ + __( + "Download the certificate and import it into QZ Tray, then restart QZ Tray." + ) + }}

-
+
- - + + -

- {{ __('QZ Tray must be installed and running on this computer. Download from') }} - qz.io. - {{ __('If QZ Tray is unavailable, printing will fall back to the browser dialog.') }} +

+ {{ + __( + "QZ Tray must be installed and running on this computer. Download from" + ) + }} + qz.io. + {{ + __( + "If QZ Tray is unavailable, printing will fall back to the browser dialog." + ) + }}

@@ -543,17 +1106,38 @@
-
-
- - - +
+ + + -

{{ __('No POS Profile Selected') }}

-

{{ __('Please select a POS Profile to configure settings') }}

+

+ {{ __("No POS Profile Selected") }} +

+

+ {{ __("Please select a POS Profile to configure settings") }} +

@@ -563,43 +1147,39 @@