-- Support article deploy script — break-policy + compliance dashboard
-- Generated: 2026-05-28 16:24:59
-- Source: local kiosk_buykiosk DB + repo markdown drafts
-- Target: production kiosk_buykiosk DB
--
-- Scope: 2 NEW articles in the Scheduling category
--   - break-policies: configurable break-policy presets, rules, default break type
--   - compliance-dashboard: violation review + resolution + CSV export
--
-- Idempotent: safe to re-run. All matching is by slug, not by ID.
-- Wrapped in a transaction; existing prod state (status, view_count,
-- author_id, is_featured) is preserved on ON DUPLICATE KEY UPDATE.
--
-- Recommended invocation:
--   mysql --default-character-set=utf8mb4 -h <prod-host> -u <user> -p kiosk_buykiosk \
--     < deploy-break-policy-articles-2026-05-28.sql
--
-- Image files referenced by these articles must be deployed FIRST:
--   public_html/images/support/articles/break_policy_overview.png
--   public_html/images/support/articles/break_policy_change_preset.png
--   public_html/images/support/articles/break_policy_edit_rule.png
--   public_html/images/support/articles/compliance_dashboard_overview.png

START TRANSACTION;

-- ---- Break Policies (slug=break-policies, status=published, category=scheduling, 11313 bytes) ----
INSERT INTO support_articles
    (category_id, title, slug, content_md, content_html, author_id,
     status, view_count, is_featured, created_at, updated_at)
VALUES (
    (SELECT id FROM support_categories WHERE slug = 'scheduling' LIMIT 1),
    'Break Policies',
    'break-policies',
    'Break Policies tell BuyerKiosk how to decide whether each break an employee takes is paid or unpaid — and whether a missed, late, or short break should be flagged as a compliance issue. Pick a preset that matches your state\'s labor law (or use the FLSA default), and the system handles the classification automatically. No more asking employees to choose \"paid\" vs. \"unpaid\" at clock-out.\n\n![Break Policy page showing the California 2025 preset, Default Break Type card, and four break rules](/images/support/articles/break_policy_overview.png)\n\n*The Break Policy page for a California store: preset header, default-break-type fallback, and the four California rules listed in priority order.*\n\n---\n\n## Getting There\n\nNavigate to **Store Settings** > **Break Policy** from the admin sidebar, or go directly to `/admin/{storeCode}/scheduling/break-policy`.\n\nYou need the `uri_schedule_manage` permission to view or edit Break Policy.\n\n> **Note:** If your store uses **When I Work** for scheduling, the policy engine is currently disabled for you — When I Work owns break classification for integrated stores. Native BuyerKiosk scheduling stores see the full feature.\n\n---\n\n## What\'s on the Page\n\n| Area | Purpose |\n|------|---------|\n| **Preset header** | Shows your current preset name and version, with the `Change Preset` button |\n| **Default Break Type** | Fallback classification (Unpaid / Paid) for breaks that don\'t match any rule |\n| **Break Rules** | The ordered list of rules that decide paid/unpaid, late/short flags, and required-break compliance |\n\n---\n\n## Presets at a Glance\n\nA preset is a complete set of break rules tuned to a specific labor jurisdiction. BuyerKiosk ships seven v1 presets:\n\n| Preset | Use it when… |\n|--------|--------------|\n| **FLSA Default (Federal)** | You\'re in a state with no specific break law (TX, FL, GA, and most others). Replicates federal-only rules. |\n| **California 2025** | Your store is in CA. 10-min paid rest per 4h, 30-min unpaid meal after 5h (must start by hour 5), 2nd meal after 10h. |\n| **New York 2025** | Your store is in NY. 30-min meal ≥6h spanning 11:00–14:00; extra 20-min meal if a shift continues past 19:00. |\n| **Oregon 2025** | Your store is in OR. 10-min paid rest per 4h; 30-min unpaid meal >6h. |\n| **Washington 2025** | Your store is in WA. 10-min paid rest per 4h; 30-min unpaid meal >5h, scheduled within hours 2–5. |\n| **Colorado 2025** | Your store is in CO. 10-min paid rest per 4h; 30-min unpaid meal >5h. |\n| **Illinois 2025** | Your store is in IL. 20-min unpaid meal ≥7.5h, taken within the first 5 hours of the shift. |\n\n> **Tip:** Not sure which preset to pick? When in doubt, start with **FLSA Default** — it exactly replicates BuyerKiosk\'s pre-2026 behavior. You can switch to a state preset any time without losing data.\n\n---\n\n## Changing Your Preset\n\nClick `Change Preset` (top-right of the page) to open the preset picker.\n\n![Change Break Policy Preset modal listing all seven presets with one-line summaries](/images/support/articles/break_policy_change_preset.png)\n\n*Each preset shows a one-line summary of its rules so you can compare at a glance. Your current preset is tagged with a blue **Current** badge.*\n\n1. Pick a preset from the list.\n2. Confirm the change — your rules are cloned from the selected preset.\n3. The new rules take effect immediately on the next punch processed.\n\n> **Warning:** Switching presets overwrites any custom rule edits you\'ve made (the system clones the preset\'s rules into your store). If you\'ve customized rules and want to keep them, note them down before switching.\n\n> **Note:** Adopting a preset **clones** its rules into your store. Future updates to that preset (e.g., a `CA-2026` release next year) **don\'t** silently change your rules — you\'ll see an \"Update available\" banner and explicitly opt in.\n\n---\n\n## Default Break Type\n\nThis is the fallback classification used when a break doesn\'t match any rule (for example, a 12-minute break on a 4-hour shift when your preset has no rule for that case).\n\n| Option | Effect |\n|--------|--------|\n| **Unpaid** | The break duration is subtracted from the employee\'s paid hours at clock-out. Matches BuyerKiosk\'s legacy behavior. |\n| **Paid** | The break duration counts as paid time. Useful for stores that pay all short breaks. |\n\nThe setting saves immediately when you click a radio button.\n\n> **Tip:** Leave this set to **Unpaid** unless your store\'s policy is explicitly to pay all unclassified breaks. The FLSA Cutoff rule (which automatically pays any break under 20 minutes) is the more common way to make short breaks paid.\n\n---\n\n## Reading the Break Rules Table\n\nRules are evaluated **in priority order** (lowest number first). The first matching rule wins. The FLSA Cutoff rule at priority 100 is a final safety net — any break shorter than its threshold is treated as paid.\n\n| Column | What it means |\n|--------|---------------|\n| **Priority** | The order rules are checked. Lower = checked first. |\n| **Type** | The rule\'s category badge: `Rest Break`, `Meal Break`, `2nd Meal`, `FLSA Cutoff`, or `Auto-Deduct`. |\n| **Trigger** | When this rule applies — typically a shift-length threshold (e.g., `Shift > 5h`) and optionally a \"must start before hour X\" constraint. |\n| **Duration** | The break length (in minutes) that satisfies this rule. Shorter breaks count as a `Short Meal` violation. |\n| **Pay** | `Paid` (counts as worked time) or `Unpaid` (subtracted from paid hours). |\n| **Required** | `Yes` means a violation is logged if the employee never takes a break of this type during a qualifying shift. |\n| **Premium Pay** | Hours surfaced as a flag on the timesheet when the rule is violated. **Flag-only — nothing is added to payroll automatically.** Review the flagged shift and add the premium manually if your state law requires it. |\n\n### How a break gets classified — step-by-step\n\n1. Sort rules by priority (lowest first).\n2. For each break, find the first rule whose `Trigger` matches the current shift length / time window / break duration.\n3. Apply that rule\'s `Pay` setting.\n4. If the rule has a \"must start before hour X\" constraint and the break started later, log a `Late Meal` violation.\n5. If the break is shorter than the rule\'s `Duration`, log a `Short Meal` violation.\n6. Any break shorter than the FLSA Cutoff\'s duration is forced to **Paid** as a final classifier (per FLSA\'s <20-min rule).\n7. After the shift ends, any `Required = Yes` rule that didn\'t have a matching break logs a `Missing Meal` or `Missing Rest` violation.\n8. Premium-pay hours from all violations are summed for the day.\n\n---\n\n## Editing a Rule\n\nClick the pencil icon (Actions column) on any rule to open the rule editor.\n\n![Edit Rule modal for Meal Break showing duration, trigger, must-start-by, paid/required toggles, and premium pay](/images/support/articles/break_policy_edit_rule.png)\n\n*The Edit Rule modal for the California Meal Break: 30-minute duration, triggered by shifts >5h, must start before hour 5, unpaid, required, with 1 hour of premium pay flagged on violation.*\n\n### Fields\n\n| Field | What it controls |\n|-------|------------------|\n| **Duration (minutes)** | Minimum break length that counts as compliant. A 25-minute break against a 30-minute rule logs a `Short Meal` violation. |\n| **Trigger: Shift hours >** | Minimum shift length for this rule to apply. Set to 5 to mean \"shifts longer than 5 hours.\" |\n| **Must start before hour of shift** | Optional. For meal rules that require the break to start within the first N hours. Late breaks log a `Late Meal` violation. |\n| **Paid break** | Toggle. When on, break time counts as paid; when off, unpaid. |\n| **Required** | Toggle. When on, a missing break of this type logs a `Missing Meal` / `Missing Rest` violation. |\n| **Premium pay on violation (hours)** | Hours flagged on the timesheet when the rule is missed or taken late. **Not auto-paid** — surfaces as a flag for your manual review. |\n\nClick `Save Changes` to commit. The change applies to all future punches immediately; historical timesheets aren\'t reclassified.\n\n> **Tip:** Resist the urge to fine-tune rules unless your state law specifically requires it. The shipped presets are reviewed against current labor law — most stores never need to edit a rule.\n\n---\n\n## \"Update Available\" — Yearly Refresh\n\nOnce a year, BuyerKiosk publishes new preset versions to reflect updated labor law (e.g., `CA-2026`). When that happens, you\'ll see an **Update available** banner on the Break Policy page for any affected preset.\n\n1. Click the banner.\n2. Review the diff preview — exactly which rules are changing, with old vs. new values shown side by side.\n3. Confirm to adopt the new version. Your custom overrides are preserved where the field-level overrides allow.\n\nThe old version stays in place until you opt in — no surprise rule changes from a yearly release.\n\n---\n\n## Where Violations Show Up\n\nOnce Break Policy is active, the policy engine logs violations to your store\'s compliance log. You\'ll see them in two places:\n\n- **On the timesheet** — Days with a violation show a red Compliance indicator. The expanded row lists each violation and any premium-pay hours flagged for the day.\n- **On the [Compliance Dashboard](/support/articles/compliance-dashboard)** — One screen for resolving and exporting all open violations across the store.\n\n---\n\n## Troubleshooting\n\n### The Break Policy page won\'t load\n- Confirm you have the `uri_schedule_manage` permission. If not, ask your account owner to grant it.\n- For When I Work integrated stores, the page may show a banner saying break policy isn\'t active for your store — that\'s expected.\n\n### A break I expected to be paid showed up as unpaid (or vice versa)\n- Open the Break Policy page and walk through the rules in priority order. The first matching rule wins.\n- Check the **Default Break Type** — if no rule matched, the fallback determines the classification.\n- Remember the FLSA Cutoff at priority 100: any break shorter than its duration (default 20 minutes) is forced to `Paid` regardless of other rules.\n\n### \"Premium pay\" flagged but the employee wasn\'t paid extra\n- That\'s intentional. Premium pay is **flag-only** in v1 — it\'s surfaced as a compliance flag for your manual review. Add the premium pay to the employee\'s timesheet manually if your state law requires it.\n\n### I changed a rule but old timesheets still show the old classification\n- Rule changes apply to **new punches only**. Historical timesheets aren\'t reclassified automatically. If you need to re-evaluate a historical period under your current policy, contact BuyerKiosk support.\n\n### I switched presets and lost my custom edits\n- Switching presets clones the new preset\'s rules in. Custom edits made to the old preset are overwritten. The \"Update available\" yearly-refresh flow (different from changing presets) preserves overrides where it can.\n\n---\n\n## Related Articles\n\n- [Compliance Dashboard](/support/articles/compliance-dashboard) — Review and resolve break violations across your store\n- [Timesheets](/support/articles/timesheets) — How break classification shows up on payroll\n- [Schedule Calendar](/support/articles/schedule-calendar)\n- [Scheduling Settings](/support/articles/scheduling-settings)\n',
    NULL,
    COALESCE((SELECT id FROM kiosk_users.users WHERE username = 'rvanvuren' LIMIT 1), 1),
    'published',
    0,
    0,
    NOW(), NOW()
)
ON DUPLICATE KEY UPDATE
    content_md = VALUES(content_md),
    content_html = NULL,
    category_id = VALUES(category_id),
    title = VALUES(title),
    updated_at = NOW();

-- ---- Compliance Dashboard (slug=compliance-dashboard, status=published, category=scheduling, 10221 bytes) ----
INSERT INTO support_articles
    (category_id, title, slug, content_md, content_html, author_id,
     status, view_count, is_featured, created_at, updated_at)
VALUES (
    (SELECT id FROM support_categories WHERE slug = 'scheduling' LIMIT 1),
    'Compliance Dashboard',
    'compliance-dashboard',
    'The Compliance Dashboard is where you review and resolve break-rule violations logged by BuyerKiosk\'s policy engine. Whenever an employee misses a required meal, takes a meal too late, takes a short meal, or skips a required rest, the engine writes a violation record. This page is one screen to see every open issue across your store, filter and sort it, mark it resolved, and export the data for payroll.\n\n![Compliance Dashboard showing the filter row (Violation Type, Employee, From Date, To Date, Status) and the violation list area](/images/support/articles/compliance_dashboard_overview.png)\n\n*The Break Compliance dashboard for a California store: filter row at the top, violations listed below.*\n\n---\n\n## Getting There\n\nNavigate to **Scheduling** > **Compliance** from the admin sidebar, or go directly to `/admin/{storeCode}/scheduling/compliance`.\n\nYou need the `uri_schedule_manage` permission to view or resolve violations.\n\n> **Prerequisite:** The Compliance Dashboard surfaces data produced by the policy engine. If your store hasn\'t [adopted a Break Policy](/support/articles/break-policies) (or hasn\'t been switched on by BuyerKiosk yet), the dashboard will be empty — there\'s nothing to flag without a policy. When I Work integrated stores don\'t generate violations in v1.\n\n---\n\n## Filtering the List\n\nThe filter row at the top lets you scope what you\'re looking at. Filters combine with AND — narrower filters = fewer rows.\n\n| Filter | Options |\n|--------|---------|\n| **Violation Type** | All types, **Missing Meal**, **Late Meal**, **Short Meal**, **Missing Rest**, **Auto-Deduct** |\n| **Employee** | All employees, or any individual team member assigned to this store |\n| **From Date / To Date** | Defaults to the last 30 days. Click the calendar icon to pick a custom range. |\n| **Status** | **All**, **Open only** (default for triage), or **Resolved only** (for audit / payroll review) |\n\n> **Tip:** For weekly payroll review, set `Status = Open only` and `From Date = the Monday of the pay period`. That\'s the smallest list you need to clear before exporting timesheets.\n\n---\n\n## Violation Types Explained\n\nThe policy engine writes one of five violation types per shift. Each has its own trigger and remediation expectation.\n\n| Type | Triggered when… | What to do |\n|------|-----------------|------------|\n| **Missing Meal** | A shift required a meal break (per your active preset) and no meal break was punched | Confirm with the employee whether they took the meal off-punch (manual edit) or genuinely missed it (apply premium pay if your state requires it) |\n| **Late Meal** | A meal break started after the \"must start before hour X\" boundary defined in the rule | Verify the start-time on the punch; if accurate, flag for premium pay where required |\n| **Short Meal** | A meal break was shorter than the rule\'s `Duration` (e.g., 22 minutes against a 30-minute rule) | Confirm whether the employee returned early voluntarily or was asked back; apply premium pay where required |\n| **Missing Rest** | A shift required a paid rest break (e.g., CA\'s 10-min per 4h rule) and none was logged | Rest breaks are often taken without punching — confirm with the employee before treating as a real violation |\n| **Auto-Deduct** | A long shift had no meal break, and your policy synthesized a virtual unpaid deduction | Audit the shift\'s actual breaks; if the employee took a meal but forgot to punch, add a manual break to undo the auto-deduct |\n\n> **Tip:** **Missing Rest** is the most common false positive — most stores don\'t require employees to punch in/out for paid rest breaks. If your culture is \"managers know rests happen,\" you can [disable the Required flag](/support/articles/break-policies#editing-a-rule) on the Rest Break rule to stop these violations from being logged.\n\n---\n\n## Reading a Violation Row\n\nEach row in the list represents one violation on one shift. The columns surface what happened and what (if anything) is owed.\n\n| Column | What it shows |\n|--------|---------------|\n| **Date** | The shift date the violation occurred on |\n| **Employee** | Name of the employee whose shift triggered the violation |\n| **Violation Type** | One of the five types above, with a colored badge |\n| **Rule** | Which rule from your active preset triggered this (e.g., `CA Meal Break`) |\n| **Premium Pay (hrs)** | Hours flagged for manager review. **Not auto-added to payroll** — you decide whether to apply it. |\n| **Status** | `Open` (pending review) or `Resolved` with the resolver\'s name and resolved-at timestamp |\n| **Actions** | `Resolve` button on open rows; `Reopen` on resolved rows |\n\nClick a row to expand it and see the shift\'s full punch history alongside the rule that triggered.\n\n---\n\n## Resolving a Violation\n\n\"Resolved\" doesn\'t mean the violation disappears — it means a manager has reviewed it and either applied the premium pay manually or determined no action is needed.\n\n1. Click `Resolve` on the row.\n2. Add a short **note** explaining what you did:\n   - \"Added $X premium pay to timesheet — CA-required missed-meal pay\"\n   - \"Verified with employee — rest break was taken, not a real violation\"\n   - \"Auto-deduct undone via manual break punch\"\n3. Click `Confirm`.\n\nThe row updates to **Resolved** and records:\n\n- `resolvedByUserId` — who marked it resolved (you)\n- `resolvedAt` — when\n- Your note (stays attached to the violation log forever)\n\n> **Tip:** Even \"no action needed\" violations should be resolved with a note. The compliance log is your audit trail — empty resolution notes make it hard to defend a wage-and-hour claim later.\n\n### Reopening a Violation\n\nIf you resolved a violation by mistake or want to revisit it:\n\n1. Filter to `Status = Resolved only`.\n2. Find the row.\n3. Click `Reopen` and add an explanatory note.\n\nThe row moves back to **Open** with the reopen action appended to its audit trail.\n\n---\n\n## Exporting for Payroll\n\nClick `Export CSV` (top-right of the page) to download the currently filtered list as a CSV file. The export includes:\n\n- Date, employee, violation type, rule name\n- Premium pay hours per row\n- Status, resolved-by, resolved-at\n- The resolution note (if any)\n\nThe CSV honors your active filters — so for a payroll-period export, set `From Date` / `To Date` first, then click `Export`.\n\n> **Tip:** Pair this export with your timesheet CSV. Cross-reference the two so any premium pay you decided to apply ends up on the right payroll line.\n\n---\n\n## Premium Pay — Important to Know\n\nPremium pay (the \"hours flagged\" column on a violation) is **never automatically added** to payroll totals in v1. The dashboard surfaces it; you decide whether to pay it.\n\n- **Some states (e.g., CA)** legally require an hour of premium pay for a missed or late meal. In those states, applying the premium is mandatory — but BuyerKiosk leaves the application to you for visibility and accuracy.\n- **Other states (e.g., FLSA Default)** don\'t require premium pay at all. The number is informational — usually 0.\n- **To actually pay an employee the premium**, edit their timesheet for that day and add a manual entry, or apply the hours through your payroll provider directly. The Compliance Dashboard\'s job is to make sure nothing slips through; it doesn\'t write to payroll on its own.\n\nA future BuyerKiosk release may add an opt-in to auto-add premium pay. Until then, treat the dashboard as a review queue, not an autopilot.\n\n---\n\n## Common Workflows\n\n### Weekly payroll review (5-minute version)\n1. Filter to `Status = Open only`, `From Date = pay-period start`.\n2. Scan each row. For each:\n   - If real → apply premium pay on the timesheet, then resolve with a note.\n   - If false positive (rest break taken without punching, employee took meal off-clock, etc.) → resolve with an explanatory note.\n3. Export the now-empty open list (or the all-status export) for your records.\n4. Run the timesheet [Custom Export](/support/articles/timesheets#exporting-timesheets).\n\n### Spot-checking a specific employee\n1. Set `Employee = {name}`, `Status = All`, `From Date = last 90 days`.\n2. Review the row count and types. Patterns (\"always Late Meal\") may indicate scheduling issues, not employee misconduct.\n\n### Quarterly compliance audit\n1. Set `Status = All`, `From Date = quarter start`, `To Date = quarter end`.\n2. Export to CSV.\n3. Use the CSV to total premium-pay hours owed, missed meal counts, etc.\n\n---\n\n## Troubleshooting\n\n### \"Failed to load violations\" error on first open\n- This usually means your store\'s compliance log table isn\'t seeded yet, or the policy engine hasn\'t run against any punches. If you\'ve just enabled Break Policy for your store, give it a day of clock-ins and recheck.\n- Persistent error: contact BuyerKiosk support with your store typeNum.\n\n### A violation looks wrong (employee did take the break)\n- Check the punch history (click the row to expand). The most common cause is a missed break punch — the employee took the break but didn\'t clock out / back in.\n- Add a manual break punch from the [Timesheet detail page](/support/articles/timesheets-time-tracking#editing-time-punches) for that day. The violation will be re-evaluated and cleared on the next timesheet save.\n\n### \"Auto-Deduct\" violation on a shift where the employee actually took a meal\n- Same root cause — break wasn\'t punched. Add a manual break punch to the timesheet; the auto-deduct unwinds.\n\n### I want to stop seeing \"Missing Rest\" violations\n- Most stores don\'t punch rest breaks (they\'re paid and short). To stop logging those violations: open the [Break Policy page](/support/articles/break-policies), edit the **Rest Break** rule, and turn the **Required** toggle off.\n\n### Export CSV is empty\n- The export honors your filters. Confirm the filter row hasn\'t narrowed too far (e.g., a specific employee + status = Resolved only when there are no resolved violations).\n\n---\n\n## Related Articles\n\n- [Break Policies](/support/articles/break-policies) — Configure which rules trigger violations\n- [Timesheets](/support/articles/timesheets) — Where premium pay lands once you apply it\n- [Timesheets & Time Tracking](/support/articles/timesheets-time-tracking) — Editing punches to fix a violation\n',
    NULL,
    COALESCE((SELECT id FROM kiosk_users.users WHERE username = 'rvanvuren' LIMIT 1), 1),
    'published',
    0,
    0,
    NOW(), NOW()
)
ON DUPLICATE KEY UPDATE
    content_md = VALUES(content_md),
    content_html = NULL,
    category_id = VALUES(category_id),
    title = VALUES(title),
    updated_at = NOW();

COMMIT;

-- ============================================================
-- Verification (uncomment and run after deploy)
-- ============================================================
-- SELECT id, slug, title, status, category_id,
--        LENGTH(content_md) AS bytes, updated_at
--   FROM support_articles
--  WHERE slug IN ('break-policies','compliance-dashboard')
--  ORDER BY slug;
