From 09df5efb07bf10f2f31f9efc92d9b60934beb8bf Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 30 May 2026 16:55:23 +0000 Subject: [PATCH] Redesign receipts/statement to match spec; add logo upload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Receipts: - Font size raised to 11pt base (labels 9pt, amounts 13pt bold) - Each field now shows LABEL (small, uppercase, gray) above VALUE — two cells per row in a 1fr/1fr CSS grid, matching the provided samples - Business header: left column = address lines, right column = Tel/Email/Web - Charge receipt: STAFF+TRANSACTION / CHARGE+TIME / AMOUNT+BALANCE - Top-up/Withdrawal receipt: STAFF+TXN / TRANSFER_TYPE+TIME / AMOUNT+BALANCE / TRANSFER_TYPE+TRANSFER_REF - Print button moved into the paper-size controls bar Statement: - Reduced from 9 to 7 columns: Date, Reference, Type, Venue, Staff, Amount (+/-), Balance — removes the separate Charge/Credit split - Amount shown as "+ £X.XX" (green) or "- £X.XX" (red) - Sub-row shows "Transfer type: X — Ref" for top-ups/withdrawals, or the note text for charges Logo: - New POST /admin/logo endpoint: accepts image upload, saves to static/logo.{ext}, auto-updates logo_url setting - New logo_max_width / logo_max_height config fields (default 200×80px) - Admin branding section: file upload input + max-width/height fields - python-multipart added to requirements.txt (needed for file upload) https://claude.ai/code/session_01JuRTR5Xjx8emQsyerBgGU7 --- main.py | 337 ++++++++++++++++++++++++++++------------------ requirements.txt | 1 + static/app.js | 35 ++++- static/index.html | 16 ++- 4 files changed, 251 insertions(+), 138 deletions(-) diff --git a/main.py b/main.py index bf7e710..6fce780 100644 --- a/main.py +++ b/main.py @@ -13,7 +13,7 @@ from pathlib import Path from typing import Optional import bcrypt -from fastapi import FastAPI, HTTPException, Cookie, Depends, Response +from fastapi import FastAPI, HTTPException, Cookie, Depends, Response, UploadFile, File from fastapi.responses import HTMLResponse from fastapi.staticfiles import StaticFiles from pydantic import BaseModel, field_validator @@ -43,6 +43,8 @@ CONFIG = { # Branding "logo_url": "", "logo_align": "left", + "logo_max_width": 200, + "logo_max_height": 80, "bar_name": "Bar", "cashier_name": "Cashier", # Transactions @@ -423,6 +425,8 @@ class AppSettingsUpdate(BaseModel): # Branding logo_url: Optional[str] = None logo_align: Optional[str] = None + logo_max_width: Optional[int] = None + logo_max_height: Optional[int] = None bar_name: Optional[str] = None cashier_name: Optional[str] = None # Transactions @@ -690,7 +694,7 @@ def _print_size_script(): function setSize(s){ var el=document.getElementById('psStyle'); if(!el){el=document.createElement('style');el.id='psStyle';document.head.appendChild(el);} - el.textContent='@media print{@page{size:'+s+';margin:'+(s==='A5'?'8mm':'14mm')+';}}';} + el.textContent='@media print{@page{size:'+s+';margin:'+(s==='A5'?'10mm':'16mm')+';}}';} setSize('A4'); """ @@ -699,6 +703,7 @@ def _print_controls(): Paper: + """ def _txn_ref(entry_id: int, s: dict) -> str: @@ -709,57 +714,86 @@ def _logo_html(s: dict) -> str: url = (s.get("logo_url") or "").strip() if not url: return "" - align = s.get("logo_align", "left") - css_cls = f"biz-logo align-{align}" if align in ("left", "center", "right") else "biz-logo align-left" - return f'logo' + align = s.get("logo_align", "left") + max_w = int(s.get("logo_max_width", 200) or 200) + max_h = int(s.get("logo_max_height", 80) or 80) + style = f"max-width:{max_w}px;max-height:{max_h}px;" + css_cl = f"biz-logo align-{align}" if align in ("left","center","right") else "biz-logo" + return f'logo' def _biz_header_html(s: dict) -> str: - parts = [_logo_html(s)] - parts.append(f'
{s.get("club_name") or "ClubLedger"}
') - addr = [s.get(f"biz_address{i}", "") for i in range(1, 5)] + [s.get("biz_country", "")] - addr = [l.strip() for l in addr if l and l.strip()] - if addr: - parts.append('
' + "
".join(addr) + "
") - contact = [x for x in [s.get("biz_phone",""), s.get("biz_email",""), s.get("biz_website","")] if x and x.strip()] - if contact: - parts.append('
' + "  |  ".join(contact) + "
") - return '
' + "\n".join(p for p in parts if p) + "
" + logo = _logo_html(s) + name = s.get("club_name") or "ClubLedger" + + addr = [( s.get(f"biz_address{i}") or "").strip() for i in range(1,5)] + addr += [(s.get("biz_country") or "").strip()] + addr = [l for l in addr if l] + + contacts = [] + if (s.get("biz_phone") or "").strip(): contacts.append(f'Tel.   {s["biz_phone"]}') + if (s.get("biz_email") or "").strip(): contacts.append(f'Email: {s["biz_email"]}') + if (s.get("biz_website") or "").strip(): contacts.append(f'Web:   {s["biz_website"]}') + + parts = [] + if logo: parts.append(logo) + parts.append(f'
{name}
') + + if addr and contacts: + parts.append( + f'
' + f'
{"
".join(addr)}
' + f'
{"
".join(contacts)}
' + f'
' + ) + elif addr: + parts.append(f'
{"
".join(addr)}
') + elif contacts: + parts.append(f'
{"
".join(contacts)}
') + + return '
' + "\n".join(parts) + "
" + +def _rx_cell(label: str, value: str, extra_cls: str = "") -> str: + val_cls = ("rx-val " + extra_cls).strip() + return f'
{label}
{value}
' RECEIPT_CSS = """ - body{font-family:Arial,sans-serif;font-size:11px;color:#111;margin:24px;} - h2{font-size:14px;font-weight:bold;margin:10px 0 4px;} - hr{border:none;border-top:1px solid #ccc;margin:10px 0;} - .controls{display:flex;align-items:center;gap:12px;margin-bottom:14px;flex-wrap:wrap;} - .size-label{font-size:12px;color:#555;} .controls label{font-size:12px;cursor:pointer;} - .print-btn{padding:7px 18px;font-size:13px;cursor:pointer;margin-left:auto;} + body{font-family:Arial,sans-serif;font-size:11pt;color:#111;margin:28px;} + hr{border:none;border-top:1px solid #ccc;margin:12px 0;} + .controls{display:flex;align-items:center;gap:12px;margin-bottom:16px;flex-wrap:wrap;} + .size-label{font-size:10pt;color:#555;} + .controls label{font-size:10pt;cursor:pointer;} + .print-btn{padding:6px 16px;font-size:10pt;cursor:pointer;margin-left:auto;} @media print{.no-print{display:none;}} - .biz-header{margin-bottom:4px;} - .biz-logo{max-height:60px;max-width:200px;display:block;margin-bottom:4px;} + /* Business header */ + .biz-logo{display:block;margin-bottom:8px;} .biz-logo.align-center{margin-left:auto;margin-right:auto;} .biz-logo.align-right{margin-left:auto;} - .biz-name{font-size:15px;font-weight:bold;margin:2px 0;} - .biz-address{color:#555;line-height:1.5;} - .biz-contact{color:#555;margin-top:3px;} - .receipt-title{font-size:13px;font-weight:bold;text-transform:uppercase;letter-spacing:.04em;margin:10px 0 6px;} - .member-section{display:flex;justify-content:space-between;align-items:baseline;margin:5px 0;} - .member-name{font-size:13px;font-weight:bold;} - .member-num{color:#555;} - .receipt-grid{display:grid;grid-template-columns:auto 1fr;gap:3px 14px;margin:6px 0;} - .rlbl{font-weight:600;color:#555;white-space:nowrap;font-size:10px;text-transform:uppercase;letter-spacing:.03em;} - .rval{text-align:right;} - .section-head{grid-column:span 2;font-weight:bold;font-size:11px;text-transform:uppercase;letter-spacing:.04em;margin:6px 0 2px;} - .charge-val{font-size:20px;font-weight:bold;color:#c00;} - .credit-val{font-size:20px;font-weight:bold;color:#080;} - .balance-val{font-size:14px;font-weight:bold;} - table{width:100%;border-collapse:collapse;margin-top:10px;} - th{background:#222;color:#fff;padding:5px 8px;text-align:left;font-size:10px;} - td{padding:4px 8px;border-bottom:1px solid #e0e0e0;} - .num{text-align:right;font-variant-numeric:tabular-nums;} - .red{color:#c00;} .grn{color:#080;} - .balance-box{margin-top:12px;text-align:right;font-size:14px;} - .balance-box span{font-weight:bold;font-size:18px;} - .sub-row td{font-size:10px;color:#777;padding-top:0;border-bottom:none;padding-left:24px;} - .footer{margin-top:14px;font-size:10px;color:#888;text-align:center;white-space:pre-wrap;} + .biz-name{font-size:14pt;font-weight:bold;margin:4px 0 6px;} + .biz-info-row{display:flex;justify-content:space-between;align-items:flex-start;gap:24px;font-size:10pt;line-height:1.7;} + .biz-addr{line-height:1.7;} + .biz-contacts{text-align:right;white-space:nowrap;line-height:1.7;} + /* Receipt */ + .rx-title{font-size:13pt;font-weight:bold;text-transform:uppercase;letter-spacing:.06em;margin:14px 0 12px;} + .rx-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px 40px;margin:10px 0;} + .rx-cell{} + .rx-lbl{font-size:9pt;font-weight:700;color:#555;text-transform:uppercase;letter-spacing:.05em;margin-bottom:3px;} + .rx-val{font-size:11pt;} + .rx-val.bold{font-weight:bold;} + .rx-val.large{font-size:13pt;font-weight:bold;} + .rx-val.charge{color:#c00;} + .rx-val.credit{color:#080;} + .footer{margin-top:20px;font-size:10pt;color:#444;line-height:1.7;white-space:pre-wrap;} + /* Statement */ + h2{font-size:13pt;font-weight:bold;margin:14px 0 4px;} + .stmt-info{font-size:10pt;color:#555;margin-bottom:12px;line-height:1.6;} + table{width:100%;border-collapse:collapse;margin-top:4px;font-size:10pt;} + th{border-bottom:2px solid #222;padding:6px 8px 6px 0;text-align:left;font-size:9pt;font-weight:700;white-space:nowrap;} + td{padding:5px 8px 5px 0;border-bottom:1px solid #e0e0e0;vertical-align:top;} + th.rnum,td.rnum{text-align:right;padding-right:0;} + .credit{color:#080;} + .debit{color:#c00;} + .sub-row td{font-size:10pt;color:#555;padding-top:0;border-bottom:none;padding-left:88px;} + .balance-box{margin-top:14px;text-align:right;font-size:11pt;font-weight:bold;} """ @app.get("/members/{member_id}/statement", response_class=HTMLResponse) @@ -775,55 +809,67 @@ def statement(member_id: int): bal = member_balance(conn, member_id) sym, div = s.get("currency_symbol","£"), s.get("currency_divisor",100) - footer = s.get("receipt_footer","") - bar_name = s.get("bar_name","Bar") - cashier_name = s.get("cashier_name","Cashier") + footer = s.get("receipt_footer","") + bar_name = s.get("bar_name","Bar") + cashier_name= s.get("cashier_name","Cashier") def fmt(p): return f"{sym}{p/div:.2f}" - def venue_label(v): return bar_name if v == "bar" else cashier_name rows_html, running = "", 0 for r in rows: txn_ref = _txn_ref(r["id"], s) + venue = bar_name if r["venue"] == "bar" else cashier_name if r["type"] == "topup": - running += r["amount"]; dr, cr = "", fmt(r["amount"]); type_lbl = "Top-up" + running += r["amount"] + amt_html = f'+ {fmt(r["amount"])}' + type_lbl = "Top-up" elif r["type"] == "withdrawal": - running -= r["amount"]; dr, cr = fmt(r["amount"]), ""; type_lbl = "Withdrawal" + running -= r["amount"] + amt_html = f'- {fmt(r["amount"])}' + type_lbl = "Withdrawal" else: - running -= r["amount"]; dr, cr = fmt(r["amount"]), ""; type_lbl = "Charge" - rows_html += (f"{r['created_at'][:16]}{type_lbl}" - f"{venue_label(r['venue'])}{txn_ref}" - f"{r['note'] or ''}{r['staff_name']}" - f"{dr}{cr}" - f"{fmt(running)}") - if r["type"] in ("topup", "withdrawal"): + running -= r["amount"] + amt_html = f'- {fmt(r["amount"])}' + type_lbl = "Charge" + + rows_html += ( + f"{r['created_at'][:16]}{txn_ref}" + f"{type_lbl}{venue}{r['staff_name']}" + f"{amt_html}{fmt(running)}" + ) + + # Detail sub-row + sub = "" + if r["type"] in ("topup","withdrawal"): tf_type = r["transfer_type"] or "" tf_ref = r["transfer_ref"] or "" - if tf_type or tf_ref: - sub = "  ·  ".join(filter(None, [ - f"Type: {tf_type}" if tf_type else "", - f"Ref: {tf_ref}" if tf_ref else "", - ])) - rows_html += f'{sub}' + if tf_type and tf_ref: + sub = f"Transfer type: {tf_type} — {tf_ref}" + elif tf_type: + sub = f"Transfer type: {tf_type}" + elif tf_ref: + sub = f"Ref: {tf_ref}" + elif r["note"]: + sub = r["note"] + + if sub: + rows_html += f'{sub}' return f""" -Statement – {member['name']} +Statement — {member['name']} {_print_controls()} -
- -
{_biz_header_html(s)}

Account Statement

-
- Member: {member['name']}  |  #{member['member_number']}  |  +
+ Member: {member['name']} — #{member['member_number']} — Generated: {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M')} UTC
- - + + {rows_html}
Date/TimeTypeVenueReferenceNoteStaffChargeCreditBalanceDate and TimeReferenceTypeVenueStaffAmountBalance
-
Current Balance: {fmt(bal)}
+
Current Balance: {fmt(bal)}
{('') if footer else ''} {_print_size_script()}""" @@ -839,80 +885,86 @@ def receipt(entry_id: int): FROM ledger_entries WHERE member_id=? AND id<=? """, (entry["member_id"], entry_id)).fetchone()[0] - sym, div = s.get("currency_symbol","£"), s.get("currency_divisor",100) + sym, div = s.get("currency_symbol","£"), s.get("currency_divisor",100) def fmt(p): return f"{sym}{p/div:.2f}" - txn_ref = _txn_ref(entry_id, s) - etype = entry["type"] - venue_name = s.get("bar_name","Bar") if entry["venue"] == "bar" else s.get("cashier_name","Cashier") + txn_ref = _txn_ref(entry_id, s) + etype = entry["type"] + venue_name = s.get("bar_name","Bar") if entry["venue"]=="bar" else s.get("cashier_name","Cashier") + tf_type = entry["transfer_type"] or "" + tf_ref = entry["transfer_ref"] or "" + timestamp = entry["created_at"][:16] + " UTC" - lbl_staff = s.get("lbl_staff", "STAFF") - lbl_txn = s.get("lbl_transaction", "TRANSACTION") - lbl_txn_time = s.get("lbl_txn_time", "TRANSACTION TIME") + lbl_staff = s.get("lbl_staff", "STAFF") + lbl_txn = s.get("lbl_transaction", "TRANSACTION") + lbl_txn_time = s.get("lbl_txn_time", "TRANSACTION TIME") lbl_remaining = s.get("lbl_remaining_balance", "REMAINING BALANCE") if etype == "topup": - title = s.get("lbl_topup_receipt", "TOP-UP RECEIPT") - footer = s.get("receipt_footer_cashier") or s.get("receipt_footer", "") + title = s.get("lbl_topup_receipt", "TOP-UP RECEIPT") + footer = s.get("receipt_footer_cashier") or s.get("receipt_footer","") + lbl_tf_sec = s.get("lbl_balance_transfer", "BALANCE TRANSFER") + lbl_amount = s.get("lbl_amount_topup", "AMOUNT TOPPED-UP") + tf_label = "Top-up" + amount_cls = "large credit" elif etype == "withdrawal": - title = s.get("lbl_withdrawal_receipt", "WITHDRAWAL RECEIPT") - footer = s.get("receipt_footer_cashier") or s.get("receipt_footer", "") + title = s.get("lbl_withdrawal_receipt","WITHDRAWAL RECEIPT") + footer = s.get("receipt_footer_cashier") or s.get("receipt_footer","") + lbl_tf_sec = s.get("lbl_balance_transfer", "BALANCE TRANSFER") + lbl_amount = s.get("lbl_amount_withdrawal","AMOUNT WITHDRAWN") + tf_label = "Withdrawal" + amount_cls = "large charge" else: - title = s.get("lbl_receipt", "RECEIPT") - footer = s.get("receipt_footer_charge") or s.get("receipt_footer", "") + title = s.get("lbl_receipt", "RECEIPT") + footer = s.get("receipt_footer_charge") or s.get("receipt_footer","") + lbl_charge = s.get("lbl_charge_venue", "CHARGE") + lbl_amount = s.get("lbl_amount_charged", "AMOUNT CHARGED") if etype == "charge": - lbl_charge = s.get("lbl_charge_venue", "CHARGE") - lbl_amount = s.get("lbl_amount_charged", "AMOUNT CHARGED") - grid_details = ( - f'
{lbl_staff}
{entry["staff_name"]}
' - f'
{lbl_txn}
{txn_ref}
' - f'
{lbl_charge}
{venue_name}
' - f'
{lbl_txn_time}
{entry["created_at"][:16]} UTC
' - ) - grid_amounts = ( - f'
{lbl_amount}
{fmt(entry["amount"])}
' - f'
{lbl_remaining}
{fmt(bal_after)}
' - ) + body_html = f"""
+ {_rx_cell(lbl_staff, entry['staff_name'])} + {_rx_cell(lbl_txn, txn_ref)} +
+
+
+ {_rx_cell(lbl_charge, venue_name)} + {_rx_cell(lbl_txn_time, timestamp)} +
+
+
+ {_rx_cell(lbl_amount, fmt(entry['amount']), 'large charge')} + {_rx_cell(lbl_remaining, fmt(bal_after), 'large')} +
""" else: - lbl_tf_section = s.get("lbl_balance_transfer", "BALANCE TRANSFER") - lbl_tf_type = s.get("lbl_transfer_type", "TRANSFER TYPE") - lbl_tf_ref = s.get("lbl_transfer_ref", "TRANSFER REFERENCE") - lbl_amount = s.get("lbl_amount_topup", "AMOUNT TOPPED-UP") if etype == "topup" else s.get("lbl_amount_withdrawal", "AMOUNT WITHDRAWN") - tf_type = entry["transfer_type"] or "" - tf_ref = entry["transfer_ref"] or "" - grid_details = ( - f'
{lbl_staff}
{entry["staff_name"]}
' - f'
{lbl_txn}
{txn_ref}
' - f'
{lbl_txn_time}
{entry["created_at"][:16]} UTC
' - ) - tf_rows = "" - if tf_type: tf_rows += f'
{lbl_tf_type}
{tf_type}
' - if tf_ref: tf_rows += f'
{lbl_tf_ref}
{tf_ref}
' - grid_amounts = ( - f'
{lbl_tf_section}
' - f'{tf_rows}' - f'
{lbl_amount}
{fmt(entry["amount"])}
' - f'
{lbl_remaining}
{fmt(bal_after)}
' - ) + lbl_tf_type = s.get("lbl_transfer_type", "TRANSFER TYPE") + lbl_tf_ref = s.get("lbl_transfer_ref", "TRANSFER REFERENCE") + body_html = f"""
+ {_rx_cell(lbl_staff, entry['staff_name'])} + {_rx_cell(lbl_txn, txn_ref)} +
+
+
+ {_rx_cell(lbl_tf_sec, tf_label)} + {_rx_cell(lbl_txn_time, timestamp)} +
+
+
+ {_rx_cell(lbl_amount, fmt(entry['amount']), amount_cls)} + {_rx_cell(lbl_remaining, fmt(bal_after), 'large')} +
+
+
+ {_rx_cell(lbl_tf_type, tf_type or '—')} + {_rx_cell(lbl_tf_ref, tf_ref or '—')} +
""" return f""" -Receipt – {member['name']} +Receipt — {member['name']} {_print_controls()} -
- -
{_biz_header_html(s)}
-
{title}
-
-
{member['name']}
-
#{member['member_number']}
-
-
-
{grid_details}
-
-
{grid_amounts}
+
{title}
+{body_html}
{('') if footer else ''} {_print_size_script()}""" @@ -1062,6 +1114,27 @@ def update_admin_settings(body: AppSettingsUpdate, user: dict = Depends(admin_us refresh_settings() return _settings +# --------------------------------------------------------------------------- +# Admin – logo upload +# --------------------------------------------------------------------------- + +@app.post("/admin/logo") +async def upload_logo(file: UploadFile = File(...), user: dict = Depends(admin_user)): + content_type = file.content_type or "" + if not content_type.startswith("image/"): + raise HTTPException(400, "Only image files are allowed") + suffix = Path(file.filename or "logo.png").suffix.lower() + if suffix not in (".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg"): + suffix = ".png" + dest = static_dir / f"logo{suffix}" + dest.write_bytes(await file.read()) + url = f"/static/logo{suffix}" + with db_conn() as conn: + conn.execute("INSERT OR REPLACE INTO app_settings (key,value) VALUES (?,?)", + ("logo_url", json.dumps(url))) + refresh_settings() + return {"url": url} + # --------------------------------------------------------------------------- # Config (public – loaded by frontend before login screen shows) # --------------------------------------------------------------------------- diff --git a/requirements.txt b/requirements.txt index d52f40e..d36de0e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ fastapi uvicorn[standard] bcrypt +python-multipart diff --git a/static/app.js b/static/app.js index 4791b48..5f081a3 100644 --- a/static/app.js +++ b/static/app.js @@ -398,6 +398,29 @@ async function doCharge() { // --------------------------------------------------------------------------- async function loadAdminView() { await Promise.all([loadAdminSettings(), loadStaffAccounts()]); + setupLogoUpload(); +} + +let _logoUploadWired = false; +function setupLogoUpload() { + if (_logoUploadWired) return; + const input = document.getElementById('s-logo-upload'); + if (!input) return; + _logoUploadWired = true; + input.addEventListener('change', async function() { + const file = this.files[0]; + if (!file) return; + const fd = new FormData(); + fd.append('file', file); + try { + const r = await fetch('/admin/logo', { method: 'POST', body: fd }); + const json = await r.json(); + if (!r.ok) throw new Error(json.detail || 'Upload failed'); + document.getElementById('s-logo-url').value = json.url; + setMsg('logoUploadMsg', 'Logo uploaded.', 'ok'); + } catch (e) { setMsg('logoUploadMsg', e.message, 'err'); } + this.value = ''; + }); } async function loadAdminSettings() { @@ -428,8 +451,10 @@ async function loadAdminSettings() { document.getElementById('s-biz-email').value = s.biz_email || ''; document.getElementById('s-biz-website').value = s.biz_website || ''; // Branding - document.getElementById('s-logo-url').value = s.logo_url || ''; - document.getElementById('s-logo-align').value = s.logo_align || 'left'; + document.getElementById('s-logo-url').value = s.logo_url || ''; + document.getElementById('s-logo-align').value = s.logo_align || 'left'; + document.getElementById('s-logo-max-width').value = s.logo_max_width || ''; + document.getElementById('s-logo-max-height').value = s.logo_max_height || ''; document.getElementById('s-bar-name').value = s.bar_name || ''; document.getElementById('s-cashier-name').value = s.cashier_name || ''; // Transactions @@ -485,8 +510,10 @@ async function saveSettings() { biz_email: _svt('s-biz-email'), biz_website: _svt('s-biz-website'), // Branding - logo_url: _svt('s-logo-url'), - logo_align: _sv('s-logo-align'), + logo_url: _svt('s-logo-url'), + logo_align: _sv('s-logo-align'), + logo_max_width: parseInt(_sv('s-logo-max-width'), 10) || null, + logo_max_height: parseInt(_sv('s-logo-max-height'), 10) || null, bar_name: _svt('s-bar-name'), cashier_name: _svt('s-cashier-name'), // Transactions diff --git a/static/index.html b/static/index.html index 5247332..e81659c 100644 --- a/static/index.html +++ b/static/index.html @@ -228,15 +228,27 @@

Branding

-
+
+ + +
+
+
-
+
+
+
+
+
+
+
+