From 1a9c0893c08c4ee79a14b6b1ccd5849d4e0546f9 Mon Sep 17 00:00:00 2001 From: Sridhar Bala Date: Wed, 19 Aug 2026 12:08:51 +0530 Subject: [PATCH 001/673] Owner ask: HSN search understands words, suggests codes from the item The HSN box now searches all 8,924 rows by text as well as code - electron finds the electronics chapters - case-insensitive, filtered locally after a one-time load, 80ms debounce so typing never waits. Rows render in the shared suggestion layout with the GST rate as a chip. Picking fills code, description and the numeric rate (the old flow pasted 18% with the percent sign into the tax box). Switching to HSN mode with an empty code now suggests the top three codes scored from the item and category names as clickable chips. Rates are labelled indicative until a post-reform refresh of the dataset - the merchant confirms the slab. Root suite green, bundle verified. --- frontend/modules/items_write.html | 4 +- frontend/static/script/js/modules/js/items.js | 95 +++++++++++++++---- 2 files changed, 78 insertions(+), 21 deletions(-) diff --git a/frontend/modules/items_write.html b/frontend/modules/items_write.html index 3653c9827..de5a434b0 100644 --- a/frontend/modules/items_write.html +++ b/frontend/modules/items_write.html @@ -308,8 +308,10 @@
+ name="items_hsncode" maxlength="60" placeholder="Type a code or a word - e.g. 8517 or electronics" autocomplete="off"> + + Rates shown are indicative - confirm the current GST slab.
diff --git a/frontend/static/script/js/modules/js/items.js b/frontend/static/script/js/modules/js/items.js index e903aa540..d84f5163f 100644 --- a/frontend/static/script/js/modules/js/items.js +++ b/frontend/static/script/js/modules/js/items.js @@ -3411,37 +3411,92 @@ $('#bar-fontSize').change(function () { +/* + * HSN lookup (owner ask): search by text as well as code - "electron" + * finds electronics rows - and suggest codes from the item and category + * names. The 8,924-row list loads once on demand and stays in memory; + * every keystroke filters locally, nothing blocks typing. + */ +PosnicPro.items._hsnRows = null; +PosnicPro.items._hsnLoad = function (done) { + if (PosnicPro.items._hsnRows) { done(PosnicPro.items._hsnRows); return; } + PosnicPro.get({ url: 'items/getJSONhsncode' }, function (data) { + var rows = (data && data.data && data.data.hsn) || []; + rows.forEach(function (r) { r._d = (r.description || '').toLowerCase(); }); + PosnicPro.items._hsnRows = rows; + done(rows); + }, function () { done([]); }); +}; +PosnicPro.items._hsnRate = function (r) { + var n = parseFloat(String(r.taxrate || '').replace('%', '')); + return isFinite(n) ? n : 0; +}; +PosnicPro.items._hsnApply = function (r) { + $('#items_hsncode').val(r.value); + $('#items_hsndescription').val(r.description); + $('#hsn_tax').val(PosnicPro.items._hsnRate(r)); + $('#hsn_suggest_row').hide(); +}; +/* Top matches for the item + category words - the "right tax" nudge. */ +PosnicPro.items.hsnSuggest = function () { + if ($("input[name='hsntax_radio_value']:checked").val() !== 'hsncode') { return; } + if ($.trim($('#items_hsncode').val())) { return; } + var words = ($('#items_name').val() + ' ' + ($('.items_category option:selected').text() || '')) + .toLowerCase().split(/[^a-z]+/).filter(function (w) { return w.length > 3; }); + if (!words.length) { $('#hsn_suggest_row').hide(); return; } + PosnicPro.items._hsnLoad(function (rows) { + var scored = []; + for (var i = 0; i < rows.length; i++) { + var hit = 0; + for (var j = 0; j < words.length; j++) { + if (rows[i]._d.indexOf(words[j]) !== -1) { hit++; } + } + if (hit) { scored.push([hit, rows[i]]); } + } + scored.sort(function (a, b) { return b[0] - a[0]; }); + var top = scored.slice(0, 3).map(function (x) { return x[1]; }); + if (!top.length) { $('#hsn_suggest_row').hide(); return; } + var esc = function (v) { return $('').text(v == null ? '' : v).html(); }; + $('#hsn_suggest_row').html('Suggested:' + top.map(function (r, i) { + return '' + + esc(r.value) + ' · ' + esc((r.description || '').slice(0, 34)) + + ' · ' + esc(String(r.taxrate || '').replace('%', '') || '0') + '%'; + }).join('')).show().data('rows', top); + }); +}; +$(document).on('click', '.hsn-chip', function () { + var rows = $('#hsn_suggest_row').data('rows') || []; + var r = rows[$(this).data('i')]; + if (r) { PosnicPro.items._hsnApply(r); } +}); +$(document).on('change', "input[name='hsntax_radio_value']", function () { + setTimeout(PosnicPro.items.hsnSuggest, 50); +}); $('.hsnCode').one({ click: function () { - var params = { - url: 'items/getJSONhsncode' - }; - PosnicPro.get(params, function (data) { - + PosnicPro.items._hsnLoad(function (rows) { $('.hsnCode').autocomplete({ - lookup: data.data['hsn'], + lookup: rows, autoSelectFirst: false, minChars: 1, - lookupLimit: 5, + deferRequestBy: 80, + lookupLimit: 8, onSelect: function (suggestion) { - $('#items_hsndescription').val(suggestion.description); - $('#hsn_tax').val(suggestion.taxrate); + PosnicPro.items._hsnApply(suggestion); }, lookupFilter: function (suggestion, query, queryLowerCase) { - var id = suggestion.description, - value = suggestion.value.toLowerCase(); - return id.indexOf(query) === 0 || value.indexOf(queryLowerCase) === 0; + return suggestion.value.indexOf(queryLowerCase) === 0 + || suggestion._d.indexOf(queryLowerCase) !== -1; }, - formatResult: function (suggestion) { - var description = suggestion.description; - return '
' + - $.Autocomplete.formatResult(suggestion) + - '
' + description + 'Tax :' + suggestion.taxrate + ''; + formatResult: function (suggestion, currentValue) { + var rate = String(suggestion.taxrate || '').replace('%', '') || '0'; + return '
' + + '
' + + $.Autocomplete.formatResult(suggestion, currentValue) + + '
' + $('').text(suggestion.description || '').html() + + '
GST ' + rate + '%
'; } }); - }, function (xhr) { - var response = jQuery.parseJSON(xhr.responseText); - PosnicPro.alert(response.type, response.message); }); } }); From ff44eb46cfb2c40b5525dbe69092b72ba4f63d21 Mon Sep 17 00:00:00 2001 From: Sridhar Bala Date: Wed, 19 Aug 2026 12:14:03 +0530 Subject: [PATCH 002/673] Live fix: register off means no register ceremony at tender - ever With the Cash Register feature disabled, the sale payload still carried a cash_register_id left in localStorage from before the switch, so the server ran the session-ownership check and refused the tender with register is already open on another device. All three sale payload sites now go through saleRegisterId(), which returns empty whenever cash_register_enable is off - the feature toggle is the single truth. Root suite green, bundle verified. --- frontend/static/script/js/modules/js/sales.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/frontend/static/script/js/modules/js/sales.js b/frontend/static/script/js/modules/js/sales.js index 41f29d12f..bfc91784c 100644 --- a/frontend/static/script/js/modules/js/sales.js +++ b/frontend/static/script/js/modules/js/sales.js @@ -3575,7 +3575,7 @@ PosnicPro.sales.addSale = { } else { payment_mode_val = $('.payment_mode').val(); } - let register_id = PosnicPro.local.get('cash_register_id'); + let register_id = PosnicPro.sales.saleRegisterId(); var isKotNewSale = (PosnicPro.sales.saleProcess === 'KOT'); var newSaleTableNumber = ''; var newSalePersonCount = ''; @@ -4118,7 +4118,7 @@ PosnicPro.sales.editSale = { sale_process = 'Add'; } - var register_id = PosnicPro.local.get('cash_register_id'); + var register_id = PosnicPro.sales.saleRegisterId(); let payment_mode_val; let payments = PosnicPro.sales.getPaymentObject(); @@ -4510,7 +4510,7 @@ PosnicPro.sales.holdSale = { console.log('multi_payment:', payments); var SalesDocumentId = PosnicPro.sales.editSaleId; - let register_id = PosnicPro.local.get('cash_register_id'); + let register_id = PosnicPro.sales.saleRegisterId(); var data = { items: PosnicPro.sales.addSalesLineTable, sales_total: $("#grand_total").val(), @@ -7456,6 +7456,16 @@ PosnicPro.sales.defaultDenominations = function () { }; return (SETS[sign] || [1, 2, 5, 10, 20, 50, 100, 200, 500]).slice(); }; +/* A sale carries a register session only while the Cash Register feature + is ON - with it off, a register id left in localStorage from before the + switch must not lock anyone out of tendering. */ +PosnicPro.sales.saleRegisterId = function () { + try { + var gs = JSON.parse(PosnicPro.local.get('general_settings') || '{}'); + if (gs.cash_register_enable === false) { return ''; } + } catch (e) { /* feature defaults on */ } + return PosnicPro.local.get('cash_register_id') || ''; +}; PosnicPro.sales.applyQuickSaleGate = function () { var on = true; try { From 732b073cb77161afaa44d56edb60d930f4d8f2cb Mon Sep 17 00:00:00 2001 From: Sridhar Bala Date: Wed, 19 Aug 2026 12:16:00 +0530 Subject: [PATCH 003/673] Owner ask: reports leave the screen as professional documents - A4 PDF, CSV, Excel - and the day-close slip is a first-class thermal option New PosnicPro.reportExport in core: gathers the on-screen report tables (headings + rows in DOM order, so every export agrees with the page) and renders a real vector A4 PDF - shop header band, dated title, section tables with dark header rows, zebra striping, right-aligned numbers, wrapped cells, repeated headers across page breaks, page X of Y footers - on a fixed white palette fully independent of the app theme. Currency signs the PDF fonts cannot draw map to Rs/EUR/GBP. CSV (BOM, sectioned) and Excel (.xls) exports walk the same tables. The old html2canvas screenshot paths are retired from Print and Download on A4/web; the framework is generic so every other report can adopt it by id + meta. Day-end also gains a DEDICATED day-close slip button (owner: many shops have only thermal printers): desktop prints the existing cash-up slip through the thermal rail; the web renders the same slip - sales, payment split incl. UPI, cash handover block with counted/difference/ signature lines, top items - as a narrow print page for driver- connected thermal printers. One slip builder feeds both rails. Root suite green, bundle verified. --- frontend/modules/dailyReport.html | 23 +- frontend/static/script/js/core/PosnicPro.js | 205 ++++++++++++++++++ .../script/js/modules/js/report_dailysales.js | 110 +++++++++- 3 files changed, 326 insertions(+), 12 deletions(-) diff --git a/frontend/modules/dailyReport.html b/frontend/modules/dailyReport.html index ab5f2e0e7..d2e42b055 100644 --- a/frontend/modules/dailyReport.html +++ b/frontend/modules/dailyReport.html @@ -107,10 +107,31 @@

Day-End Summary

+ + + diff --git a/frontend/modules/quotes.html b/frontend/modules/quotes.html index 40f6805fe..9e48f64e0 100644 --- a/frontend/modules/quotes.html +++ b/frontend/modules/quotes.html @@ -129,7 +129,7 @@
New quotation
Authorised signature
- +
T
-

+

close diff --git a/frontend/modules/pendingReport.html b/frontend/modules/pendingReport.html index f1e495c2a..654e817d0 100644 --- a/frontend/modules/pendingReport.html +++ b/frontend/modules/pendingReport.html @@ -611,7 +611,7 @@

#
-

#

+

#

close diff --git a/frontend/modules/receivings_write.html b/frontend/modules/receivings_write.html index bb68cd79e..af8b63f96 100644 --- a/frontend/modules/receivings_write.html +++ b/frontend/modules/receivings_write.html @@ -102,9 +102,9 @@