feat(pos): receipt logo + Monster Robot template (store name/address/footer)

receipts.py renders store_logo (img) above the title; store_logo added to sales
settings (get/save) + a Logo URL field in POS Settings. Logo hosted at
site/receipt-logo.webp (Monster Robot, from WowPlatter receipt_templates). Print
window now waits for window.onload so the logo prints. Deploy via /opt/recordgod/deploy.sh.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
type-two 2026-06-23 17:33:08 +10:00
parent 5351cfb9c2
commit bf08cd9b35
4 changed files with 12 additions and 7 deletions

View File

@ -18,6 +18,9 @@ def render_receipt_html(sale, items, customer, st):
store = st.get("store_name") or "RecordGod"
addr = st.get("store_address") or ""
footer = st.get("receipt_footer") or "thanks for digging"
logo = st.get("store_logo") or ""
logo_html = (f'<div class="ct" style="margin-bottom:4px"><img src="{_e(logo)}" alt="" '
f'style="max-height:72px;max-width:200px;object-fit:contain"></div>') if logo else ""
date = str(sale.get("sale_date") or "")[:16].replace("T", " ")
who = (customer or {}).get("name") or "Guest"
@ -71,7 +74,7 @@ def render_receipt_html(sale, items, customer, st):
return f"""<div class="receipt-container" style="font-family:ui-monospace,Menlo,Consolas,monospace;font-size:12px;line-height:1.6;color:#111;max-width:300px;margin:0 auto;padding:6px">
<style>.receipt-container td{{padding:1px 0}} .receipt-container table{{width:100%;border-collapse:collapse}} .receipt-container .hr{{border-top:1px dashed #999;margin:7px 0}} .receipt-container .ct{{text-align:center}} @media print{{.receipt-actions{{display:none}}}}</style>
<div class="ct" style="font-weight:700;font-size:15px">{_e(store)}</div>
{logo_html}<div class="ct" style="font-weight:700;font-size:15px">{_e(store)}</div>
{f'<div class="ct" style="color:#555">{_e(addr)}</div>' if addr else ''}
<div class="ct" style="color:#555">{_e(sale.get('sale_number',''))} · {_e(date)} · {_e(who)}</div>
<div class="hr"></div>

View File

@ -292,7 +292,8 @@ async def _settings(db):
"tax_type": s.get("tax_type", "exclusive"),
"store_name": s.get("store_name", "RecordGod"),
"store_address": s.get("store_address", ""),
"receipt_footer": s.get("receipt_footer", "thanks for digging")}
"receipt_footer": s.get("receipt_footer", "thanks for digging"),
"store_logo": s.get("store_logo", "")}
@router.get("/settings")
@ -305,7 +306,7 @@ async def save_settings(body: dict, ident=Depends(require_token), db=Depends(get
m = {"currency_symbol": body.get("currency_symbol"), "default_tax_rate": body.get("tax_rate"),
"default_discount_rate": body.get("discount_rate"), "tax_type": body.get("tax_type"),
"store_name": body.get("store_name"), "store_address": body.get("store_address"),
"receipt_footer": body.get("receipt_footer")}
"receipt_footer": body.get("receipt_footer"), "store_logo": body.get("store_logo")}
for k, v in m.items():
if v is None:
continue

View File

@ -167,6 +167,7 @@
<div class="row" style="margin-bottom:8px"><span class="lbl">Store name</span><input id="setName" style="flex:1"></div>
<div class="row" style="margin-bottom:8px"><span class="lbl">Address</span><input id="setAddr" style="flex:1" placeholder="prints under the store name"></div>
<div class="row" style="margin-bottom:8px"><span class="lbl">Receipt footer</span><input id="setFooter" style="flex:1" placeholder="thanks for digging"></div>
<div class="row" style="margin-bottom:8px"><span class="lbl">Logo URL</span><input id="setLogo" style="flex:1" placeholder="https://recordgod.com/receipt-logo.webp"></div>
<div class="row" style="margin-bottom:8px"><span class="lbl">Currency</span><input id="setCur" style="width:60px">
<span class="lbl" style="min-width:40px">Tax %</span><input id="setTax" class="num" type="number" step="0.1">
<select id="setTaxType"><option value="exclusive">added on top</option><option value="inclusive">included (GST incl)</option></select></div>
@ -353,8 +354,8 @@ async function showReceipt(id){
}
function printReceipt(){
const w=window.open('','_blank','width=380,height=680'); if(!w){ alert('allow pop-ups to print the receipt'); return; }
w.document.write('<!doctype html><html><head><meta charset=utf-8><title>Receipt</title><style>body{margin:16px;background:#fff}</style></head><body>'+rcptHtml+'</body></html>');
w.document.close(); w.focus(); setTimeout(()=>{ w.print(); }, 250);
w.document.write('<!doctype html><html><head><meta charset=utf-8><title>Receipt</title><style>body{margin:16px;background:#fff}</style></head><body>'+rcptHtml+'<scr'+'ipt>window.onload=function(){setTimeout(function(){window.print()},350)}</scr'+'ipt></body></html>');
w.document.close(); w.focus();
}
function newSale(){ $('#receiptPane').style.display='none'; $('#checkout').style.display='block'; resetCust(); $('#msg').textContent=''; setMethod('cash'); $('#q').focus(); }
function emailDlg(){
@ -433,14 +434,14 @@ async function runBulk(){
/* ── settings ── */
async function loadSettings(){
const s=await get('/sales/settings'); CUR=s.currency_symbol||'$';
$('#setName').value=s.store_name||''; $('#setAddr').value=s.store_address||''; $('#setFooter').value=s.receipt_footer||'';
$('#setName').value=s.store_name||''; $('#setAddr').value=s.store_address||''; $('#setFooter').value=s.receipt_footer||''; $('#setLogo').value=s.store_logo||'';
$('#setCur').value=CUR; $('#setTax').value=s.tax_rate; $('#setDisc').value=s.discount_rate; $('#setTaxType').value=s.tax_type||'exclusive';
if(!(+$('#tax').value)) $('#tax').value=s.tax_rate||0;
render();
}
async function saveSettings(){
await post('/sales/settings',{currency_symbol:$('#setCur').value,tax_rate:$('#setTax').value,discount_rate:$('#setDisc').value,
tax_type:$('#setTaxType').value,store_name:$('#setName').value,store_address:$('#setAddr').value,receipt_footer:$('#setFooter').value});
tax_type:$('#setTaxType').value,store_name:$('#setName').value,store_address:$('#setAddr').value,receipt_footer:$('#setFooter').value,store_logo:$('#setLogo').value});
CUR=$('#setCur').value||'$'; $('#setMsg').textContent='saved'; setTimeout(()=>$('#setMsg').textContent='',1500); render();
}
async function emailTest(){

BIN
site/receipt-logo.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB