diff --git a/public/pos/pos-app.js b/public/pos/pos-app.js index bf9fd20..d091476 100644 --- a/public/pos/pos-app.js +++ b/public/pos/pos-app.js @@ -277,11 +277,16 @@ function render(){ const icon = p.category_icon ? `` : initials(p.category||p.brand||'?'); - return ``; diff --git a/public/pos/pos.html b/public/pos/pos.html index c9f10dc..6bafb40 100644 --- a/public/pos/pos.html +++ b/public/pos/pos.html @@ -96,6 +96,11 @@ input{font:inherit;color:inherit} .prod .sub{font-size:11px;color:var(--muted-fg);margin-top:2px;letter-spacing:.01em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} .prod .price{font-size:14px;font-weight:600;letter-spacing:-.005em;color:var(--fg);font-variant-numeric:tabular-nums;white-space:nowrap} .prod .price small{font-size:11px;color:var(--muted-fg);font-weight:500;margin-left:2px} +/* Out of stock: the quantity is not positive. The cue is informational — + the item still sells (the till oversells on purpose) — so it only + tints the line. The number itself is never hidden or clamped. */ +.prod.oos .sub{color:var(--danger)} +.prod.oos .name{opacity:.72} /* ---- Cart bottom sheet ---- */ .cart-bar{position:fixed;left:0;right:0;bottom:0;background:#fff;border-top:1px solid var(--muted);padding:12px 16px calc(12px + env(safe-area-inset-bottom));z-index:20;display:flex;align-items:center;gap:12px;box-shadow:var(--shadow-up);max-width:480px;margin:0 auto;transform:translateY(100%);transition:transform .22s cubic-bezier(.2,.8,.2,1)} @@ -307,6 +312,13 @@ input{font:inherit;color:inherit} :root[data-theme="dark"] .prod .sub{color:var(--icon-fg)} :root[data-theme="dark"] .prod .price{color:var(--fg)} :root[data-theme="dark"] .prod .price small{color:var(--icon-fg)} +/* The dark theme restates `.prod .sub` at a higher specificity than + `.prod.oos .sub`, so the out-of-stock colour needs a dark rule of + its own or it never shows. `#ff6680` is the dark danger colour + this file already uses (.totals.discount-line, .edit-actions .danger); + --danger itself is not rebound for dark and would not clear AA on the + dark canvas. */ +:root[data-theme="dark"] .prod.oos .sub{color:#ff6680} :root[data-theme="dark"] .cart-bar{background:var(--surface);border-top-color:var(--muted);box-shadow:var(--shadow-up)} :root[data-theme="dark"] .cart-bar .count span{color:var(--icon-fg)} :root[data-theme="dark"] .cart-bar .total{color:var(--fg)} diff --git a/public/pos/sw.js b/public/pos/sw.js index 1927b2b..f41851c 100644 --- a/public/pos/sw.js +++ b/public/pos/sw.js @@ -60,7 +60,13 @@ // plus blanking the figure when the row is hidden. Also carries the dark // palette lift. // v37: pos-app.js accepts a comma as the decimal point (custom item price, cash given). -const CACHE = "pos-v37"; +// v38: the catalogue tile shows the stock quantity. current_qty was already +// coming down the wire — the till just never drew it. The number is shown +// as it is (negatives included) and the tile is tinted when the quantity is +// not positive. Nothing is blocked: the item still sells. +// Bumped because pos-app.js and pos.html are both served cache-first out of +// the shell; without a new CACHE name the tills keep the old ones. +const CACHE = "pos-v38"; const APP_SHELL = [ './', './index.html', diff --git a/src/lib/pos-cart-quantity.test.ts b/src/lib/pos-cart-quantity.test.ts index 7ab06ac..88f5a9f 100644 --- a/src/lib/pos-cart-quantity.test.ts +++ b/src/lib/pos-cart-quantity.test.ts @@ -264,3 +264,25 @@ describe("POS [hidden] attribute actually hides", () => { ); }); }); + +describe("the POS tile shows the stock quantity", () => { + it("renders the quantity on the tile", () => { + expect(POS_APP).toContain("current_qty"); + expect(POS_APP).toContain("in stock"); + }); + + it("marks the tile out of stock when the quantity is not positive", () => { + expect(POS_APP).toMatch(/oos/); + }); + + it("styles the out-of-stock tile in both themes", () => { + // The cue only works if the eye finds it. The dark theme restates + // `.prod .sub` at a higher specificity than `.prod.oos .sub`, so + // without a dark rule of its own the out-of-stock colour is + // overridden and the tile looks like every other one. + expect(POS_HTML).toMatch(/\.prod\.oos \.sub\{[^}]*color:var\(--danger\)/); + expect(POS_HTML).toMatch( + /:root\[data-theme="dark"\] \.prod\.oos \.sub\{[^}]*color:/, + ); + }); +}); diff --git a/src/lib/server/pos-service.test.ts b/src/lib/server/pos-service.test.ts index 193f010..10626f7 100644 --- a/src/lib/server/pos-service.test.ts +++ b/src/lib/server/pos-service.test.ts @@ -711,3 +711,30 @@ describe("POS never sells at R0", () => { expect(pb._salesCalls).toHaveLength(0); }); }); + +describe("listActiveStockCatalog", () => { + it("returns min_qty alongside current_qty", async () => { + const pb = { + collection: () => ({ + getFullList: vi.fn().mockResolvedValue([ + { + id: "si1", + sku: "SKU-1", + name: "Widget", + description: "A widget", + unit: "ea", + category: "", + current_qty: 5, + min_qty: 2, + selling_price: 10, + cost_price: 5, + is_active: true, + }, + ]), + }), + }; + const out = await listActiveStockCatalog(pb as never); + expect(out[0].min_qty).toBe(2); + expect(out[0].current_qty).toBe(5); + }); +}); diff --git a/src/lib/server/pos-service.ts b/src/lib/server/pos-service.ts index ef6e3e0..1ca8c36 100644 --- a/src/lib/server/pos-service.ts +++ b/src/lib/server/pos-service.ts @@ -157,6 +157,7 @@ export async function listActiveStockCatalog(pb: PosPb): Promise