From 38c67141848154724f40139015bf2c1c52ca0d1c Mon Sep 17 00:00:00 2001 From: type-two Date: Mon, 22 Jun 2026 22:55:31 +1000 Subject: [PATCH] feat(print): label print (artist/title/price/SKU/QR, Dymo 54x25mm) + optional RFID chip program (Chafon H102 via local :7790 daemon) Co-Authored-By: Claude Opus 4.8 --- site/admin.html | 60 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/site/admin.html b/site/admin.html index 2359f1a..03edd57 100644 --- a/site/admin.html +++ b/site/admin.html @@ -204,6 +204,7 @@ function invEdit(sku){
In stock
${fld('Notes','eNotes',r.notes)}
+
`); } @@ -240,6 +241,65 @@ async function invDoAdd(){ await fetch('/admin/inventory/add',{method:'POST',headers:hdr(),body:JSON.stringify(body)}); invModalClose(); loadInv(); } + +// ---------- Label print + RFID (drives the local rfid-daemon :7790, Chafon H102) ---------- +const RFID_DAEMON = 'http://127.0.0.1:7790'; // localhost is a secure context β€” HTTPS page may call it +function invLabel(sku){ + const r = invItems[sku] || { sku }; + const qr = 'https://api.qrserver.com/v1/create-qr-code/?size=120x120&margin=0&data=' + encodeURIComponent(r.sku); + invModalBox(`

🏷 Label β€” ${escapeH(r.title||sku)}

+
+
+
${escapeH(r.artist||'')}
+
${escapeH(r.title||'')}
+
${money(r.price)}
+
${escapeH(r.sku)}${r.condition?' Β· '+escapeH(r.condition):''}
+
+ +
+ +
checking tag reader…
+
+
+
`); + daemonPing(); +} +async function daemonPing(){ + const el = $('#lblDaemon'); if(!el) return; + try { const r = await fetch(RFID_DAEMON+'/status', {signal:AbortSignal.timeout(2500)}).then(x=>x.json()); + el.innerHTML = '● tag reader connected'; } + catch(e){ el.innerHTML = '● rfid-daemon not running on :7790 β€” RFID disabled (label still prints)'; } +} +function invDoPrint(r){ + const qr = 'https://api.qrserver.com/v1/create-qr-code/?size=200x200&margin=0&data=' + encodeURIComponent(r.sku); + const html = ` +
${escapeH(r.artist||'')}
${escapeH(r.title||'')}
+
${money(r.price)}
${escapeH(r.sku)}
+
`; + const w = window.open('', '_blank', 'width=420,height=240'); + if(w){ w.document.open(); w.document.write(html); w.document.close(); } + if($('#lblRfid') && $('#lblRfid').checked) rfidWrite(r.sku, r.release_id); +} +async function rfidWrite(sku, releaseId){ + const msg = $('#lblMsg'); if(msg) msg.innerHTML = '⚑ writing tag (present the chip to the reader)…'; + try { + await fetch(RFID_DAEMON+'/clear-mask', {method:'POST', signal:AbortSignal.timeout(3000)}).catch(()=>{}); + const body = { sku }; if(releaseId) body.releaseId = String(releaseId); + const d = await fetch(RFID_DAEMON+'/write-tag', {method:'POST', headers:{'Content-Type':'application/json'}, + body:JSON.stringify(body), signal:AbortSignal.timeout(120000)}).then(x=>x.json()); + if(msg) msg.innerHTML = d.ok ? `βœ“ RFID tag programmed (${escapeH(sku)})` + : `RFID write failed: ${escapeH(d.error||'?')}`; + } catch(e){ if(msg) msg.innerHTML = 'RFID daemon unreachable (:7790)'; } +} function invImport(){ invModalBox(`

Import list (bulk stock-in)

One per line β€” Release ID / barcode / catalogue no. Resolved against the catalog, each becomes an in-stock item.