/* Exported Promotions workspace, backed by recorded promotions and sales. */ (function () { const {useState,useEffect} = React; const cash = n => n == null ? '—' : '$' + (n/100).toLocaleString('en-AU',{minimumFractionDigits:2,maximumFractionDigits:2}); const pct = n => n == null ? '—' : (n>0?'+':'') + Number(n).toFixed(1) + '%'; const date = s => s ? String(s).slice(0,10).split('-').reverse().join('/') : '—'; const days = (a,b) => Math.round((Date.parse(b)-Date.parse(a))/86400000); const addDays = (s,n) => new Date(Date.parse(s+'T00:00:00Z')+n*86400000).toISOString().slice(0,10); const I = props => ; const K = props => ; const Note = ({children}) =>
{children}
; const Empty = ({children}) =>
{children}
; const State = ({value}) => {const tones={live:'paid',scheduled:'brand',draft:'hold',paused:'void',ended:'flat',cancelled:'flat'};return {value ? value[0].toUpperCase()+value.slice(1) : 'Unknown'};}; const Where = ({where}) => {where==='basket'?'Register rule':'Shelf price'}; function Card({title,sub,children,className=''}) {return
{title}
{sub&&
{sub}
}{children}
;} function Table({heads,columns,children}) {return
{heads.map((h,i)=>
{h}
)}{children}
;} const Cell = ({children,num=false}) =>
{children}
; function All({d,open,create}) { const [filter,setFilter]=useState('live'); const all=d.promotions||[], live=all.filter(p=>p.state==='live'), funded=all.filter(p=>p.funded); const filters=[['live','Live',p=>p.state==='live'],['scheduled','Scheduled',p=>p.state==='scheduled'],['paused','Paused',p=>p.state==='paused'],['draft','Draft',p=>p.state==='draft'],['ended','Ended',p=>['ended','cancelled'].includes(p.state)],['basket','Register rules',p=>p.where==='basket'],['funded','Supplier funded',p=>!!p.funded],['all','Everything',()=>true]]; const shown=all.filter(filters.find(f=>f[0]===filter)[2]); const start=addDays(d.today,-7), visible=all.filter(p=>!['draft','cancelled'].includes(p.state)&&days(start,p.endsOn)>=0&&days(start,p.startsOn)<42); return <>
p.where==='basket').length+' are register rules, not shelf prices'}}/>days(d.today,p.endsOn)<=7).length} delta={{text:'The recorded base price resumes when the offer ends'}}/>
Six weeks out
Dates and overlaps across recorded promotions. Open a bar to inspect its offer.
{Array.from({length:6},(_,i)=>
{date(addDays(start,i*7)).slice(0,5)}
)}
today
{visible.map(p=>{const a=Math.max(0,days(start,p.startsOn)),b=Math.min(42,days(start,p.endsOn)+1);return
;})}{!visible.length&&Nothing scheduled or live in this window.}
{filters.map(([id,label,pred])=>)}
{shown.map(p=>
open(p)}>{p.name}{p.funded&&funded}{p.id}{p.mechanicLabel}{p.say}{p.scopeKind==='all'?'Group':p.scopeKind}{p.scopeLabel}{p.siteName||d.siteName||'Organisation'}{date(p.startsOn)} – {date(p.endsOn)}{p.state==='live'&&{days(d.today,p.endsOn)} days left}
)}
{!shown.length&&{all.length?'Nothing matches that filter.':'No promotions yet. Create a draft to begin.'}}
; } function Register({d,open}) { const [lines,setLines]=useState([]),[options,setOptions]=useState([]),[query,setQuery]=useState(''),[result,setResult]=useState(null),[error,setError]=useState(null),[busy,setBusy]=useState(false); useEffect(()=>{let alive=true;window.OfficeAPI.promotionScopeOptions().then(o=>{if(alive)setOptions(o.products||[]);}).catch(e=>{if(alive)setError(e.message);});return()=>{alive=false;};},[]); useEffect(()=>{let alive=true;if(!lines.length){setResult(null);setBusy(false);return;}setBusy(true);setResult(null);const t=setTimeout(()=>window.OfficeAPI.simulatePromotions({siteId:d.siteId,lines}).then(x=>{if(alive){setResult(x);setError(null);}}).catch(e=>{if(alive)setError(e.message);}).finally(()=>{if(alive)setBusy(false);}),150);return()=>{alive=false;clearTimeout(t);};},[JSON.stringify(lines),d.siteId]); const live=(d.promotions||[]).filter(p=>p.state==='live'),prices=live.filter(p=>p.where==='price'),rules=live.filter(p=>p.where==='basket'); const add=id=>{setLines(prev=>prev.some(l=>l.productId===id)?prev.map(l=>l.productId===id?{...l,qty:l.qty+1}:l):[...prev,{productId:id,qty:1}]);setQuery('');}; const change=(id,delta)=>setLines(prev=>prev.map(l=>l.productId===id?{...l,qty:l.qty+delta}:l).filter(l=>l.qty>0)); return <>Price offers are stored as promotion prices; supported basket offers are stored as rules. This view reads the publication records and simulates the supported register calculations.
{prices.map(p=>
{p.lineCount} lines · {p.say}
)}{!prices.length&&No live price promotions.}
{rules.map(p=>
{p.say}
{p.prompt&&
{p.prompt}
}
)}{!rules.length&&No live basket rules.}
{d.till?.note||'Publication records are available; individual register delivery acknowledgements are not.'}
{live.map(p=>
{p.name}{['esl','ticket','cfd','online','cat','prompt'].map(c=>)}
)}
Selected channels describe intent; they do not prove delivery.
{query.trim()&&
{options.filter(p=>(p.name+' '+(p.sku||'')).toLowerCase().includes(query.toLowerCase())).slice(0,8).map(p=>)}
} {error&&{error}}{busy&&
Evaluating the current basket…
}
{lines.map(l=>{const r=result?.lines?.find(x=>x.productId===l.productId),p=options.find(x=>x.id===l.productId);return
{l.qty}
{r?.name||p?.name||l.productId}
{r?cash(r.unitCents)+' each':'Awaiting calculation'}
{busy?'…':cash(r?.totalCents)}
;})}{!lines.length&&Add a product to start a basket.}
{(result?.appliedRules||[]).map((r,i)=>
{r.name||r.description||'Basket rule'}
{r.note||r.ruleId}
{cash(r.discountCents??r.savedCents)}
)}
Before promotions{cash(result?.subtotalCents)}
Saved{cash(result?.discountCents)}
To pay{cash(result?.totalCents)}
{(result?.limitations||[]).map((n,i)=>
{n}
)}
; } function Overlaps({d,open}) { const c=d.conflicts||{},s=c.counts||{},rows=c.rows||[]; return <>
{c.policy||'Overlaps use the current publication model.'}
{rows.map((r,i)=>
{(r.candidates||r.promotions||[]).map(p=>
{p.where==='basket'?'basket':'snapshot'}
{p.say||p.where}
{cash(p.priceCents??p.promoCents)}
)}
Current stored unit price: {cash(r.effectiveCents)}{r.belowCost ? " · below recorded cost floor " + cash(r.floorCents) : ""}. {(r.trace||[]).join(" ")}{r.note||""}
)}
{!rows.length&&No overlapping publication records in this scope.}; } function Group({d,open}) { const [tab,setTab]=useState('stores'),g=d.group||{},sites=g.sites||[]; const tabs=[['stores','By store'],['diverge','Price divergence'],['risk','Funding at risk'],['patchy','Uneven coverage']]; const active=(d.promotions||[]).filter(p=>['live','scheduled'].includes(p.state)); return <>
p.state==='live').length} delta={{text:sites.length+' authorised stores'}}/>
{g.note||'Promotion ownership and the price-delivery scope are shown separately.'}
{tabs.map(([k,l])=>)}
{tab==='stores'?{sites.map(s=>
{s.siteName}{s.liveCount??0}{s.scheduledCount??0}{s.fundedCount??0}Promotion owner · price profiles apply across the organisation
)}
:tab==='patchy'?<>{active.map(p=>
{p.name}{p.siteName||p.siteId}
)}
{!active.length&&No live or scheduled promotions.}:{tab==='diverge'?'Independent store promotion prices are not recorded by the current organisation-wide profile model.':'Recorded sales do not identify which funded promotion won, so realised funding loss cannot be calculated.'}}
; } function Performance({d,open}) { const p=d.performance||{},rows=p.rows||[],s=p.summary||{},measured=rows.filter(r=>r.gpDeltaCents!=null),best=measured.slice().sort((a,b)=>b.gpDeltaCents-a.gpDeltaCents)[0]; return <>
r.gpDeltaCents>0).length+' of '+measured.length:'—'} delta={{text:'Measured scope GP versus the stated baseline'}}/>
{p.basisNote}
Against trailing eight weeks
{rows.map(r=>
{date(r.period?.from)} – {date(r.period?.to)}{r.scopeUnits??'—'}{r.baselineUnits==null?'—':Number(r.baselineUnits).toFixed(1)}{pct(r.liftPct)}{cash(r.recordedGpCents)}{cash(r.gpDeltaCents)}{r.note||'Sales in scope during the offer; not verified promotion redemptions.'}
)}
{!rows.length&&No promotion period has recorded sales to compare yet.}
; } function Funding({d,refresh}) { const f=d.funding||{},s=f.summary||{},rows=f.rows||[]; const [selected,setSelected]=useState(null),[status,setStatus]=useState('open'),[amount,setAmount]=useState(''),[units,setUnits]=useState(''),[reference,setReference]=useState(''),[note,setNote]=useState(''),[busy,setBusy]=useState(false),[error,setError]=useState(null); const edit=r=>{setSelected(r);setStatus(r.record?.status||'open');setAmount(r.record?.amountCents==null?'':String(r.record.amountCents/100));setUnits(r.record?.units==null?'':String(r.record.units));setReference(r.record?.reference||'');setNote(r.record?.note||'');setError(null);}; const save=async()=>{if(!amount.trim()||!Number.isFinite(Number(amount))||Number(amount)<0){setError('Enter the documented claim amount.');return;}if(!note.trim()){setError('Add a note identifying the supporting evidence.');return;}setBusy(true);setError(null);try{await window.OfficeAPI.savePromotionFundingRecord(selected.promotionId,{revision:selected.record?.revision??selected.revision??'default',status,amountCents:Math.round(Number(amount)*100),units:units===''?null:Number(units),reference:reference||null,note:note||null});setSelected(null);refresh();}catch(e){setError(e.message);}finally{setBusy(false);}}; return <>
{f.basisNote}
Co-op deals
{rows.map(r=>
{r.name||d.promotions.find(p=>p.id===r.promotionId)?.name}{r.supplier}{r.ref||'—'}{cash(r.perCents)}{r.scopeUnits??'—'}{cash(r.record?.amountCents)}{r.record?.status||'No claim recorded'}
)}
{!rows.length&&No supplier-funded promotions in this scope.}
{selected&&

Record supplier claim

This records a claim you have documented. It does not send a claim or create a supplier credit.