/* ============================================================= CoreIQ Office — Promotions (LIVE · this pharmacy) Author here; the register executes. Publishing writes to the two tables the till already reads — a promo price per product (pricing_profiles) or one multibuy rule plus a pricing group (promotion_rules) — and PowerSync carries them to the registers within seconds. A draft changes nothing. Ported from the design's Promotions shell + builder + detail, restricted to the five mechanics the till can execute today. Threshold, second-unit, gift-with-purchase and member price arrive with the till release. Registers window.OFFICE_SCREENS.promotions / .promotionNew / .promotionDetail ============================================================= */ const { useState: pmState, useEffect: pmEffect, useMemo: pmMemo } = React; const pmM = (c) => (c == null ? "—" : "$" + (Number(c) / 100).toLocaleString("en-AU", { minimumFractionDigits: 2, maximumFractionDigits: 2 })); const pmSigned = (c) => (c == null ? "—" : (c > 0 ? "+" : c < 0 ? "−" : "") + pmM(Math.abs(c))); const pmPct = (v) => (v == null ? "—" : Number(v).toFixed(1) + "%"); const pmDmy = (iso) => (iso ? iso.slice(8, 10) + "/" + iso.slice(5, 7) + "/" + iso.slice(0, 4) : "—"); const pmAddDays = (iso, d) => new Date(Date.parse(iso + "T00:00:00Z") + d * 86400000).toISOString().slice(0, 10); const pmDays = (a, b) => Math.round((Date.parse(b) - Date.parse(a)) / 86400000); const PM_STATE = { draft: { l: "Draft", bg: "var(--bg-subtle)", fg: "var(--text-subtle)", bd: "var(--line)" }, scheduled: { l: "Scheduled", bg: "var(--navy-100)", fg: "var(--navy-800)", bd: "var(--navy-200)" }, live: { l: "Live", bg: "var(--paid-bg)", fg: "var(--paid-text)", bd: "var(--paid)" }, paused: { l: "Paused", bg: "var(--hold-bg)", fg: "var(--hold-text)", bd: "var(--hold)" }, ended: { l: "Ended", bg: "var(--bg-subtle)", fg: "var(--text-subtle)", bd: "var(--line)" }, cancelled: { l: "Cancelled", bg: "var(--bg-subtle)", fg: "var(--text-subtle)", bd: "var(--line)" }, }; const PM_ACTION = { publish: { l: "Publish", primary: true, confirm: "Publish this promotion? It reaches the registers within seconds." }, pause: { l: "Pause", confirm: "Pause it? The registers stop applying it until you resume." }, resume: { l: "Resume" }, endEarly: { l: "End early", danger: true, confirm: "End it now? Prices revert on the registers and it cannot be resumed." }, extend: { l: "Extend" }, cancel: { l: "Cancel", danger: true, confirm: "Cancel it? It will not start." }, startNow: { l: "Start now", confirm: "Start today instead of the scheduled date?" }, rerun: { l: "Run it again" }, edit: { l: "Edit" }, discard: { l: "Discard", danger: true, confirm: "Discard this draft?" }, }; const PM_API_ACTION = { publish: "publish", pause: "pause", resume: "resume", endEarly: "end", cancel: "cancel", startNow: "start-now", rerun: "rerun" }; function PmStateTag({ state }) { const s = PM_STATE[state] || PM_STATE.draft; return {s.l}; } function PmErr({ text }) { return text ?
{text}
: null; } // Runs a lifecycle action with a confirm where the design asks for one. async function pmRunAction(id, key, extra, pushToast, onDone) { const a = PM_ACTION[key]; if (a && a.confirm && !window.confirm(a.confirm)) return; try { const r = await window.OfficeAPI.promotionAction(id, PM_API_ACTION[key] || key, extra); pushToast && pushToast({ kind: "ok", text: (a ? a.l : key) + " — done." }); onDone && onDone(r); } catch (e) { pushToast && pushToast({ kind: "error", text: e && e.message ? e.message : "That didn't work." }); } } // ================================================================= // LIST + CALENDAR // ================================================================= function PmCalendar({ promotions, today }) { const start = pmAddDays(today, -7); const DAYS = 42; const rows = promotions.filter((p) => p.state !== "draft" && p.state !== "cancelled" && pmDays(start, p.endsOn) >= 0 && pmDays(start, p.startsOn) < DAYS); const weeks = Array.from({ length: 6 }, (_, i) => pmAddDays(start, i * 7)); return (
Six weeks out
Where two bars overlap the register gives the customer whichever is better — that is the point of this view.
{weeks.map((w) =>
{pmDmy(w).slice(0, 5)}
)}
{rows.length === 0 &&
Nothing scheduled or live in this window.
} {rows.map((p) => { const s = Math.max(0, pmDays(start, p.startsOn)), e = Math.min(DAYS, pmDays(start, p.endsOn) + 1); const st = PM_STATE[p.state]; return (
{p.name}
{p.say} · {p.scopeLabel}
{p.where === "basket" ? "register rule" : "shelf price"}
); })}
); } function Promotions() { const { setScreen, setScreenState, pushToast } = window.useOffice(); const Kpi = window.KpiCard; const [data, setData] = pmState(null); const [err, setErr] = pmState(null); const [loading, setLoading] = pmState(true); const [tab, setTab] = pmState("active"); const [reloadKey, setReloadKey] = pmState(0); pmEffect(() => { let alive = true; setLoading(true); setErr(null); window.OfficeAPI.promotions().then((d) => { if (alive) setData(d); }).catch((e) => { if (alive) setErr(e && e.message ? e.message : "Couldn't load promotions."); }).finally(() => { if (alive) setLoading(false); }); return () => { alive = false; }; }, [reloadKey]); const all = (data && data.promotions) || []; const counts = (data && data.counts) || {}; const TABS = [ { k: "active", label: "Live & scheduled", n: (counts.live || 0) + (counts.scheduled || 0) + (counts.paused || 0) }, { k: "draft", label: "Drafts", n: counts.draft || 0 }, { k: "past", label: "Ended", n: (counts.ended || 0) + (counts.cancelled || 0) }, { k: "all", label: "All", n: all.length }, ]; const rows = all.filter((p) => tab === "all" || (tab === "active" ? ["live", "scheduled", "paused"].includes(p.state) : tab === "draft" ? p.state === "draft" : ["ended", "cancelled"].includes(p.state))); const open = (p) => { setScreenState((st) => ({ ...st, promotionId: p.id })); setScreen("promotionDetail"); }; const create = () => { setScreenState((st) => ({ ...st, promotionEditId: null, promotionRerunOf: null })); setScreen("promotionNew"); }; const live = all.filter((p) => p.state === "live"); const endingSoon = live.filter((p) => data && pmDays(data.today, p.endsOn) <= 7).length; return (
live · this pharmacy · writes to the registers

Promotions

{data && Kpi && (
p.where === "basket").length + " are register rules, not shelf prices" }} /> p.funded && ["live", "scheduled", "paused"].includes(p.state)).length)} delta={{ text: "co-op claims to raise" }} />
)} {data &&
}
{TABS.map((t) => )}
{!loading && rows.length === 0 && (
{all.length === 0 ? "No promotions yet. The first one you publish reaches the registers within seconds." : "Nothing in this tab."}
)} {rows.length > 0 && (
Promotion
Offer
Scope
Dates
Lines
State
{rows.map((p) => (
open(p)}>
{p.name} {p.mechanicLabel}{p.where === "basket" ? " · register rule" : " · shelf price"}{p.funded ? " · funded by " + p.funded.supplier : ""}
{p.say}
{p.scopeLabel}
{pmDmy(p.startsOn)} → {pmDmy(p.endsOn)}
{p.lineCount}
))}
)}
); } // ================================================================= // BUILDER — the design's New promotion, on real data. // Every section, its copy and its order come from the design prototype // (office-screens-promotions-4.jsx). Numbers on the right come from // /promotions/preview, which runs the design's planning maths server-side // against the stock ledger, eight weeks of sales and observed competitor // prices. Nothing reaches a register until you publish. // ================================================================= const pmSay = (id, p) => { const M = (c) => pmM(c); switch (id) { case "pct": return (p.pct || 0) + "% off"; case "amount": return M(p.offCents || 0) + " off"; case "fixed": return M(p.priceCents || 0); case "member": return M(p.priceCents || 0) + " members"; case "multibuy": return (p.qty || 0) + " for " + M(p.bundleCents || 0); case "mixmatch": return "any " + (p.qty || 0) + " for " + M(p.bundleCents || 0); case "threshold": return M(p.offCents || 0) + " off " + M(p.spendCents || 0); case "bundle": return "buy one, second line " + (p.bPct || 0) + "% off"; case "secondhalf": return "2nd unit " + (p.pct || 50) + "% off"; case "gwp": return ("free " + (p.gift || "")).trim(); default: return ""; } }; const pmShort = (name) => String(name || "").split(" ").slice(0, 3).join(" "); // One ticket — real artwork at real size. Ported from the design's shelf-ticket screen. function PgTicket({ name, sku, beforeCents, priceCents, say, startsOn, endsOn, theme, themes, isPrice, member, clearance }) { const t = (themes && themes[theme]) || (themes && themes.red) || { flag: "#d81f2a", fg: "#fff", ink: "#b3141d", tint: "#fdecec" }; const showPrice = isPrice && priceCents != null; const save = showPrice ? beforeCents - priceCents : null; const dollars = showPrice ? Math.floor(priceCents / 100) : ""; const cents = showPrice ? String(priceCents % 100).padStart(2, "0") : ""; return (
{clearance ? "CLEARANCE" : member ? "MEMBER PRICE" : !showPrice ? "OFFER" : "SPECIAL"} {save != null && save > 0 && SAVE ${(save / 100).toFixed(2)}}
{name}
{showPrice ? (
${dollars}{cents}
) : (
{String(say || "").toUpperCase()}
)}
{showPrice ? "Usually " + pmM(beforeCents) : pmM(beforeCents) + " each"} {pmDmy(startsOn)} – {pmDmy(endsOn)}
{member ? "Loyalty card must be presented. " : ""}{clearance ? "Short-dated stock. " : ""}While stocks last.{sku}
); } function PromotionNew() { const { screenState, setScreen, setScreenState, pushToast, stores: orgStores } = window.useOffice(); const editId = screenState.promotionEditId || null; const rerunOf = screenState.promotionRerunOf || null; const OIcon = window.OIcon; const [meta, setMeta] = pmState(null); const [opts, setOpts] = pmState(null); const [today, setToday] = pmState(null); const [name, setName] = pmState(""); const [mech, setMech] = pmState("pct"); const [p, setP] = pmState({ pct: 20, offCents: 500, priceCents: 999, qty: 3, bundleCents: 3000, spendCents: 5000, bPct: 25, b: "", gift: "" }); const [level, setLevel] = pmState("category"); const [key, setKey] = pmState(""); const [stores, setStores] = pmState([]); const [from, setFrom] = pmState(""); const [to, setTo] = pmState(""); const [channels, setChannels] = pmState(["esl", "ticket"]); const [funded, setFunded] = pmState(false); const [supplier, setSupplier] = pmState(""); const [perCents, setPerCents] = pmState(150); const [prompt, setPrompt] = pmState(""); const [reason, setReason] = pmState(""); const [theme, setTheme] = pmState("red"); const [excl, setExcl] = pmState([]); const [preview, setPreview] = pmState(null); const [previewErr, setPreviewErr] = pmState(null); const [busy, setBusy] = pmState(false); const [loaded, setLoaded] = pmState(!editId && !rerunOf); pmEffect(() => { let alive = true; window.OfficeAPI.promotionsMeta().then((m) => { if (alive) setMeta(m); }).catch(() => {}); window.OfficeAPI.promotionScopeOptions().then((o) => { if (alive) setOpts(o); }).catch((e) => { if (alive) setPreviewErr(e && e.message ? e.message : "Couldn't load the scope options."); }); window.OfficeAPI.promotions().then((d) => { if (!alive) return; setToday(d.today); if (!editId && !rerunOf) { setFrom(d.today); setTo(pmAddDays(d.today, 13)); } }).catch(() => {}); const src = editId || rerunOf; if (src) window.OfficeAPI.promotion(src).then((d) => { if (!alive) return; setName(d.name); setMech(d.mechanic); setP((s) => ({ ...s, ...d.params })); setLevel(d.scopeKind === "all" ? "group" : d.scopeKind); setKey(d.scopeRef || ""); setChannels((d.channels || []).filter((c) => c !== "pos")); setExcl(d.exclusions || []); setPrompt(d.prompt || ""); setReason(d.why || ""); setTheme(d.ticketTheme || "red"); if (d.funded) { setFunded(true); setSupplier(d.funded.supplier); setPerCents(d.funded.perCents); } if (editId) { setFrom(d.startsOn); setTo(d.endsOn); } else { const days = pmDays(d.startsOn, d.endsOn); window.OfficeAPI.promotions().then((x) => { if (alive) { setFrom(x.today); setTo(pmAddDays(x.today, days)); } }).catch(() => {}); } setLoaded(true); }).catch((e) => setPreviewErr(e.message)); return () => { alive = false; }; }, [editId, rerunOf]); // one store today; the chip row is the design's, the list is real pmEffect(() => { if (orgStores && orgStores.length && stores.length === 0) setStores([orgStores[0].id]); }, [orgStores]); const M = (meta && meta.mechanics) || []; const m = M.find((x) => x.id === mech) || { id: mech, where: "price", label: mech, d: "" }; const isPrice = m.where === "price"; const params = mech === "pct" ? { pct: Number(p.pct) } : mech === "amount" ? { offCents: Number(p.offCents) } : (mech === "fixed" || mech === "member") ? { priceCents: Number(p.priceCents) } : (mech === "multibuy" || mech === "mixmatch") ? { qty: Number(p.qty), bundleCents: Number(p.bundleCents) } : mech === "threshold" ? { spendCents: Number(p.spendCents), offCents: Number(p.offCents) } : mech === "secondhalf" ? { pct: Number(p.pct) || 50 } : mech === "bundle" ? { bPct: Number(p.bPct), b: p.b } : { gift: p.gift, qty: Number(p.qty) }; const scopeKind = level === "group" ? "all" : level; const input = { name: name || "Untitled promotion", mechanic: mech, params, scopeKind, scopeRef: scopeKind === "all" ? null : key || null, exclusions: excl, startsOn: from, endsOn: to, why: reason || null, prompt: channels.includes("prompt") ? prompt || null : null, channels: ["pos", ...channels], ticketTheme: theme, funded: funded && supplier ? { supplier, perCents: Number(perCents) || 0 } : null, siteId: stores[0] || null }; const scoped = scopeKind === "all" || !!key; const ready0 = !!from && !!to && scoped && loaded; pmEffect(() => { if (!ready0) { setPreview(null); return; } let alive = true; const t = setTimeout(() => { window.OfficeAPI.previewPromotion(input).then((pv) => { if (alive) { setPreview(pv); setPreviewErr(null); } }).catch((e) => { if (alive) { setPreview(null); setPreviewErr(e && e.message ? e.message : "Couldn't preview."); } }); }, 250); return () => { alive = false; clearTimeout(t); }; }, [JSON.stringify(input), ready0]); const lines = (preview && preview.lines) || []; const affected = lines.filter((l) => !l.held); const held = lines.filter((l) => l.held); const sum = preview && preview.summary; const breaches = affected.filter((l) => l.under); const overlaps = affected.filter((l) => l.existing && l.existing.length); const stock = affected.filter((l) => l.stock); const shortL = stock.filter((l) => l.stock.short); const heavy = stock.filter((l) => l.stock.heavy); const comp = affected.filter((l) => l.landing); const overshot = comp.filter((l) => l.landing.overshootCents > 5).sort((a, b) => b.landing.overshootCents - a.landing.overshootCents); const stillLosing = comp.filter((l) => l.promoCents != null && l.landing.cheapest && l.promoCents > l.landing.cheapest.priceCents).sort((a, b) => (b.promoCents - b.landing.cheapest.priceCents) - (a.promoCents - a.landing.cheapest.priceCents)); const roll = (sum && sum.roll) || { wins: 0, losses: 0, noData: 0, over: 0, total: 0 }; const durDays = sum ? sum.days : 0; const storeName = (sum && sum.storeName) || (orgStores && orgStores[0] && orgStores[0].name) || "this store"; const ready = name.trim().length > 2 && stores.length > 0 && affected.length > 0 && !(preview && preview.problem) && (!breaches.length || reason.trim().length > 3) && m.available !== false; const save = async (asDraft) => { setBusy(true); try { const body = { ...input, name: name.trim() }; const d = editId ? await window.OfficeAPI.updatePromotion(editId, body) : await window.OfficeAPI.createPromotion(body); if (!asDraft) { if (!window.confirm("Publish \"" + name.trim() + "\"? " + (isPrice ? "It pushes " + affected.length + " price" + (affected.length === 1 ? "" : "s") + " to the registers within seconds." : "The rule reaches every till within seconds."))) { setBusy(false); setScreenState((st) => ({ ...st, promotionId: d.id, promotionEditId: null, promotionRerunOf: null })); setScreen("promotionDetail"); return; } await window.OfficeAPI.promotionAction(d.id, "publish"); } pushToast({ kind: asDraft ? "hold" : "ok", text: asDraft ? "Saved as draft · " + name.trim() + " — not visible at any register until you publish it." : (editId ? "Changes saved · " : "Promotion created · ") + name.trim() + " · " + affected.length + " line" + (affected.length === 1 ? "" : "s") + " · " + (isPrice ? "prices reach the registers within seconds" : "rule cached on every till") }); setScreenState((st) => ({ ...st, promotionId: d.id, promotionEditId: null, promotionRerunOf: null })); setScreen(asDraft ? "promotionDetail" : "promotions"); } catch (e) { pushToast({ kind: "error", text: e && e.message ? e.message : "Couldn't save." }); } finally { setBusy(false); } }; const Chip = ({ on, onClick, children, title }) => ; const setKeyFor = (l) => { setLevel(l); setKey(""); }; const cats = (opts && opts.categories) || [], brands = (opts && opts.brands) || [], prods = (opts && opts.products) || []; const setsAll = (meta && meta.exclusionSets) || []; const chans = ((meta && meta.channels) || []).filter((c) => !c.always); const themes = (meta && meta.ticketThemes) || {}; const heldCountFor = (id) => held.filter((h) => h.held.set === id).length; const cents = (v, set, step) => set(Math.round(Number(e.target.value || 0) * 100))} />; const set = (k, v) => setP((s) => ({ ...s, [k]: v })); return (

{editId ? "Edit promotion" : rerunOf ? "Run it again" : "New promotion"}

{editId ? "Changes take effect at the next register sync. Shelf tickets will need reprinting if the price moves." : rerunOf ? "Copied from the earlier run. Give it new dates — everything else carried over." : "The panel on the right updates as you build. Nothing reaches a register until you publish."}
{/* ---------------- form ---------------- */}
Name it
setName(e.target.value)} />
This is what prints on the ticket and shows on the receipt, so write it the way a customer would read it.
Mechanic
Price mechanics resolve to a number the register receives. Register rules are evaluated at the till against the whole basket.
{M.map((x) => ( ))}
{mech === "pct" && } {mech === "amount" && } {(mech === "fixed" || mech === "member") && } {(mech === "multibuy" || mech === "mixmatch") && <>} {mech === "threshold" && <>} {mech === "secondhalf" && } {mech === "bundle" && <>} {mech === "gwp" && <>}
Reads as {pmSay(mech, params)}
What it applies to
Same hierarchy as pricing rules. A category promotion covers every line in it, now and any added later.
{[["group", "Everything"], ["category", "Category"], ["brand", "Brand"], ["sku", "Single line"]].filter(([l]) => mech !== "multibuy" || l === "sku").map(([l, lab]) => setKeyFor(l)}>{lab})}
{level === "category" && } {level === "brand" && } {level === "sku" && } {level === "group" &&
Every front-of-shop line. Prescriptions, S8 and consignment stock are excluded automatically.
}
Exclusions
Applied before anything else. The locked sets are not ours to discount, so they are held out of every promotion whatever its scope.
{setsAll.map((s) => { const on = s.mandatory || excl.includes(s.id); const caught = heldCountFor(s.id); return (
{ if (!s.mandatory) setExcl((v) => v.includes(s.id) ? v.filter((x) => x !== s.id) : [...v, s.id]); }}>
{s.label}{s.mandatory && always}
{s.why}
{caught > 0 && {caught} in scope}
); })}
{held.length > 0 &&
Holding back {held.length} line{held.length === 1 ? "" : "s"}: {held.slice(0, 6).map((h) => pmShort(h.name)).join(", ")}{held.length > 6 ? ", …" : ""}.
}
Where and when
{(orgStores || []).map((s) => setStores([s.id])}>{s.name})}
Ends at close of trade on the last day. The price then returns to whatever the pricing engine says at that point, not to today's price.
Where it shows
{}}>Register & receipt {chans.map((c) => setChannels((s) => s.includes(c.id) ? s.filter((x) => x !== c.id) : [...s, c.id])}>{c.label})}
{channels.includes("prompt") && <> setPrompt(e.target.value)} />}
Supplier funding
{funded &&
}
{/* ---------------- consequences ---------------- */}
What this will do
Lines affected{affected.length}
Stores{stores.length || "—"}
Avg margin after{sum && sum.avgMarginAfterPct != null ? Math.round(sum.avgMarginAfterPct) + "%" : isPrice ? "—" : "at till"}
Given up per unit{isPrice ? (sum && sum.givenUpPerUnitCents != null ? pmM(sum.givenUpPerUnitCents) : "—") : "varies"}
{ready0 && preview && affected.length === 0 && (
{held.length > 0 ? "Every line in this scope is held back by an exclusion. " + (held.some((h) => setsAll.find((s) => s.id === h.held.set && s.mandatory)) ? "At least one is a mandatory set, so this promotion cannot run as scoped." : "Turn one off above, or change the scope.") : (preview.problem || "Nothing matches that scope yet.")}
)} {!ready0 &&
Choose the scope and dates to see what this will do.
} {stock.length > 0 && (
Will the stock last {durDays} days?
{shortL.length === 0 && heavy.length === 0 &&
Every line covers the run at the expected lift, with something left over. Nothing to do.
} {shortL.length > 0 && sum.stock.worst && (
{shortL.length === 1 ? "1 line runs out" : shortL.length + " lines run out"} before the end.{" "} {pmShort(sum.stock.worst.name)} has {sum.stock.worst.onHand} on hand at {storeName} and would go on day {sum.stock.worst.runsOut} of {durDays}. {sum.stock.worst.canReorder ? " Lead time is " + sum.stock.worst.leadDays + " days, so a reorder placed now still lands in time." : " Lead time is " + sum.stock.worst.leadDays + " days, which is too long to restock mid-promotion — either shorten the run or order before it starts."}
)} {heavy.length > 0 &&
{heavy.length} line{heavy.length === 1 ? " is" : "s are"} over-covered. You will finish with promotion-priced stock on the shelf that then reverts to full price. Consider a shorter run or a deeper discount.
}
{stock.slice(0, 6).map((l) => (
{l.name}
{l.stock.onHand} on hand · {Math.round(l.stock.weeklyUnits * 10) / 10}/wk normally · +{Math.round(l.stock.lift * 100)}% expected
{l.stock.short ? "day " + l.stock.runsOut : l.stock.leftOver + " left"}{l.stock.short ? "runs out" : "at the end"}
))} {stock.length > 6 &&
and {stock.length - 6} more
}
Lift is a planning estimate from discount depth, whether the line is a KVI, and whether a ticket goes up. It is not a forecast — treat it as the order of magnitude, not the number.
)} {comp.length > 0 && (
Where this lands you
{!isPrice ? (
A {m.label.toLowerCase()} has no single unit price, so there is nothing to compare against a shelf price. The observed set below is context for the lines it covers, not a verdict.
) : ( <>
0 ? " is-all" : roll.wins === 0 ? " is-none" : "")}>{roll.wins}of {roll.total - roll.noData} cheapest
{roll.losses === 0 && roll.wins > 0 && "This depth makes you the cheapest observed price on every line it covers."} {roll.losses > 0 && roll.wins > 0 && roll.losses + " line" + (roll.losses === 1 ? " is" : "s are") + " still beaten by someone even at this depth. Worth knowing before you print tickets calling it a special."} {roll.wins === 0 && roll.losses > 0 && "This depth does not make you cheapest on any line. The promotion costs margin without winning the comparison."} {roll.noData > 0 && " " + roll.noData + " line" + (roll.noData === 1 ? " has" : "s have") + " no usable observation."}
{overshot.length > 0 &&
Deeper than you need on {overshot.length === 1 ? "1 line" : overshot.length + " lines"}.{" "}{pmShort(overshot[0].name)} goes to {pmM(overshot[0].promoCents)}, but {pmM(overshot[0].landing.enoughCents)} would still have undercut {overshot[0].landing.cheapest.retailer} at {pmM(overshot[0].landing.cheapest.priceCents)} — {pmM(overshot[0].landing.overshootCents)} a unit given away for nothing.
} {stillLosing.length > 0 &&
Still beaten on {stillLosing.length === 1 ? "1 line" : stillLosing.length + " lines"}.{" "}{pmShort(stillLosing[0].name)} lands at {pmM(stillLosing[0].promoCents)} against {stillLosing[0].landing.cheapest.retailer} at {pmM(stillLosing[0].landing.cheapest.priceCents)}.
} )}
{comp.slice(0, 5).map((l) => (
{l.name}
{l.landing.chips.slice(0, 4).map((c) => {c.retailer} {pmM(c.priceCents)}{c.onSale && sale})} {l.landing.chips.length === 0 && not observed}
{l.promoCents != null ? pmM(l.promoCents) : "at till"}{l.promoCents == null ? "no verdict" : l.landing.position || "—"}
))} {comp.length > 5 &&
and {comp.length - 5} more
}
{comp.some((l) => l.landing.anyStale) ? "Greyed prices are older than three days and should not be trusted. " : ""} {comp.some((l) => l.landing.chips.some((c) => c.onSale)) ? <>Prices marked sale look like the competitor's own promotional price, not their shelf price — matching a catalogue special with a permanent promotion is how margin leaks. : null} Nothing here sets your depth; that is your call.
)} {breaches.length > 0 &&
{breaches.length} line{breaches.length === 1 ? " goes" : "s go"} below cost. Permitted with a reason, which is recorded against the promotion.
} {breaches.length > 0 &&