POSTGRESQL REFERENCE · WORKING SOURCE
public/app.js
This file ships in the runnable source archive. Synthetic fixtures and demonstration policies are identified in the README.
Download the complete project ↓const $ = (selector, root = document) => root.querySelector(selector);
const esc = (value) => String(value ?? "").replace(/[&<>"']/g, c => ({"&":"&","<":"<",">":">",'"':""","'":"'"}[c]));
const shortId = value => String(value || "").slice(0, 8).toUpperCase();
const money = cents => new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" }).format(Number(cents || 0) / 100);
const human = value => String(value || "").replaceAll("_", " ").replace(/\b\w/g, c => c.toUpperCase());
const tz = "America/Denver";
const date = (value, options = {}) => value ? new Intl.DateTimeFormat("en-US", { timeZone: tz, month: "short", day: "numeric", hour: "numeric", minute: "2-digit", ...options }).format(new Date(value)) : "—";
const day = value => date(value, { hour: undefined, minute: undefined });
const atLocal = value => {
if (!value) return "";
const parts = Object.fromEntries(new Intl.DateTimeFormat("en-CA", { timeZone: tz, year:"numeric", month:"2-digit", day:"2-digit", hour:"2-digit", minute:"2-digit", hourCycle:"h23" }).formatToParts(new Date(value)).map(p => [p.type,p.value]));
return `${parts.year}-${parts.month}-${parts.day}T${parts.hour}:${parts.minute}`;
};
function localISO(value) {
if (!value) return undefined;
const target = Date.parse(`${value}:00Z`);
if (!Number.isFinite(target)) throw new Error("Enter a valid date and time.");
let guess = target;
for (let n = 0; n < 4; n++) {
const rendered = Date.parse(`${atLocal(new Date(guess).toISOString())}:00Z`);
guess += target - rendered;
}
const result = new Date(guess).toISOString();
if (atLocal(result) !== value) throw new Error("That local time does not exist in America/Denver. Choose another time.");
return result;
}
const icons = {
capacity:'<path d="M3 20h18M4 20V8l8-5 8 5v12M8 20v-9h8v9M8 15h8M12 11v9"/>',
booking:'<path d="M7 4H5v17h14V4h-2M8 3h8v4H8zM8 12h8M8 16h5"/>',
operations:'<path d="M3 7h12v11H3zM15 11h3l3 4v3h-6M6 7V4h7v3"/><circle cx="7" cy="19" r="2"/><circle cx="18" cy="19" r="2"/>',
charges:'<path d="M5 3h14v19l-3-2-4 2-4-2-3 2zM8 8h8M8 12h8M8 16h5"/>',
box:'<path d="m3 7 9-4 9 4v11l-9 4-9-4zM3 7l9 5 9-5M12 12v10M7 5l9 5"/>',
};
const icon = name => `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${icons[name] || icons.box}</svg>`;
const brand = () => '<div class="brand"><span class="brand-mark" aria-hidden="true"><i></i><i></i><i></i><i></i></span><div><strong>svc_warehouse</strong><small>Space, accounted for.</small></div></div>';
const defaults = () => ({ warehouseId:"wh-denver",zoneId:"zone-denver-a",quantity:6,startAt:"2026-10-26T09:00",endAt:"2026-10-29T00:00",heightMm:1500,weightKg:800 });
const S = {
user:null, csrfToken:null, now:null, data:null, tab:"capacity", busy:false, notice:null, retry:null,
booking:defaults(), range:{warehouseId:"wh-denver",zoneId:"zone-denver-a",startAt:"2026-10-26T09:00",endAt:"2026-10-29T00:00"},
capacity:null, quote:null, hold:null, selectedReservation:null, selectedPallets:new Set(), releaseRequestId:null,
invoice:null, audit:null, receivingDrafts:{}, loginEmail:"shipper@cedar.demo", switching:false,
};
const operator = () => S.user?.role === "operator";
const reservations = () => S.data?.reservations || [];
const selected = () => reservations().find(r => r.id === S.selectedReservation);
const allPallets = () => S.data?.pallets || [];
const inventory = () => allPallets().filter(p => p.reservationId === S.selectedReservation);
const activePallets = () => inventory().filter(p => !p.releasedAt);
const warehouse = id => S.data?.warehouses?.find(w => w.id === id);
const zone = (warehouseId, zoneId) => warehouse(warehouseId)?.zones?.find(z => z.id === zoneId);
const expired = value => value && S.now ? new Date(value) <= new Date(S.now) : false;
const statusPill = value => `<span class="pill ${["cancelled","expired"].includes(value)?"red":value==="completed"?"gray":value==="open"?"orange":"blue"}">${esc(human(value))}</span>`;
const rolePill = () => `<span class="pill ${operator()?"orange":"blue"}">${operator()?"Warehouse operator":"Customer"}</span>`;
const empty = (title, text, name="box") => `<div class="empty">${icon(name)}<h3>${esc(title)}</h3><p>${esc(text)}</p></div>`;
async function api(path, { method="GET", body, key } = {}) {
const headers = { "Accept":"application/json" };
if (body !== undefined) headers["Content-Type"] = "application/json";
if (method !== "GET" && S.csrfToken) headers["X-CSRF-Token"] = S.csrfToken;
if (key) headers["Idempotency-Key"] = key;
let response;
try { response = await fetch(path, {method, headers, body:body === undefined?undefined:JSON.stringify(body), credentials:"same-origin"}); }
catch { const e = new Error("The service could not be reached. Check your connection, then retry this same action."); e.retryable=true; throw e; }
let result;
try { result=await response.json(); } catch { const e = new Error("The service returned an unreadable response. Refresh to check the saved state."); e.retryable=true; throw e; }
if (!response.ok) {
const e=new Error(result.error?.message || `Request failed (${response.status}).`);
e.code=result.error?.code;e.details=result.error?.details;e.retryable=result.error?.retryable || response.status>=500;e.status=response.status;
throw e;
}
return result;
}
function fail(error, retry=null) {
S.notice={type:"error",title:error.status===409?"The operation needs your attention":error.status===403?"This action is not available for this user":"Unable to complete the action",message:error.message,code:error.code,details:error.details};
S.retry=error.retryable?retry:null;
}
function success(message) {S.notice={type:"success",title:"Saved",message};S.retry=null;}
async function loadBootstrap() {
const data=await api("/api/bootstrap");
S.data=data;S.user=data.user || S.user;S.csrfToken=data.csrfToken || S.csrfToken;S.now=data.now || S.now;
if(!reservations().some(r=>r.id===S.selectedReservation)) S.selectedReservation=reservations()[0]?.id || null;
S.selectedPallets=new Set([...S.selectedPallets].filter(id=>activePallets().some(p=>p.id===id)));
const warehouses=data.warehouses || [];
for(const form of [S.booking,S.range]) {
if(!warehouses.some(w=>w.id===form.warehouseId))form.warehouseId=warehouses[0]?.id || "";
const zones=warehouse(form.warehouseId)?.zones || [];
if(!zones.some(z=>z.id===form.zoneId))form.zoneId=zones[0]?.id || "";
}
if(!S.quote && data.quotes?.length) {
const q=data.quotes.find(q=>!expired(q.expiresAt)&&q.status!=="confirmed");
if(q?.lines)S.quote=q;
}
if(S.quote && data.holds?.length) S.hold=data.holds.find(h=>h.quoteId===S.quote.id) || S.hold;
}
async function loadCapacity() {
const query=new URLSearchParams({warehouseId:S.range.warehouseId,zoneId:S.range.zoneId,startAt:localISO(S.range.startAt),endAt:localISO(S.range.endAt)});
S.capacity=await api(`/api/capacity?${query}`);
}
async function loadInvoice() {
S.invoice=S.selectedReservation?await api(`/api/reservations/${encodeURIComponent(S.selectedReservation)}/invoice`):null;
}
async function refresh() {
await loadBootstrap();
if(S.tab==="capacity") await loadCapacity();
if(S.tab==="charges") await loadInvoice();
}
async function action(path,body,{idempotent=false,message="Your changes have been recorded.",after}={}) {
const key=idempotent?crypto.randomUUID():undefined;
const perform=async()=>{
if(S.busy)return;
S.busy=true;S.notice=null;render();
try {
const result=await api(path,{method:"POST",body,key});
if(after)await after(result);
await refresh();success(message);
} catch(e){fail(e,perform);}finally{S.busy=false;render();}
};
await perform();
}
async function view(tab) {
if(S.busy)return;
S.tab=tab;S.notice=null;S.busy=true;render();
try {await refresh();}catch(e){fail(e,()=>view(tab));}finally{S.busy=false;render();}
}
function notice() {
if(!S.notice)return "";
const n=S.notice;
let details="";
if(n.details){
const d=n.details;
if(typeof d==="string")details=d;
else if(d.available!==undefined)details=`Available compatible positions: ${d.available}.`;
else if(d.minimumAvailable!==undefined)details=`Minimum compatible availability: ${d.minimumAvailable}.`;
else if(d.reason)details=d.reason;
else if(d.action)details=d.action;
}
return `<div class="notice ${n.type==="error"?"error":""}" role="${n.type==="error"?"alert":"status"}"><div><strong>${esc(n.title)}</strong><p>${esc(n.message)}</p>${details?`<p class="small">${esc(details)}</p>`:""}</div><div class="row">${S.retry?'<button data-action="retry">Retry</button>':""}<button data-action="dismiss" aria-label="Dismiss notification">×</button></div></div>`;
}
function auth() {
return `${S.busy?'<div class="busy-strip" role="progressbar" aria-label="Signing in"></div>':""}<main class="auth-page"><section class="auth-brand">${brand()}<div class="auth-copy"><div class="eyebrow">Capacity → custody → clarity</div><h1>Every position.<br>Every pallet.<br>Accounted for.</h1><p>Make a storage commitment. Record what arrives. Follow each movement through to an explainable bill.</p><div class="auth-art" aria-hidden="true"><span></span><span></span><span></span><span></span><span></span></div></div><div class="auth-bottom">STANDARD AMBIENT PALLET STORAGE<br>A working, synthetic warehouse operations prototype.</div></section><section class="auth-form-wrap"><div class="auth-form"><span class="demo-pill">Demonstration environment</span><h2>Open your workspace</h2><p>Choose a seeded account to follow the customer or warehouse operator workflow.</p>${notice()}<form id="login-form"><div class="stack"><div class="field"><label for="login-email">Demo account</label><select id="login-email" name="email"><option value="shipper@cedar.demo" ${S.loginEmail==="shipper@cedar.demo"?"selected":""}>Cedar Supply · customer</option><option value="shipper@juniper.demo" ${S.loginEmail==="shipper@juniper.demo"?"selected":""}>Juniper Outfitters · customer</option><option value="operator@warehouse.demo" ${S.loginEmail==="operator@warehouse.demo"?"selected":""}>Warehouse operations · operator</option></select></div><div class="field"><label for="login-password">Demo password</label><input id="login-password" name="password" type="password" value="warehouse-demo" required autocomplete="current-password"><small>Local demo password: warehouse-demo</small></div></div><button type="submit" ${S.busy?"disabled":""}>${S.busy?"Opening workspace…":"Sign in to workspace →"}</button></form><p class="note">All customers, goods, warehouses, and prices are synthetic. These accounts authenticate with the service; each customer can see only their own records.</p></div></section></main>`;
}
function clockPanel() {
if(!operator()||S.data?.demoMode===false)return "";
const checkpoints=[['2026-10-26T15:00:00Z','26 Oct · 09:00 · receive'],['2026-10-27T22:00:00Z','27 Oct · 16:00 · partial release'],['2026-10-28T16:00:00Z','28 Oct · 10:00 · final release']];
return `<div class="clock-controls"><details><summary><strong>Presentation clock</strong> · ${esc(date(S.now))} Denver · advance to the next operation</summary><div class="row">${checkpoints.map(([value,label])=>`<button class="secondary" data-clock="${value}" ${new Date(value)<=new Date(S.now)||S.busy?"disabled":""}>${label}</button>`).join("")}</div><form id="clock-form" class="row" style="margin-top:12px"><div class="field"><label for="clock-value">Advance to a Denver local time</label><input type="datetime-local" id="clock-value" name="now" required value="${esc(atLocal(S.now))}"></div><button class="secondary" type="submit" style="align-self:end" ${S.busy?"disabled":""}>Advance clock</button></form><p class="note">The clock moves forward only. Operations use this time; their recording time is preserved separately. A reset is available through the local reset command.</p></details></div>`;
}
const headings={capacity:["01 / CAPACITY","Space with a clear commitment.","See what is physically free now and what can be offered across the requested interval."],booking:["02 / BOOKING","A promise, made explicit.","Check compatibility, review the estimate, and secure capacity before confirming your reservation."],operations:["03 / OPERATIONS","Record what actually happens.","Receive, position, and release goods while keeping the remaining commitment visible."],charges:["04 / CHARGES","Every charge has a source.","Inspect actual pallet-days, completed handling, and the movements behind each line."]};
function render() {
if(!S.user){$("#app").innerHTML=auth();return;}
const [eyebrow,title,sub]=headings[S.tab];
$("#app").innerHTML=`${S.busy?'<div class="busy-strip" role="progressbar" aria-label="Updating workspace"></div>':""}<div class="shell"><aside class="sidebar">${brand()}<nav class="nav" aria-label="Workspaces">${["capacity","booking","operations","charges"].map(tab=>`<button data-tab="${tab}" class="${S.tab===tab?"active":""}" ${S.tab===tab?'aria-current="page"':""}>${icon(tab)}<span>${human(tab)}</span></button>`).join("")}</nav><div class="sidebar-bottom"><span class="demo-pill">Synthetic data only</span><div class="sidebar-note"><strong>One position. One pallet.</strong>Ambient rack storage.<br>Capacity and custody stay distinct.<span class="tagline">A small service. A complete workflow.</span></div></div></aside><div class="main"><header class="topbar"><div class="location"><i class="dot"></i><span>Warehouse storage as a service</span></div><div class="user-tools"><div class="avatar">${esc((S.user.name||S.user.orgName||"DM").split(" ").map(s=>s[0]).slice(0,2).join(""))}</div><div class="user-meta">${esc(S.user.orgName||S.user.name)}<span>${operator()?"Warehouse operator":"Customer workspace"}</span></div><button class="quiet" data-action="switch">Switch user ↗</button></div></header><main class="content" id="workspace"><div class="page-head"><div><div class="eyebrow">${eyebrow}</div><h1>${title}</h1><p>${sub}</p></div><div class="date-stamp"><span class="demo-pill">Synthetic demo</span><strong>${esc(date(S.now))}</strong>America/Denver · demo clock</div></div>${notice()}${clockPanel()}${S.tab==="capacity"?capacityView():S.tab==="booking"?bookingView():S.tab==="operations"?operationsView():chargesView()}<footer class="footer-note"><span><i></i>Persisted operations · synthetic data · America/Denver</span><span>svc_warehouse / prototype 0.1</span></footer></main></div></div>`;
$("#app").querySelectorAll("button[type=submit]").forEach(b=>{if(S.busy)b.disabled=true;});
}
function selectOptions(items,value,label=item=>item.name) {return items.map(item=>`<option value="${esc(item.id)}" ${item.id===value?"selected":""}>${esc(label(item))}</option>`).join("");}
function locationFields(form,prefix) {
return `<div class="field"><label for="${prefix}-warehouse">Warehouse</label><select id="${prefix}-warehouse" name="warehouseId" data-bind="${prefix}.warehouseId">${selectOptions(S.data?.warehouses||[],form.warehouseId)}</select></div><div class="field"><label for="${prefix}-zone">Storage zone</label><select id="${prefix}-zone" name="zoneId" data-bind="${prefix}.zoneId">${selectOptions(warehouse(form.warehouseId)?.zones||[],form.zoneId)}</select></div>`;
}
function dateFields(form,prefix) {
return `<div class="field"><label for="${prefix}-start">Arrival · Denver time</label><input type="datetime-local" step="3600" id="${prefix}-start" name="startAt" data-bind="${prefix}.startAt" value="${esc(form.startAt)}" required></div><div class="field"><label for="${prefix}-end">Expected departure · Denver time</label><input type="datetime-local" step="3600" id="${prefix}-end" name="endAt" data-bind="${prefix}.endAt" value="${esc(form.endAt)}" required></div>`;
}
function exceptionsView(items) {
if(!items?.length)return "";
return `<div class="panel"><div class="panel-head"><div><div class="eyebrow">Operator attention</div><h2>${items.length} unresolved ${items.length===1?"exception":"exceptions"}</h2><p>The recorded physical state remains visible until an operator resolves it.</p></div><span class="pill orange">Action needed</span></div>${items.map(e=>`<div class="exception"><h3>${esc(human(e.kind||e.type||e.code||"Capacity exception"))}${e.positionLabel?` · ${esc(e.positionLabel)}`:""}</h3><p>${esc(e.message||e.description||"A physical constraint threatens an existing storage commitment.")}</p><p><strong>Next action:</strong> ${esc(e.action||e.operatorAction||"Arrange a capacity-checked extension, relocate the pallet to a feasible position, or record its actual release. Contact the affected shipper if the promise cannot be honored.")}</p>${e.threatenedReservationIds?.length?`<p>Affected booking references: ${e.threatenedReservationIds.map(id=>esc(shortId(id))).join(", ")}</p>`:""}${e.reservationId&&reservations().some(r=>r.id===e.reservationId)?`<div class="row"><button class="secondary small-button" data-open-reservation="${esc(e.reservationId)}">Open operations</button></div>`:""}${operator()&&e.blockId?`<div class="row"><button class="secondary small-button" data-unblock="${esc(e.blockId)}">Remove completed block</button></div>`:""}</div>`).join("")}</div>`;
}
function capacityView() {
const c=S.capacity;
return `<section class="panel"><form id="capacity-form" class="filter-grid">${locationFields(S.range,"range")}${dateFields(S.range,"range")}<button type="submit" class="secondary">Check interval</button></form></section>${!c?'<div class="inline-loading"><span class="spinner"></span>Checking persisted capacity…</div>':`<div class="metrics grid four"><div class="metric primary"><div class="label">Physically free now</div><div class="number">${esc(c.physical?.free)} <small>/ ${esc(c.totalPositions)} positions</small></div><p>No pallet present and no current block.</p></div><div class="metric"><div class="label"><i class="swatch occupied"></i> Goods in custody</div><div class="number">${esc(c.physical?.occupied)} <small>pallets</small></div><p>Present until an actual release is recorded.</p></div><div class="metric"><div class="label">Bookable for the full interval</div><div class="number">${esc(c.projected?.minimumAvailable)} <small>positions</small></div><p>Same compatible positions across the whole interval.</p></div><div class="metric"><div class="label"><i class="swatch blocked"></i> Blocked now</div><div class="number">${esc(c.physical?.blocked)} <small>positions</small></div><p>Physical outages remain visible.</p></div></div><section class="panel"><div class="panel-head"><div><div class="eyebrow">${esc(warehouse(c.warehouseId)?.name||"Warehouse")} / ${esc(zone(c.warehouseId,c.zoneId)?.name||"Zone")}</div><h2>Capacity through time</h2><p>${esc(date(localISO(S.range.startAt)))} — ${esc(date(localISO(S.range.endAt)))} · Denver</p></div><span class="pill gray">1 pallet = 1 position</span></div>${timeline(c)}<div class="legend">${["occupied","held","committed","blocked","available"].map(k=>`<span><i class="swatch ${k}"></i>${human(k)}</span>`).join("")}</div><div class="policy"><strong>Departure assumption.</strong> ${esc(c.projected?.assumption||"Future availability assumes on-time departure at the reservation end. Goods that overstay remain occupied and require operator action.")} Physical vacancy alone is not a promise of bookable space.</div></section><section class="panel"><div class="panel-head"><div><h2>Positions right now</h2><p>Actual occupancy and current operational blocks.</p></div><span class="pill gray">${esc(c.totalPositions)} rack positions</span></div><div class="positions">${(c.positions||[]).map(p=>`<div class="position ${p.blocked?"blocked":p.currentPalletId||p.occupied?"occupied":""}"><span class="position-icon" aria-hidden="true">${p.blocked?"×":p.currentPalletId||p.occupied?"▣":"□"}</span><strong>${esc(p.label||p.id)}</strong><small>${p.blocked?"Blocked":p.currentPalletId||p.occupied?"Occupied":"Physically free"}</small></div>`).join("")}</div></section>${exceptionsView(c.exceptions)}${operator()?blockForm(c)+blockList(c):""}`}`;
}
function timeline(c) {
const all=c.timeline||[];
if(!all.length)return empty("No interval to display","Choose an arrival and expected departure to inspect capacity.","capacity");
const step=Math.max(1,Math.ceil(all.length/13));
const samples=all.filter((_,i)=>i%step===0||i===all.length-1);
return `<div class="timeline" role="img" aria-label="Projected capacity. Minimum ${esc(c.projected?.minimumAvailable)} available positions over the requested interval.">${samples.map(row=>{
const total=Math.max(Number(c.totalPositions)||1,["occupied","held","committed","blocked","available"].reduce((n,k)=>n+Number(row[k]||0),0));
return `<div class="timeline-col ${row.deficit>0?"deficit":""}" title="${esc(date(row.at))}: ${esc(row.available)} available, ${esc(row.occupied)} occupied, ${esc(row.held)} held, ${esc(row.committed)} committed, ${esc(row.blocked)} blocked${row.deficit>0?`, ${row.deficit} deficit`:""}"><div class="timeline-value">${esc(row.available)}</div><div class="timeline-stack">${["occupied","held","committed","blocked","available"].map(k=>`<div class="bar ${k}" style="height:${Math.max(0,Number(row[k]||0))/total*100}%"></div>`).join("")}</div><div class="timeline-time"><strong>${esc(day(row.at))}</strong>${esc(date(row.at,{month:undefined,day:undefined}))}</div></div>`;
}).join("")}</div><div class="note">Numbers above each bar show projected available positions. Availability checks every hour; ${step>1?`the chart samples every ${step} hours`:'each hour is shown'}. Red outlines flag a capacity deficit. This is a projection from current records, not a reconstruction of past occupancy.</div>`;
}
function blockForm(c) {
return `<section class="panel"><details style="border:0;padding:0;margin:0"><summary>Record a blocked position</summary><p class="note">Record a true outage even if it conflicts with a promise. Existing goods and commitments remain on record, and any conflict appears as an exception.</p><form id="block-form"><div class="form-grid"><div class="field"><label for="block-positions">Affected positions · select one or more</label><select id="block-positions" name="positionIds" multiple required>${selectOptions(c.positions||[],null,p=>p.label||p.id)}</select></div><div class="stack"><div class="field"><label for="block-start">Starts · Denver time</label><input id="block-start" type="datetime-local" step="3600" name="startAt" value="${esc(S.range.startAt)}" required></div><div class="field"><label for="block-end">Ends · Denver time</label><input id="block-end" type="datetime-local" step="3600" name="endAt" value="${esc(S.range.endAt)}" required></div></div><div class="field full"><label for="block-reason">Business reason</label><input id="block-reason" name="reason" placeholder="For example: damaged rack beam awaiting inspection" required minlength="3"></div></div><div class="form-actions"><button type="submit" class="secondary">Record capacity block</button></div></form></details></section>`;
}
function blockList(c) {
const blocks=(S.data.blocks||[]).filter(b=>b.warehouseId===c.warehouseId&&(c.positions||[]).some(p=>p.id===b.positionId));
if(!blocks.length)return "";
return `<section class="panel"><div class="panel-head"><div><h2>Recorded capacity blocks</h2><p>Clear a restriction only after verifying the position is safe to use.</p></div></div><div class="table-wrap"><table><thead><tr><th>Position</th><th>Blocked interval · Denver</th><th>Reason</th><th>Action</th></tr></thead><tbody>${blocks.map(b=>`<tr><td><strong>${esc(b.positionId)}</strong><small>${expired(b.endAt)?"Interval ended":new Date(b.startAt)>new Date(S.now)?"Scheduled":"Active now"}</small></td><td>${esc(date(b.startAt))}<small>through ${esc(date(b.endAt))}</small></td><td>${esc(b.reason)}</td><td><button class="secondary small-button" data-unblock="${esc(b.id)}">Clear restriction</button></td></tr>`).join("")}</tbody></table></div></section>`;
}
function bookingView() {
const q=S.quote,h=S.hold;
const confirmed=h?.status==="confirmed"||q?.status==="confirmed";
const steps=[['Request',!!q],['Quote',!!h],['Hold',confirmed],['Confirmed',false]];
const active=confirmed?3:h?2:q?1:0;
return `<div class="steps" aria-label="Booking progress">${steps.map(([name,complete],i)=>`<div class="step ${i===active?"active":complete?"complete":""}"><i>${complete?"✓":i+1}</i>${name}</div>`).join("")}</div>${operator()?`<div class="callout neutral" style="margin-bottom:22px"><strong>Operator workspace</strong>Review confirmed commitments below. Sign in as a customer to create a storage request and accept its quote.</div>`:`<div class="grid split"><section class="panel"><div class="panel-head"><div><h2>Storage request</h2><p>Standard ambient pallets · sealed, packaged dry goods.</p></div><span class="pill blue">Customer</span></div><form id="quote-form"><div class="form-grid">${locationFields(S.booking,"booking")}${dateFields(S.booking,"booking")}<div class="field"><label for="booking-quantity">Pallet positions</label><input type="number" id="booking-quantity" name="quantity" data-bind="booking.quantity" min="1" max="1000" value="${S.booking.quantity}" required></div><div class="field"><label>Accepted footprint</label><div class="callout neutral" style="padding:9px 11px;min-height:44px">1,219 × 1,016 mm · 48 × 40 in</div></div><div class="field"><label for="booking-height">Loaded height · mm</label><input type="number" id="booking-height" name="heightMm" data-bind="booking.heightMm" min="1" max="10000" value="${S.booking.heightMm}" required></div><div class="field"><label for="booking-weight">Gross weight · kg / pallet</label><input type="number" id="booking-weight" name="weightKg" data-bind="booking.weightKg" min="1" max="100000" value="${S.booking.weightKg}" required></div></div><div class="policy"><strong>Supported goods:</strong> sealed books, textiles, and dry household merchandise. No food, pharmaceuticals, liquids, hazardous goods, or special temperature requirements. ${esc(zone(S.booking.warehouseId,S.booking.zoneId)?.maxHeightMm||1800)} mm / ${esc(zone(S.booking.warehouseId,S.booking.zoneId)?.maxWeightKg||1000)} kg maximum in this zone.</div><div class="form-actions"><button class="accent" type="submit">Check capacity & get quote →</button><p>A quote does not hold space.</p></div></form>${compatibleOptions()}</section><section>${quoteView(q,h,confirmed)}</section></div>`}<section class="panel"><div class="panel-head"><div><h2>${operator()?"Customer commitments":"Your reservations"}</h2><p>Accepted terms, outstanding arrivals, and goods in custody.</p></div><button class="quiet small-button" data-action="refresh">Refresh ↻</button></div>${reservationCards()}</section>`;
}
function compatibleOptions() {
return `<details><summary>Compare storage capabilities</summary><div class="table-wrap"><table><thead><tr><th>Warehouse / zone</th><th>Limits</th><th>Fit</th></tr></thead><tbody>${(S.data?.warehouses||[]).flatMap(w=>(w.zones||[]).map(z=>{
const fit=Number(S.booking.heightMm)<=Number(z.maxHeightMm||1800)&&Number(S.booking.weightKg)<=Number(z.maxWeightKg||1000);
return `<tr><td><strong>${esc(w.name)}</strong><small>${esc(z.name)}</small></td><td>${esc(z.maxHeightMm||1800)} mm<small>${esc(z.maxWeightKg||1000)} kg</small></td><td>${fit?`<button class="secondary small-button" data-use-zone="${esc(z.id)}" data-warehouse="${esc(w.id)}">Use zone</button>`:'<span class="pill red">Exceeds limit</span>'}</td></tr>`;
})).join("")}</tbody></table></div><p class="note">These are capability matches. Availability is checked for your full interval when you request a quote.</p></details>`;
}
function quoteView(q,h,confirmed) {
if(!q)return `<div class="panel" style="height:calc(100% - 22px)"><div class="panel-head"><div><div class="eyebrow">Your estimate</div><h2>Terms before commitment.</h2></div></div>${empty("Start with a storage request","Your itemized estimate and hold options will appear here once the service checks the request.","booking")}<div class="policy"><strong>Demonstration tariff</strong><br>$2.00 / occupied pallet-day<br>$4.00 / received pallet<br>$3.00 / released pallet<br>Unreceived reserved space has no fee.</div></div>`;
const isExpired=expired(q.expiresAt),holdExpired=h&&expired(h.expiresAt);
return `<div class="quote-card"><div class="quote-head"><div class="row between"><span class="eyebrow">Itemized estimate</span><span class="pill ${isExpired?"red":"gray"}">${isExpired?"Expired":"Synthetic rates"}</span></div><div class="quote-total">${money(q.totalCents)}</div><p>Estimated for ${esc(q.quantity||S.booking.quantity)} pallets · actual custody determines the bill.</p>${q.availability?`<p style="margin-top:9px"><strong>${esc(q.availability.available)}</strong> compatible positions available at quote time.</p>`:""}</div><div class="quote-body">${(q.lines||[]).map(l=>`<div class="line-item"><span>${esc(human(l.kind))}<small>${esc(l.quantity)} ${l.kind==="storage"?"pallet-days":"pallets"} × ${money(l.unitCents)}</small></span><strong>${money(l.totalCents)}</strong></div>`).join("")}<div class="note" style="margin-top:14px">${esc(q.rateVersion||"Agreed tariff")} · USD<br>Quote valid until ${esc(date(q.expiresAt))} Denver.</div>${confirmed?'<div class="callout" style="margin-top:18px"><strong>Reservation confirmed</strong>The accepted tariff is frozen. Find this reservation below to follow receiving and release.</div>':h?`<div class="hold-box"><strong>${holdExpired?"This hold has expired":"Capacity is temporarily held"}</strong><p>${esc((h.positionIds||[]).length||q.quantity)} positions · expires ${esc(date(h.expiresAt))} Denver.<br>${holdExpired?"Request a new quote to continue.":"Confirmation validates and accepts the held capacity atomically."}</p><button class="accent" data-action="confirm" ${holdExpired||isExpired||S.busy?"disabled":""}>Confirm reservation</button></div>`:isExpired?'<div class="callout orange" style="margin-top:18px"><strong>A new quote is required</strong>Submit the storage request again to refresh the terms and capacity check.</div>':`<div class="form-actions"><button class="accent" data-action="hold" ${S.busy?"disabled":""}>Create temporary hold</button></div><p class="note" style="margin-top:12px">A quote consumes no capacity. The hold reserves compatible positions for up to 10 demo minutes.</p>`}<div class="policy">No fee for unreceived reserved space. No taxes, minimums, or other fees in this demonstration. A partial warehouse-local calendar day counts as one pallet-day.</div></div></div>`;
}
function reservationCards() {
if(!reservations().length)return empty("No reservations yet",operator()?"Confirmed customer bookings will appear here with their outstanding arrivals.":"Create a quote, place a temporary hold, then confirm the reservation.","booking");
return reservations().map(r=>`<article class="reservation-card ${r.id===S.selectedReservation?"selected":""}"><div class="row between"><div><div class="title"><strong>${esc(r.customerName||S.user.orgName)} · ${esc(shortId(r.id))}</strong>${statusPill(r.status)}</div><div class="sub">${esc(warehouse(r.warehouseId)?.name||r.warehouseId)} / ${esc(zone(r.warehouseId,r.zoneId)?.name||r.zoneId)}<br>${esc(date(r.startAt))} → ${esc(date(r.endAt))} Denver</div></div><button class="secondary small-button" data-open-reservation="${esc(r.id)}">${operator()?"Open operations":"View inventory"} →</button></div><div class="reservation-counts"><span><strong>${esc(r.quantity)}</strong>reserved</span><span><strong>${esc(r.unreceived)}</strong>unreceived</span><span><strong>${esc(r.inCustody)}</strong>in custody</span><span><strong>${esc(r.released)}</strong>released</span></div><div class="row between"><span class="note">${esc(r.rateVersion)} · receiving ${esc(human(r.receivingStatus))}</span><button class="quiet small-button" data-invoice="${esc(r.id)}">Inspect charges ↗</button></div></article>`).join("");
}
function reservationSelector(id="reservation-select") {
return `<div class="field"><label for="${id}">Reservation</label><select id="${id}" data-select-reservation>${selectOptions(reservations(),S.selectedReservation,r=>`${r.customerName||S.user.orgName} · ${shortId(r.id)} · ${r.inCustody} in custody`)}</select></div>`;
}
function receivingDraft(r) {
if(!S.receivingDrafts[r.id]){
const count=Math.min(5,Number(r.unreceived)||0),prefix=`SYN-${String(r.id).replace(/^res-/, "").slice(0,4).toUpperCase()}`;
S.receivingDrafts[r.id]={labels:Array.from({length:count},(_,i)=>`${prefix}-${String(i+1).padStart(3,"0")}`).join("\n"),heightMm:"",weightKg:"",reason:"Scheduled inbound delivery",approveVariance:false,expectedEndAt:""};
}
return S.receivingDrafts[r.id];
}
function operationsView() {
if(!reservations().length)return `<section class="panel">${empty("No expected arrivals yet",operator()?"A customer must confirm a reservation before goods can be received.":"Confirm your first reservation in the Booking workspace.","operations")}<div class="form-actions"><button class="secondary" data-tab="booking">Go to Booking →</button></div></section>`;
const r=selected(),pallets=activePallets();
if(!r)return empty("Choose a reservation","Select a reservation to inspect its current goods.");
return `<section class="panel"><div class="grid two keep">${reservationSelector()}<div class="row" style="justify-content:flex-end;align-self:end">${statusPill(r.status)}<span class="pill gray">Receiving ${esc(human(r.receivingStatus))}</span><button class="quiet small-button" data-action="refresh">Refresh ↻</button></div></div><div class="divider"></div><div class="row between"><div><h2>${esc(warehouse(r.warehouseId)?.name||r.warehouseId)}</h2><p class="note" style="margin:0">${esc(date(r.startAt))} → ${esc(date(r.endAt))} Denver · ${esc(zone(r.warehouseId,r.zoneId)?.name||r.zoneId)}</p></div><div class="reservation-counts" style="margin:8px 0"><span><strong>${esc(r.unreceived)}</strong>unreceived</span><span><strong>${esc(r.inCustody)}</strong>in custody</span><span><strong>${esc(r.released)}</strong>released</span></div></div>${["cancelled","expired"].includes(r.status)&&r.inCustody>0?'<div class="callout orange" style="margin-top:16px"><strong>Goods remain in custody</strong>Ending a booking releases only its outstanding commitment. These pallets remain present and billable until their actual release is recorded.</div>':""}</section>${operator()&&r.receivingStatus==="open"&&r.status==="confirmed"?receiveForm(r):""}${exceptionsView((S.data.exceptions||[]).filter(e=>!e.reservationId||e.reservationId===r.id))}<section class="panel"><div class="panel-head"><div><h2>${operator()?"Current custody":"Your inventory"}</h2><p>${operator()?"Select the pallets that actually leave the warehouse.":"Select pallets and request their release from the operator."}</p></div><span class="pill ${pallets.length?"blue":"gray"}">${pallets.length} present</span></div>${pallets.length?`<div class="row between" style="margin-bottom:12px"><span class="note">${S.selectedPallets.size} selected</span><div class="row"><button class="quiet small-button" data-action="select-two">Select first two</button><button class="quiet small-button" data-action="select-all">Select all</button></div></div><div class="table-wrap"><table><thead><tr><th><input type="checkbox" aria-label="Select all pallets" id="select-all-pallets" ${S.selectedPallets.size===pallets.length?"checked":""}></th><th>Pallet</th><th>Position</th><th>Received · Denver</th></tr></thead><tbody>${pallets.map(p=>`<tr class="${S.selectedPallets.has(p.id)?"selected-row":""}"><td><input type="checkbox" data-pallet="${esc(p.id)}" aria-label="Select ${esc(p.label)}" ${S.selectedPallets.has(p.id)?"checked":""}></td><td><strong>${esc(p.label||shortId(p.id))}</strong><small>${p.externalId?`External: ${esc(p.externalId)}`:`Internal: ${esc(shortId(p.id))}`}</small></td><td><span class="pill gray">${esc(p.positionLabel||p.positionId)}</span></td><td>${esc(date(p.receivedAt))}</td></tr>`).join("")}</tbody></table></div><form id="release-form"><div class="form-grid" style="margin-top:18px"><div class="field full"><label for="release-reason">${operator()?"Release":"Request"} reason</label><input id="release-reason" name="reason" required minlength="3" value="${operator()?"Collected by authorized carrier":"Goods ready for collection"}"></div></div><div class="form-actions"><button class="accent" type="submit" ${!S.selectedPallets.size||S.busy?"disabled":""}>${operator()?"Record release":"Request release"} · ${S.selectedPallets.size} ${S.selectedPallets.size===1?"pallet":"pallets"}</button><p>${operator()?"Only selected pallets are removed from custody.":"Custody changes only after the operator records departure."}</p></div></form>`:empty(Number(r.released)>0?"All received goods have departed":"No pallets received yet",Number(r.released)>0?"The custody history and charges remain available below.":"Accepted goods will appear here with stable internal identifiers and their assigned position.")}${releaseRequests(r)}${releasedHistory()}${operator()&&pallets.length?relocateForm(r):""}</section>${manageReservation(r)}<section class="panel"><div class="row between"><div><h2>Recorded history</h2><p class="note" style="margin-bottom:0">Actions stay linked to their actor, time, reason, and reservation.</p></div><div class="row"><button class="secondary small-button" data-action="audit">View audit history</button><button class="secondary small-button" data-invoice="${esc(r.id)}">Inspect invoice →</button></div></div>${auditView()}</section>`;
}
function receiveForm(r) {
const draft=receivingDraft(r);
return `<section class="panel"><div class="panel-head"><div><div class="eyebrow">Expected arrival</div><h2>Receive against this reservation</h2><p>One line per pallet. Positions are assigned atomically from compatible capacity.</p></div><span class="pill orange">${esc(r.unreceived)} outstanding</span></div><form id="receive-form"><div class="grid two"><div class="field"><label for="receive-labels">Pallet labels · optional external ID after a comma</label><textarea id="receive-labels" name="labels" data-receive="labels" placeholder="SYN-PALLET-001\nSYN-PALLET-002, SYN-EXT-002" required>${esc(draft.labels)}</textarea><small>Demo labels are synthetic, not GS1-conformant identifiers.</small></div><div class="stack"><div class="form-grid"><div class="field"><label for="receive-height">Actual height · mm, if different</label><input type="number" min="1" id="receive-height" name="heightMm" data-receive="heightMm" value="${esc(draft.heightMm)}" placeholder="Agreed height"></div><div class="field"><label for="receive-weight">Actual gross weight · kg, if different</label><input type="number" min="1" id="receive-weight" name="weightKg" data-receive="weightKg" value="${esc(draft.weightKg)}" placeholder="Agreed weight"></div></div><div class="field"><label for="receive-reason">Receiving reason / variance explanation</label><input id="receive-reason" name="reason" data-receive="reason" value="${esc(draft.reason)}" required minlength="3"></div><label class="check-label"><input type="checkbox" name="approveVariance" data-receive="approveVariance" ${draft.approveVariance?"checked":""}>Explicitly accept an early, late, or excess delivery, subject to capacity validation.</label><div class="field"><label for="receive-end">New expected departure for a late receipt · optional</label><input id="receive-end" type="datetime-local" step="3600" name="expectedEndAt" data-receive="expectedEndAt" value="${esc(draft.expectedEndAt)}"></div></div></div><div class="form-actions"><button type="submit" class="accent" id="receive-submit">Receive ${draft.labels.split("\n").filter(s=>s.trim()).length} pallets</button><p>Receipt time: ${esc(date(S.now))} Denver.</p></div></form>${Number(r.unreceived)>0?`<details><summary>Close receiving and decide the unreceived balance</summary><form id="close-receiving-form"><div class="form-grid"><div class="field"><label for="close-disposition">Remaining ${esc(r.unreceived)} ${Number(r.unreceived)===1?"position":"positions"}</label><select id="close-disposition" name="disposition"><option value="release">Release unused capacity</option><option value="retain">Retain the commitment until its end</option></select></div><div class="field"><label for="close-reason">Business reason</label><input id="close-reason" name="reason" value="Delivery complete; no further pallets expected" required minlength="3"></div></div><div class="form-actions"><button type="submit" class="secondary">Close receiving</button><p>Received goods remain in custody.</p></div></form></details>`:`<div class="form-actions"><button class="secondary small-button" data-action="close-full">Close completed receiving</button></div>`}</section>`;
}
function releaseRequests(r) {
const requests=(S.data.releaseRequests||[]).filter(q=>q.reservationId===r.id);
if(!requests.length)return "";
return `<details open><summary>Release requests · ${requests.length}</summary><div class="stack">${requests.map(q=>`<div class="callout neutral"><div class="row between"><div><strong>${esc(human(q.status||"pending"))} · ${(q.palletIds||[]).length} pallets</strong>${esc(q.reason||"Collection requested")}${q.createdAt?`<br>${esc(date(q.createdAt))}`:""}</div>${operator()&&["pending","requested",undefined].includes(q.status)?`<button class="secondary small-button" data-release-request="${esc(q.id)}">Select requested pallets</button>`:""}</div></div>`).join("")}</div></details>`;
}
function releasedHistory() {
const pallets=inventory().filter(p=>p.releasedAt);
if(!pallets.length)return "";
return `<details><summary>Released inventory · ${pallets.length} pallets</summary><div class="table-wrap"><table><thead><tr><th>Pallet</th><th>Received · Denver</th><th>Released · Denver</th></tr></thead><tbody>${pallets.map(p=>`<tr><td><strong>${esc(p.label)}</strong><small>${esc(shortId(p.id))}</small></td><td>${esc(date(p.receivedAt))}</td><td>${esc(date(p.releasedAt))}</td></tr>`).join("")}</tbody></table></div></details>`;
}
function relocateForm(r) {
const positions=zone(r.warehouseId,r.zoneId)?.positions||[];
return `<details><summary>Relocate a pallet within its compatible zone</summary><form id="relocate-form"><div class="form-grid"><div class="field"><label for="relocate-pallet">Pallet</label><select name="palletId" id="relocate-pallet">${selectOptions(activePallets(),null,p=>`${p.label} · ${p.positionLabel||p.positionId}`)}</select></div><div class="field"><label for="relocate-position">Destination position</label><select name="positionId" id="relocate-position">${selectOptions(positions,null,p=>p.label||p.id)}</select></div><div class="field full"><label for="relocate-reason">Movement reason</label><input name="reason" id="relocate-reason" placeholder="Reason for changing the physical position" required minlength="3"></div></div><div class="form-actions"><button type="submit" class="secondary">Validate capacity & relocate</button></div></form><p class="note" style="margin-top:12px">A destination must remain compatible and available. The original movement is preserved in history.</p></details>`;
}
function manageReservation(r) {
if(r.status!=="confirmed")return "";
return `<section class="panel"><details style="border:0;margin:0;padding:0"><summary>Amend or end the remaining commitment</summary><p class="note">Changing a reservation checks all affected capacity atomically. A failed amendment preserves the original allocation. Goods already in custody remain recorded.</p><form id="amend-form"><div class="form-grid"><div class="field"><label for="amend-start">Arrival · Denver time</label><input type="datetime-local" step="60" name="startAt" id="amend-start" value="${esc(atLocal(r.startAt))}" required><small>Keep the recorded arrival unchanged, or choose an exact hour for a revised start.</small></div><div class="field"><label for="amend-end">Expected departure · Denver time</label><input type="datetime-local" step="3600" name="endAt" id="amend-end" value="${esc(atLocal(r.endAt))}" required></div><div class="field"><label for="amend-quantity">Total quantity</label><input type="number" name="quantity" id="amend-quantity" min="1" value="${esc(r.quantity)}" required></div><div class="field"><label for="amend-reason">Reason</label><input name="reason" id="amend-reason" placeholder="Explain the revised commitment" required minlength="3"></div></div><div class="form-actions"><button class="secondary" type="submit">Validate & save amendment</button></div></form><div class="divider"></div><form id="end-form"><div class="field"><label for="end-reason">Reason for ending the booking</label><input id="end-reason" name="reason" placeholder="Explain why outstanding commitments should end" required minlength="3"></div><div class="form-actions"><button type="submit" class="danger" name="endAction" value="cancel">Cancel remaining commitment</button>${operator()&&expired(r.endAt)?'<button type="submit" class="secondary" name="endAction" value="expire">Mark booking expired</button>':""}<p>Present goods stay visible and billable.</p></div></form></details></section>`;
}
function auditView() {
if(!S.audit)return "";
const events=S.audit.events||S.audit||[];
return `<div class="divider"></div>${events.length?`<div class="table-wrap"><table><thead><tr><th>Action</th><th>Occurred · Denver</th><th>Actor / reason</th></tr></thead><tbody>${events.map(e=>`<tr><td><strong>${esc(human(e.kind||e.action||e.type))}</strong><small>${esc(shortId(e.id))}</small></td><td>${esc(date(e.occurredAt||e.createdAt||e.recordedAt))}<small>Recorded ${esc(date(e.recordedAt||e.createdAt))}</small></td><td>${esc(e.actorName||e.actorId||e.userId||"Recorded operator")}<small>${esc(e.reason||e.payload?.reason||"")}</small></td></tr>`).join("")}</tbody></table></div>`:empty("No audit entries yet","Recorded operations will appear in this history.")}`;
}
function chargesView() {
if(!reservations().length)return `<section class="panel">${empty("No invoice to preview","Confirm a reservation and record its operations to see explainable charges.","charges")}</section>`;
const invoice=S.invoice;
return `<section class="panel"><div class="grid two keep">${reservationSelector("charges-reservation")}<div class="row" style="justify-content:flex-end;align-self:end"><button class="secondary small-button" data-action="invoice-refresh">Recalculate preview ↻</button></div></div></section>${!invoice?'<div class="inline-loading"><span class="spinner"></span>Reading charges and source movements…</div>':invoiceView(invoice)}`;
}
function invoiceView(i) {
const s=i.summary||{},lines=i.lines||[],events=i.events||[];
const groups=[['Storage',s.palletDays,'pallet-days',200,s.storageCents],['Receiving',lines.filter(l=>l.kind==="receiving").reduce((n,l)=>n+Number(l.quantity||0),0),'pallets',400,s.receivingCents],['Release',lines.filter(l=>l.kind==="release").reduce((n,l)=>n+Number(l.quantity||0),0),'pallets',300,s.releaseCents]];
const provisional=lines.some(l=>l.provisional);
return `<section class="panel"><div class="panel-head"><div><div class="eyebrow">Invoice preview / ${esc(shortId(i.reservationId))}</div><h2>${esc(i.customerName||S.user.orgName)}</h2><p>As of ${esc(date(i.asOf||S.now))} Denver · ${esc(i.rateVersion)} · USD</p></div><span class="pill ${provisional?"orange":"gray"}">${provisional?"Accruing custody":"Preview only"}</span></div><div class="invoice-total"><div><div class="eyebrow">${provisional?"Recorded + accrued charges":"Charges from recorded operations"}</div><strong>${money(s.totalCents)}</strong><p>${esc(s.palletDays||0)} occupied pallet-days · completed receiving and release</p></div><div class="estimate"><span>Original quote estimate</span><strong>${money(i.quoteEstimateCents)}</strong><p>Actual arrivals and departures determine the eventual bill.</p></div></div><div class="table-wrap" style="margin-top:22px"><table><thead><tr><th>Charge</th><th>Measured quantity</th><th class="right">Agreed unit price</th><th class="right">Amount</th></tr></thead><tbody>${groups.map(([name,qty,unit,fallback,total])=>{
const actualUnit=lines.find(l=>l.kind===name.toLowerCase())?.unitCents;
return `<tr><td><strong>${name}</strong></td><td>${esc(qty||0)} ${unit}</td><td class="right">${actualUnit===undefined?"—":money(actualUnit)}</td><td class="right"><strong>${money(total)}</strong></td></tr>`;
}).join("")}${Number(s.adjustmentCents)?`<tr><td><strong>Linked adjustments</strong></td><td>Recorded corrections</td><td class="right">—</td><td class="right"><strong>${money(s.adjustmentCents)}</strong></td></tr>`:""}</tbody></table></div><div class="policy"><strong>Accepted demonstration policy.</strong> Each warehouse-local calendar day with positive custody overlap is one pallet-day. A partial day counts as one; a release at midnight adds no day. Unreceived reserved space is free. No taxes, minimums, or extra fees. ${provisional?"Open-custody storage is provisional as of the preview time; completed charges are persisted.":"Completed operations and their frozen tariff support this preview."}</div></section><section class="panel"><div class="panel-head"><div><h2>Follow each line to its evidence</h2><p>Rates, service dates, and movement references remain attached to the charge.</p></div><span class="pill gray">${lines.length} lines</span></div>${lines.length?`<div class="table-wrap"><table><thead><tr><th>Charge / pallet</th><th>Service date</th><th class="right">Quantity × rate</th><th class="right">Amount</th><th>Evidence</th></tr></thead><tbody>${lines.map(l=>`<tr><td><strong>${esc(human(l.kind))}</strong><small>${esc(l.palletLabel||allPallets().find(p=>p.id===l.palletId)?.label||shortId(l.palletId))}${l.provisional?' · accruing':""}</small></td><td>${esc(l.serviceDate?.slice(0,10)||"—")}</td><td class="right nowrap">${esc(l.quantity)} × ${money(l.unitCents)}</td><td class="right nowrap"><strong>${money(l.totalCents)}</strong></td><td>${lineEvidence(l,events)}</td></tr>`).join("")}</tbody></table></div>`:empty("No completed operations yet","Unreceived reservations do not generate fees in this demonstration.","charges")}${operator()&&events.length?adjustmentForm(events):""}</section>`;
}
function adjustmentForm(events) {
return `<details><summary>Record a linked correction</summary><p class="note">Add a reasoned correction to a recorded movement. This preserves the original event and adds a linked adjustment; it does not rewrite physical custody or historical timestamps.</p><form id="adjustment-form"><div class="form-grid"><div class="field full"><label for="adjust-movement">Source movement</label><select id="adjust-movement" name="movementId">${selectOptions(events,null,e=>`${human(e.kind)} · ${allPallets().find(p=>p.id===e.palletId)?.label||shortId(e.palletId)} · ${date(e.occurredAt)}`)}</select></div><div class="field"><label for="adjust-reason">Business reason</label><input id="adjust-reason" name="reason" placeholder="Explain why a correction is necessary" required minlength="3"></div><div class="field"><label for="adjust-amount">Charge adjustment · USD, optional</label><input id="adjust-amount" name="amount" type="text" inputmode="decimal" placeholder="0.00"><small>Negative amount for a credit. Blank records a note only.</small></div><div class="field full"><label for="adjust-note">Correction note</label><textarea id="adjust-note" name="note" required placeholder="Describe the error and its correction."></textarea></div></div><div class="form-actions"><button class="secondary" type="submit">Append linked correction</button></div></form></details>`;
}
function lineEvidence(l,events) {
const ids=l.movementIds||[];
const source=events.filter(e=>ids.includes(e.id));
return `<details class="line-evidence"><summary>View source${source.length>1?"s":""}</summary><p><strong>Tariff:</strong> ${esc(l.rateVersion||S.invoice?.rateVersion)}<br><strong>Line:</strong> ${esc(shortId(l.id))}${l.provisional?'<br>Provisional accrual at the displayed preview time.':""}</p>${source.length?source.map(e=>`<div class="evidence-event"><p><strong>${esc(human(e.kind))}</strong><br>Occurred ${esc(date(e.occurredAt))} Denver<br>Recorded ${esc(date(e.recordedAt))} Denver<br>Actor: ${esc(e.actorName||e.actorId||"Warehouse operator")}<br>${e.fromPositionId?`From ${esc(e.fromPositionId)}<br>`:""}${e.toPositionId?`To ${esc(e.toPositionId)}<br>`:""}Reason: ${esc(e.reason||"Recorded operation")}${e.adjustmentOf?`<br>Corrects movement ${esc(shortId(e.adjustmentOf))}`:""}</p></div>`).join(""):`<p>${ids.length?ids.map(id=>esc(shortId(id))).join(", "):"Accrual is linked to the pallet’s receipt and current custody."}</p>`}</details>`;
}
document.addEventListener("input",event=>{
const target=event.target;
if(target.dataset.bind){const [group,key]=target.dataset.bind.split(".");S[group][key]=target.value;}
if(target.dataset.receive&&selected()){
const draft=receivingDraft(selected());draft[target.dataset.receive]=target.type==="checkbox"?target.checked:target.value;
if(target.dataset.receive==="labels"&&$("#receive-submit"))$("#receive-submit").textContent=`Receive ${target.value.split("\n").filter(s=>s.trim()).length} pallets`;
}
});
document.addEventListener("change",async event=>{
const target=event.target;
if(target.dataset.bind){
const [group,key]=target.dataset.bind.split(".");S[group][key]=target.value;
if(key==="warehouseId"){S[group].zoneId=warehouse(target.value)?.zones?.[0]?.id||"";render();}
}
if(target.matches("[data-select-reservation]")){
S.selectedReservation=target.value;S.selectedPallets.clear();S.invoice=null;S.audit=null;S.releaseRequestId=null;
if(S.tab==="charges")await view("charges");else render();
}
if(target.dataset.pallet){target.checked?S.selectedPallets.add(target.dataset.pallet):S.selectedPallets.delete(target.dataset.pallet);render();}
if(target.id==="select-all-pallets"){S.selectedPallets=target.checked?new Set(activePallets().map(p=>p.id)):new Set();render();}
});
document.addEventListener("click",async event=>{
const b=event.target.closest("button");if(!b||b.disabled)return;
try {
if(b.dataset.tab){await view(b.dataset.tab);return;}
if(b.dataset.clock){await action("/api/demo/clock",{now:b.dataset.clock},{message:"The presentation clock advanced. Custody and capacity have been recalculated."});return;}
if(b.dataset.openReservation){S.selectedReservation=b.dataset.openReservation;S.selectedPallets.clear();S.audit=null;await view("operations");return;}
if(b.dataset.invoice){S.selectedReservation=b.dataset.invoice;S.invoice=null;await view("charges");return;}
if(b.dataset.useZone){S.booking.warehouseId=b.dataset.warehouse;S.booking.zoneId=b.dataset.useZone;render();return;}
if(b.dataset.releaseRequest){const r=S.data.releaseRequests.find(r=>r.id===b.dataset.releaseRequest);S.releaseRequestId=r.id;S.selectedPallets=new Set((r.palletIds||[]).filter(id=>activePallets().some(p=>p.id===id)));render();return;}
if(b.dataset.unblock){await action(`/api/blocks/${encodeURIComponent(b.dataset.unblock)}/unblock`,{reason:"Operator verified the outage has ended"},{message:"The capacity block was removed with a recorded adjustment."});return;}
switch(b.dataset.action){
case"dismiss":S.notice=null;S.retry=null;render();break;
case"retry":if(S.retry)await S.retry();break;
case"refresh":await view(S.tab);break;
case"invoice-refresh":await view("charges");break;
case"switch":{
await api("/api/logout",{method:"POST",body:{}});S.user=null;S.data=null;S.csrfToken=null;S.quote=null;S.hold=null;S.capacity=null;S.invoice=null;S.selectedReservation=null;S.selectedPallets.clear();S.notice=null;S.tab="capacity";render();break;
}
case"hold":await action("/api/holds",{quoteId:S.quote.id},{message:"Compatible positions are temporarily held. Confirm before the hold expires.",after:r=>{S.hold=r;}});break;
case"confirm":await action(`/api/holds/${encodeURIComponent(S.hold.id)}/confirm`,{},{idempotent:true,message:"Reservation confirmed. The accepted tariff is now frozen.",after:r=>{S.selectedReservation=r.id||r.reservation?.id;S.hold.status="confirmed";S.quote.status="confirmed";}});break;
case"select-two":S.selectedPallets=new Set(activePallets().slice(0,2).map(p=>p.id));render();break;
case"select-all":S.selectedPallets=new Set(activePallets().map(p=>p.id));render();break;
case"close-full":await action(`/api/reservations/${encodeURIComponent(S.selectedReservation)}/close-receiving`,{disposition:"release",reason:"All expected pallets received; receiving complete"},{message:"Receiving is closed. Goods remain in custody until release."});break;
case"audit":S.busy=true;render();try{S.audit=await api(`/api/audit?reservationId=${encodeURIComponent(S.selectedReservation)}`);}finally{S.busy=false;}render();break;
}
}catch(e){S.busy=false;fail(e);render();}
});
document.addEventListener("submit",async event=>{
const form=event.target;if(!form.id)return;event.preventDefault();if(S.busy)return;
const data=Object.fromEntries(new FormData(form));
try {
switch(form.id){
case"login-form":{
S.loginEmail=data.email;S.busy=true;S.notice=null;render();
try{const login=await api("/api/login",{method:"POST",body:{email:data.email,password:data.password}});S.user=login.user;S.csrfToken=login.csrfToken;S.now=login.now;await refresh();}catch(e){fail(e);}finally{S.busy=false;render();}break;
}
case"capacity-form":S.range={...S.range,...data};await view("capacity");break;
case"quote-form":{
S.booking={...S.booking,...data};
await action("/api/quotes",{warehouseId:data.warehouseId,zoneId:data.zoneId,quantity:Number(data.quantity),startAt:localISO(data.startAt),endAt:localISO(data.endAt),pallet:{lengthMm:1219,widthMm:1016,heightMm:Number(data.heightMm),weightKg:Number(data.weightKg),condition:"ambient",goods:"packaged_dry"}},{message:"The itemized quote is ready. It does not consume capacity until you place a hold.",after:r=>{S.quote=r;S.hold=null;}});break;
}
case"clock-form":await action("/api/demo/clock",{now:localISO(data.now)},{message:"The presentation clock advanced. Actual goods remain in custody until release."});break;
case"receive-form":{
const pallets=data.labels.split("\n").map(s=>s.trim()).filter(Boolean).map(line=>{const[label,externalId]=line.split(",").map(v=>v.trim());return {label,...(externalId?{externalId}:{}),...(data.heightMm?{heightMm:Number(data.heightMm)}:{}),...(data.weightKg?{weightKg:Number(data.weightKg)}:{})};});
if(!pallets.length)throw new Error("Enter at least one pallet label.");
await action(`/api/reservations/${encodeURIComponent(S.selectedReservation)}/receive`,{pallets,reason:data.reason,approveVariance:data.approveVariance==="on",...(data.expectedEndAt?{expectedEndAt:localISO(data.expectedEndAt)}:{})},{idempotent:true,message:`${pallets.length} pallets received. Linked occupancy replaced their unfulfilled commitment.`,after:()=>{S.receivingDrafts[S.selectedReservation].labels="";}});break;
}
case"close-receiving-form":await action(`/api/reservations/${encodeURIComponent(S.selectedReservation)}/close-receiving`,{disposition:data.disposition,reason:data.reason},{message:data.disposition==="release"?"Receiving closed. Unused positions were released; received pallets remain in custody.":"Receiving closed. The unreceived commitment is retained until its end."});break;
case"release-form":{
const palletIds=[...S.selectedPallets];if(!palletIds.length)throw new Error("Select at least one pallet.");
await action(`/api/reservations/${encodeURIComponent(S.selectedReservation)}/${operator()?"release":"release-requests"}`,{palletIds,reason:data.reason,...(operator()&&S.releaseRequestId?{releaseRequestId:S.releaseRequestId}:{})},{idempotent:operator(),message:operator()?`${palletIds.length} pallets released. Only their physical positions were freed.`:"Release requested. The operator will record the actual departure.",after:()=>{S.selectedPallets.clear();S.releaseRequestId=null;}});break;
}
case"adjustment-form":{
const amount=String(data.amount||"0").trim();
if(!/^-?\d+(?:\.\d{1,2})?$/.test(amount))throw new Error("Enter a signed USD amount with at most two decimal places, such as -4.00.");
const negative=amount.startsWith("-"),[whole,fraction=""]=amount.replace("-","").split(".");
const amountCents=(Number(whole)*100+Number(fraction.padEnd(2,"0")))*(negative?-1:1);
if(!Number.isSafeInteger(amountCents))throw new Error("The adjustment amount is too large.");
await action(`/api/movements/${encodeURIComponent(data.movementId)}/adjustments`,{reason:data.reason,note:data.note,amountCents},{idempotent:true,message:"A linked correction was appended. The original movement remains unchanged."});break;
}
case"relocate-form":await action(`/api/pallets/${encodeURIComponent(data.palletId)}/relocate`,{positionId:data.positionId,reason:data.reason},{message:"Pallet relocated after protected compatibility and capacity checks."});break;
case"amend-form":await action(`/api/reservations/${encodeURIComponent(S.selectedReservation)}/amend`,{startAt:data.startAt===atLocal(selected().startAt)?selected().startAt:localISO(data.startAt),endAt:localISO(data.endAt),quantity:Number(data.quantity),reason:data.reason},{message:"The revised commitment is saved. Its accepted tariff remains fixed."});break;
case"end-form":await action(`/api/reservations/${encodeURIComponent(S.selectedReservation)}/${event.submitter?.value||"cancel"}`,{reason:data.reason},{message:"The remaining commitment ended. Goods still present remain visible in custody."});break;
case"block-form":{
const positionIds=[...form.elements.positionIds.selectedOptions].map(o=>o.value);if(!positionIds.length)throw new Error("Select at least one position to block.");
await action("/api/blocks",{warehouseId:S.range.warehouseId,positionIds,startAt:localISO(data.startAt),endAt:localISO(data.endAt),reason:data.reason},{message:"The outage is recorded. Any affected promises are shown as exceptions."});break;
}
}
}catch(e){S.busy=false;fail(e);render();}
});
async function boot() {
try {
const session=await api("/api/session");S.user=session.user;S.csrfToken=session.csrfToken;S.now=session.now;
if(S.user)await refresh();
} catch(e){if(e.status!==401)fail(e,boot);}
render();
}
await boot();