WFDASH-WEBAPP/public/pos/letterhead-config.js

150 lines
4.7 KiB
JavaScript

// Letterhead configuration for the till.
//
// The POS uses a `letterhead_settings` row from the main WFDASH
// PocketBase (shared with the PDF flow). This module:
// 1. Caches the *list* of available letterheads in localStorage
// so the settings page can show the picker offline.
// 2. Caches the *active* letterhead (the one the till is currently
// running under) in localStorage so the till can render receipts
// and compute VAT without re-fetching on every sale.
// 3. Exposes a tiny global API that other POS scripts (pos-app.js,
// receipt.html, etc.) can use without each one re-implementing
// the cache + fetch dance.
//
// Like pos-config.js, the cache key prefix is `pos.letterhead.*`
// so it's clear what's till data vs. shop data when debugging.
window.PosLetterhead = (function () {
'use strict';
// Westcott Group is the canonical POS letterhead per the operator
// (per the 2026-09-04 audit). If the till has no active letterhead
// yet, we default-pick this id when the first letterhead list comes
// back. The id matches the PB row "5wezvypn6fy47p9" in the user's
// instance — see docs.
const DEFAULT_ID = '5wezvypn6fy47p9';
const DEFAULT_DISPLAY_NAME = 'WESTSCOTT GROUP OF COMPANIES';
const KEY_ACTIVE_ID = 'pos.letterhead.activeId';
const KEY_ACTIVE = 'pos.letterhead.active';
const KEY_LIST = 'pos.letterhead.list';
function lsGet(key) {
try { return JSON.parse(localStorage.getItem(key) || 'null'); } catch (e) { return null; }
}
function lsSet(key, val) {
try { localStorage.setItem(key, JSON.stringify(val)); } catch (e) { /* quota or disabled */ }
}
function lsDel(key) {
try { localStorage.removeItem(key); } catch (e) { /* ignore */ }
}
// -------- list (used by the settings picker) --------
function getCachedList() {
return lsGet(KEY_LIST) || [];
}
function setCachedList(items) {
lsSet(KEY_LIST, items);
}
// -------- active letterhead (used by sales + receipt) --------
function getActiveId() {
return lsGet(KEY_ACTIVE_ID) || null;
}
function getActive() {
return lsGet(KEY_ACTIVE) || null;
}
function setActive(item) {
if (!item || !item.id) return;
lsSet(KEY_ACTIVE_ID, item.id);
lsSet(KEY_ACTIVE, item);
}
function clearActive() {
lsDel(KEY_ACTIVE_ID);
lsDel(KEY_ACTIVE);
}
// -------- network calls --------
async function fetchList() {
const r = await window.posFetch('/letterhead');
if (!r.ok) throw new Error('letterhead list returned ' + r.status);
const j = await r.json();
const items = j.items || [];
setCachedList(items);
return items;
}
async function fetchById(id) {
if (!id) throw new Error('letterhead id is required');
const r = await window.posFetch('/letterhead/' + encodeURIComponent(id));
if (!r.ok) {
if (r.status === 404) throw new Error('letterhead ' + id + ' not found');
throw new Error('letterhead fetch returned ' + r.status);
}
const j = await r.json();
const item = j.item;
if (!item) throw new Error('letterhead response missing item');
setActive(item);
return item;
}
/**
* One-shot boot helper. Called from pos-app.js and settings.html.
* - If we have a cached active letterhead, return it immediately.
* - Otherwise, list letterheads; if Westcott Group is in the list
* auto-pick it; else pick the first active one.
* - Fetch the full row in the background so the cache stays warm
* (returns the fresh row if it differs).
*/
async function ensureActive() {
const cached = getActive();
if (cached && cached.id) {
// Refresh in the background — the till can keep using the cached
// copy while the network call is in flight.
fetchById(cached.id).catch(() => { /* keep cached on failure */ });
return cached;
}
// No active yet — list and pick a default.
let list = getCachedList();
if (!list.length) {
list = await fetchList();
}
const westcott = list.find(x =>
(x.display_name || '').toUpperCase().includes('WESTSCOTT') ||
(x.company || '').toUpperCase().includes('WESTSCOTT')
);
const pick = westcott || list[0];
if (!pick) {
// No active letterheads in PB. Mark this so the UI can show
// a clear "you need to set up a letterhead" message instead of
// a generic error.
clearActive();
return null;
}
return await fetchById(pick.id);
}
return {
// getters
getActiveId,
getActive,
getCachedList,
// setters
setActive,
setCachedList,
clearActive,
// network
fetchList,
fetchById,
ensureActive,
// constants (for debugging / display in settings)
DEFAULT_ID,
DEFAULT_DISPLAY_NAME,
};
})();