Blame | Last modification | View Log | RSS feed
<section class="wrapper"><div class="ocs-root"><style>/* Scoped to .ocs-root so it cannot leak into the admin theme. Colours use rgb()and selectors use classes, never id-hashes - Velocity would try to parse aleading "#" as a directive. */.ocs-root {--ocs-surface: rgb(255, 255, 255);--ocs-border: rgb(226, 232, 240);--ocs-text: rgb(15, 23, 42);--ocs-muted: rgb(100, 116, 139);--ocs-accent: rgb(79, 70, 229);--ocs-accent-soft: rgb(238, 242, 255);--ocs-ok: rgb(22, 163, 74);--ocs-ok-soft: rgb(240, 253, 244);--ocs-warn: rgb(180, 83, 9);--ocs-warn-soft: rgb(255, 251, 235);--ocs-danger: rgb(190, 18, 60);--ocs-danger-soft: rgb(255, 241, 242);font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;color: var(--ocs-text);font-size: 13px;line-height: 1.5;padding: 18px 22px 32px;box-sizing: border-box;}.ocs-root *, .ocs-root *::before, .ocs-root *::after { box-sizing: border-box; }.ocs-head { display: flex; align-items: flex-start; justify-content: space-between;gap: 16px; flex-wrap: wrap; margin-bottom: 8px; }.ocs-title { font-size: 20px; font-weight: 650; letter-spacing: -0.01em; margin: 0; }.ocs-sub { color: var(--ocs-muted); margin: 4px 0 0; font-size: 12.5px; max-width: 70ch; }.ocs-note { background: var(--ocs-warn-soft); border: 1px solid rgb(253, 230, 138);color: var(--ocs-warn); border-radius: 8px; padding: 9px 12px;font-size: 12.5px; margin: 14px 0 18px; }.ocs-tabs { display: flex; gap: 6px; border-bottom: 1px solid var(--ocs-border);margin-bottom: 16px; }.ocs-tab { border: 0; background: none; padding: 8px 14px; font-size: 13px;color: var(--ocs-muted); cursor: pointer; border-bottom: 2px solid transparent; }.ocs-tab.is-on { color: var(--ocs-accent); border-bottom-color: var(--ocs-accent);font-weight: 600; }.ocs-card { background: var(--ocs-surface); border: 1px solid var(--ocs-border);border-radius: 10px; padding: 16px 18px; margin-bottom: 18px; }.ocs-card h3 { margin: 0 0 4px; font-size: 14px; font-weight: 650; }.ocs-card p.ocs-hint { margin: 0 0 14px; color: var(--ocs-muted); font-size: 12.5px;max-width: 78ch; }.ocs-form { display: flex; flex-wrap: wrap; gap: 10px; align-items: flex-end; }.ocs-field { display: flex; flex-direction: column; gap: 4px; }.ocs-field label { font-size: 11.5px; color: var(--ocs-muted); text-transform: uppercase;letter-spacing: 0.04em; }.ocs-field input, .ocs-field select {border: 1px solid var(--ocs-border); border-radius: 7px; padding: 6px 9px;font-size: 13px; min-width: 170px; background: var(--ocs-surface);color: var(--ocs-text); height: 32px; }.ocs-field-wide input { min-width: 260px; }.ocs-btn { border: 1px solid var(--ocs-border); background: var(--ocs-surface);color: var(--ocs-text); border-radius: 7px; padding: 6px 13px;font-size: 12.5px; cursor: pointer; height: 32px; }.ocs-btn:hover { border-color: var(--ocs-accent); color: var(--ocs-accent); }.ocs-btn-primary { background: var(--ocs-accent); border-color: var(--ocs-accent);color: rgb(255, 255, 255); }.ocs-btn-primary:hover { color: rgb(255, 255, 255); opacity: 0.92; }.ocs-btn-danger { color: var(--ocs-danger); }.ocs-btn-danger:hover { border-color: var(--ocs-danger); color: var(--ocs-danger); }.ocs-btn-sm { height: 26px; padding: 2px 9px; font-size: 12px; }table.ocs-table { width: 100%; border-collapse: collapse; font-size: 12.5px; }table.ocs-table th { text-align: left; font-weight: 600; color: var(--ocs-muted);font-size: 11.5px; text-transform: uppercase; letter-spacing: 0.04em;padding: 8px 10px; border-bottom: 1px solid var(--ocs-border);white-space: nowrap; }table.ocs-table td { padding: 8px 10px; border-bottom: 1px solid var(--ocs-border);vertical-align: middle; }table.ocs-table tr.ocs-off td { background: rgb(250, 250, 251); color: var(--ocs-muted); }.ocs-scroll { overflow-x: auto; }.ocs-pill { display: inline-block; border-radius: 999px; padding: 1px 8px;font-size: 11.5px; font-weight: 600; }.ocs-pill-on { background: var(--ocs-ok-soft); color: var(--ocs-ok); }.ocs-pill-off { background: var(--ocs-danger-soft); color: var(--ocs-danger); }.ocs-pill-alias { background: var(--ocs-accent-soft); color: var(--ocs-accent); }.ocs-mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;font-size: 12px; }.ocs-muted { color: var(--ocs-muted); }.ocs-empty { text-align: center; color: var(--ocs-muted); padding: 22px; }.ocs-msg { border-radius: 8px; padding: 9px 12px; font-size: 12.5px; margin-bottom: 14px;display: none; }.ocs-msg-ok { background: var(--ocs-ok-soft); color: var(--ocs-ok);border: 1px solid rgb(187, 247, 208); }.ocs-msg-bad { background: var(--ocs-danger-soft); color: var(--ocs-danger);border: 1px solid rgb(254, 205, 211); }.ocs-export { width: 100%; min-height: 220px; font-family: ui-monospace, Menlo, monospace;font-size: 12px; border: 1px solid var(--ocs-border); border-radius: 8px;padding: 10px; display: none; }</style><div class="ocs-head"><div><h2 class="ocs-title">Offer circular — scope config</h2><p class="ocs-sub">Which OEM divisions get ingested from the monthly circular, andwhich alternate labels mean a division that already exists. This is the onlyauthority on scope — the parser has no built-in list.</p></div><div><button class="ocs-btn" type="button" data-ocs-export>Export as SQL</button><button class="ocs-btn" type="button" data-ocs-reload>Reload</button></div></div><div class="ocs-note">Changes here take effect on the <strong>next ingest</strong>. Scope is read when acircular is parsed, so editing it does not alter a circular that is already published— re-ingest that circular from the review screen to apply it.</div><div class="ocs-msg ocs-msg-ok" data-ocs-ok></div><div class="ocs-msg ocs-msg-bad" data-ocs-bad></div><div class="ocs-tabs"><button class="ocs-tab is-on" type="button" data-ocs-tab="divisions">Divisions</button><button class="ocs-tab" type="button" data-ocs-tab="aliases">Label aliases</button></div><div data-ocs-panel="divisions"><div class="ocs-card"><h3>Add or edit a division</h3><p class="ocs-hint">The label must match the circular's “OEM Name” cellexactly. Saving an existing label edits it. A division needs a category so thematcher knows which catalog bucket to search; a brand may be missing (Google hasno catalog brand row), in which case its products stay unresolved.</p><div class="ocs-form"><div class="ocs-field ocs-field-wide"><label>OEM label (verbatim)</label><input type="text" data-ocs-d-label placeholder="Samsung Tabs" /></div><div class="ocs-field"><label>Display name</label><input type="text" data-ocs-d-display placeholder="Samsung Tablets" /></div><div class="ocs-field"><label>Brand</label><select data-ocs-d-brand><option value="">- none -</option></select></div><div class="ocs-field"><label>Category</label><select data-ocs-d-category><option value="">- none -</option></select></div><div class="ocs-field"><label>In scope</label><select data-ocs-d-inscope><option value="true">Yes - ingest it</option><option value="false">No - drop it</option></select></div><div class="ocs-field ocs-field-wide"><label>Reason (required when out of scope)</label><input type="text" data-ocs-d-reason placeholder="Laptops - out of scope" /></div><button class="ocs-btn ocs-btn-primary" type="button" data-ocs-d-save>Save division</button><button class="ocs-btn" type="button" data-ocs-d-clear>Clear</button></div></div><div class="ocs-card"><h3>Divisions</h3><p class="ocs-hint">Taking a division out of scope never deletes anything —offers already ingested keep their history. It changes what the next ingest keeps.</p><div class="ocs-scroll"><table class="ocs-table"><thead><tr><th>OEM label</th><th>Display name</th><th>Brand</th><th>Category</th><th>Scope</th><th>Reason</th><th>Offers</th><th>Aliases</th><th></th></tr></thead><tbody data-ocs-divisions><tr><td colspan="9" class="ocs-empty">Loading…</td></tr></tbody></table></div></div></div><div data-ocs-panel="aliases" style="display:none"><div class="ocs-card"><h3>Add a label alias</h3><p class="ocs-hint">Use this when the circular prints a different spelling of adivision that already exists — “XIAOMI CE” for“Xiaomi CE”, say. An alias only says two labels mean the samedivision; it carries no brand or category of its own, so the scope decision staysin one place. If the label needs a <em>different</em> category, it is a division,not an alias.</p><div class="ocs-form"><div class="ocs-field ocs-field-wide"><label>Label as printed</label><input type="text" data-ocs-a-label placeholder="XIAOMI CE" /></div><div class="ocs-field"><label>Means this division</label><select data-ocs-a-division><option value="">- pick one -</option></select></div><div class="ocs-field ocs-field-wide"><label>Note (optional)</label><input type="text" data-ocs-a-note placeholder="Sep'26 circular prints it upper case" /></div><button class="ocs-btn ocs-btn-primary" type="button" data-ocs-a-save>Save alias</button></div></div><div class="ocs-card"><h3>Label aliases</h3><div class="ocs-scroll"><table class="ocs-table"><thead><tr><th>Label as printed</th><th>Means</th><th>Note</th><th>Added by</th><th></th></tr></thead><tbody data-ocs-aliases><tr><td colspan="5" class="ocs-empty">Loading…</td></tr></tbody></table></div></div></div><textarea class="ocs-export" data-ocs-export-box readonly></textarea></div><script type="text/javascript">// This page can be opened directly, so it cannot rely on admin.vm having already// set the global "context".window.ocsContext = "${rc.contextPath}";// Standalone bootstrap - same reason as the review screen. Injected as a fragment// jQuery is already present and this does nothing; opened by URL the fragment// arrives alone with no include-scripts.vm, and the screen would never initialise.(function () {var base = window.ocsContext || '';function addScript(src, next) {var s = document.createElement('script');s.src = src;s.onload = next;document.head.appendChild(s);}// The script registers everything inside $(function(){...}), which runs on the// NEXT tick - so at this script's onload the init function does not exist yet.// Calling it here directly is a silent no-op that leaves the screen on "Loading".// Defer through jQuery's ready queue instead: our callback is registered after// the file's own, so it runs after it.function start() {if (!window.jQuery) { return; }window.jQuery(function () {if (window.initOfferCircularScope) { window.initOfferCircularScope(); }});}if (!window.jQuery) {addScript(base + '/resources/js/jquery-3.2.1.min.js', function () {addScript(base + '/resources/js/offer-circular-scope.js?v=${version}', start);});} else if (!window.initOfferCircularScope) {addScript(base + '/resources/js/offer-circular-scope.js?v=${version}', start);} else {start();}})();</script></section>