-- Support article deploy script (v2)
-- Generated: 2026-05-23 21:13:45
-- Source: local kiosk_buykiosk DB
-- Target: production kiosk_buykiosk DB
--
-- v2 changes:
--   - Author lookup uses kiosk_users.users (canonical) instead of uf_user (legacy).
--   - Includes content for ALL 9 QuickBooks articles + 2 kiosk + 1 QB overview
--     (was only 3 articles in v1).
--
-- Idempotent: safe to re-run. All matching is by slug, not by ID.
-- Run inside a transaction so you can ROLLBACK if anything looks wrong.
--
-- Recommended invocation:
--   mysql --default-character-set=utf8mb4 -h <prod-host> -u <user> -p kiosk_buykiosk \
--     < deploy-support-articles-2026-05-23.sql
--
-- IMPORTANT:
--   - Author lookup reads from the kiosk_users database. The MySQL user
--     running it must have SELECT on kiosk_users.users.
--   - `--default-character-set=utf8mb4` is REQUIRED. Several article bodies
--     contain 4-byte emoji (🟢/🔴/etc); the columns are utf8mb4 but the mysql
--     client defaults to utf8 (3-byte) and will throw
--     "Incorrect string value: '\xF0\x9F...' for column content_md" without it.

START TRANSACTION;

-- ============================================================
-- 1. Upsert 'QuickBooks' support category
-- ============================================================
INSERT INTO support_categories (name, slug, description, icon, sort_order, parent_id, is_active, created_at, updated_at)
VALUES (
    'QuickBooks',
    'quickbooks',
    'QuickBooks Online integration: connection setup, account mapping, daily approval workflow, dashboards, sync log, audit log, and reconciliation.',
    'fa-file-invoice-dollar',
    12, NULL, 1, NOW(), NOW()
)
ON DUPLICATE KEY UPDATE
    name = VALUES(name),
    description = VALUES(description),
    icon = VALUES(icon),
    is_active = 1,
    updated_at = NOW();

-- ============================================================
-- 2. Upsert support articles by slug (12 articles)
-- ============================================================
-- New rows: inserted with the local DB's status and is_featured values.
-- Existing rows: ONLY content_md, content_html (cleared), and category_id change.
-- Existing status, view_count, author_id, is_featured are PRESERVED on prod.

-- ---- Article #94: Customize the Printed Receipt (slug=customize-printed-receipt, status=published) ----
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 = 'kiosk' LIMIT 1),
    'Customize the Printed Receipt',
    'customize-printed-receipt',
    'Every time a customer finishes checking in at the BuyerKiosk, two receipts print: a **store copy** for your team to reference during the buy and a **customer copy** that goes home with the seller. Almost every line of text on both receipts — pass reasons, positive comments, \"most wanted\" items, store hours, store address, and the customer-facing footer — is editable from the kiosk\'s hidden admin panel. This article walks through how to reach that panel and customize each piece.\n\n![BuyerKiosk Administration Functions menu](/images/support/articles/kiosk_admin_panel.jpg)\n\n*The BuyerKiosk Administration Functions screen is your control center for everything that prints on the receipt.*\n\n---\n\n## Sample Receipts\n\nHere\'s what the two printed receipts look like, with the customizable areas labeled. Use these as a reference as you work through the sections below.\n\n### Store Copy (Buy Slip)\n\nThe store copy is the first receipt to print. Your team uses it during evaluation — they circle the pass reasons and positive comments that apply to the customer\'s items.\n\n![Sample store-copy receipt showing pass reasons and things we liked](/images/support/articles/kiosk_receipt_store_copy.jpg)\n\n*Visible customizations: **Pass Reasons** (Excessive Wear, Light Staining, Older Styles, etc.) and **Things We Liked** (Cute Stuff, Current Styles, Brands We Love, High Need Items).*\n\n### Customer Copy (Claim Check)\n\nThe customer copy is the second receipt to print. It\'s the one the customer takes home and is the one they\'re most likely to read at home.\n\n![Sample customer-copy receipt showing store info, pickup language, store hours, and most wanted items](/images/support/articles/kiosk_receipt_customer_copy.jpg)\n\n*Visible customizations: **Store name, address, and phone** (header), **Customer Receipt Pick Up Language** (\"Customer must pick up clothes the same day...\"), **Store Hours** (Mon-Sat 10:00am to 8:00pm, Sun 1:00pm to 6:00pm), **Most Wanted Items** (High Chairs, Halloween Costumes, Strollers, etc.), and the **Footer Language** (\"Thank you for bringing your items in today.\").*\n\n---\n\n## What You Can Customize\n\n| Receipt Element | Admin Panel Button | Where It Appears |\n|---|---|---|\n| Store name, address, and phone | `Update Store Info` | Customer receipt header |\n| Store hours | `Update Store Info` | Customer receipt body |\n| Customer pickup instructions | `Update Customer Receipt` | Customer receipt body |\n| Customer-facing footer / thank-you | `Update Customer Receipt` | Customer receipt footer |\n| Reasons you passed on items | `Update Pass Reasons` | Store receipt (buyer-facing during evaluation) |\n| Positive comments about items | `Update Things We Liked` | Store receipt |\n| Items you\'re actively buying | `Most Wanted Items` | Store receipt, customer receipt, both, or neither |\n\n> **Note:** Looking to update the **disclosure / terms & conditions** that customers agree to during check-in? That text is shown on-screen during the check-in flow, not printed on the receipt. See [Customize the Customer Disclosure](/support/articles/customize-customer-disclosure) for that workflow.\n\n---\n\n## How to Reach the Kiosk Admin Panel\n\nThe admin panel is intentionally hidden from customers. Getting in is a four-step process.\n\n### Step 1 — Start a Check-In\n\nFrom the main kiosk screen, tap `English` or `Español` to begin a check-in.\n\n![BuyerKiosk main screen with English and Spanish buttons](/images/support/articles/kiosk_first_screen.jpg)\n\n*The starting screen customers see when they walk up to the kiosk.*\n\n### Step 2 — Get to the Manual Driver\'s License Screen\n\nYou need to land on the **manual** driver\'s license input screen — the one with a keyboard, *not* the scanner camera view.\n\n- **If your store has a DL scanner**, the scanning screen will appear first. Tap `Cancel Scan` at the bottom to switch to manual entry.\n\n  ![Driver\'s license scanner screen with Cancel Scan button](/images/support/articles/kiosk_drivers_license_scan.jpg)\n\n- **If your store does not have a scanner**, the manual entry screen appears automatically.\n\nYou should now see a screen titled \"Please sign in...\" with a `Drivers License #` field and a state dropdown.\n\n![Manual driver\'s license entry screen — tap the logo at the top 10 times](/images/support/articles/kiosk_drivers_license_manual.jpg)\n\n*This is the screen where the hidden gesture works. Note the store logo banner at the top.*\n\n### Step 3 — Tap the Logo 10 Times\n\n> **Warning:** This gesture **only works on the manual driver\'s license screen** (the one with the keyboard). If you tap the logo from the very first English/Spanish welcome screen, it intentionally exits the BuyerKiosk app and drops you onto Android — that\'s a separate \"escape hatch\" for store staff who need to reach Android settings, not the admin panel. If you ended up at the Android home screen or seeing app icons, you tapped from the wrong screen. Relaunch BuyerKiosk and start over at Step 1.\n\nOnce you\'re on the manual DL screen, tap the **store logo at the top of the screen** about 10 times in a row. A password prompt will appear.\n\n![Administration password prompt](/images/support/articles/kiosk_admin_password.jpg)\n\n*The password dialog. Enter your admin password and tap `OK`.*\n\n> **Tip:** The admin password is `kiosk` for all stores.\n\n### Step 4 — You\'re In\n\nAfter entering the correct password, the full **BuyerKiosk Administration Functions** menu appears with all the customization buttons.\n\n---\n\n## Update Store Hours (and Store Header)\n\nThe store hours block and the store name/address/phone header at the top of the customer receipt all come from a single screen: **Update Store Info**. If your hours change for the season or you move locations, this is where to update it.\n\nFrom the admin panel, tap `Update Store Info`.\n\n![Update Store Info screen with store name, address, phone, and three hours fields](/images/support/articles/kiosk_update_store_info.jpg)\n\nThe screen shows your full store identity. The fields that print on the customer receipt are:\n\n| Field | What It Prints |\n|---|---|\n| `Store Name` | Big headline at the top of the customer receipt |\n| `Store Address`, `Store City`, `Store State`, `Store Zipcode` | Address block under the store name |\n| `Store Telephone` | Phone line below the address |\n| `Store Hours1` | First line of the **Store Hours:** section |\n| `Store Hours2` | Second line of the **Store Hours:** section |\n| `Store Hours3` | Third line of the **Store Hours:** section (leave blank if not needed) |\n\n### Tips for Formatting Store Hours\n\nThe three hours fields print as-is on three separate lines under the \"Store Hours:\" heading. Common patterns:\n\n- **Two ranges** (most common): `Mon-Sat 10:00am to 8:00pm` on line 1, `Sun 1:00pm to 6:00pm` on line 2, line 3 blank.\n- **Single range**: `Mon-Sun 10:00am to 9:00pm` on line 1, lines 2 and 3 blank.\n- **Daily detail**: Use all three lines, e.g., `Mon-Thu 10:00am to 8:00pm`, `Fri-Sat 10:00am to 9:00pm`, `Sun 12:00pm to 6:00pm`.\n\n> **Tip:** Use consistent formatting (e.g., always `10:00am` or always `10 AM`, but not both). Look at your current customer receipt as a style guide before editing.\n\nWhen done, tap `Submit` to save or `Cancel` to discard.\n\n> **Warning:** The `Update Store Info` screen contains additional toggles (text alerts, designer handbag processing, SMS Club, loyalty program, etc.) that affect more than just the receipt. If you\'re only changing hours or address, leave the other settings alone unless you know what they do.\n\n---\n\n## Update the Customer Receipt (the 2nd receipt)\n\nThe customer receipt is the second one that prints after a completed buy. It\'s the copy that goes home with the seller and is the one your customer is most likely to read.\n\nFrom the admin panel, tap `Update Customer Receipt`.\n\n![Update Customer Receipt screen](/images/support/articles/kiosk_update_customer_receipt.jpg)\n\nYou\'ll see two editable blocks:\n\n1. **Customer Receipt Pick Up Language** — Instructions about claiming any items you passed on. Example: *\"Customer must pick up clothes the same day or they will be donated and payment made by store credit.\"*\n2. **Customer Receipt Footer Language** — A closing line printed at the very bottom. Example: *\"Thank you for bringing your items in today.\"*\n\nAfter editing, you have three options:\n\n- `Print` — Prints a test receipt so you can verify line breaks and spacing before saving.\n- `Submit` — Saves your changes.\n- `Cancel` — Discards changes.\n\n> **Warning:** Keep each line under **60 characters** or the printer will cut off the right side of your text.\n\n---\n\n## Update Pass Reasons\n\nPass reasons are the short labels your team circles on the store receipt when declining an item during a buy (for example, \"Excessive Wear\", \"Light Staining\", \"Older Styles\"). They show up on the store receipt so the customer understands *why* an item didn\'t make the cut.\n\nFrom the admin panel, tap `Update Pass Reasons`.\n\n![Update Pass Reasons screen with 10 reason fields](/images/support/articles/kiosk_update_pass_reasons.jpg)\n\n- Enter up to **10 reasons**.\n- Keep each phrase under **20 characters** to avoid printer cutoff.\n- Good examples: `Excessive Wear`, `Light Staining`, `Older Styles`, `High Inventory`, `Missing Pieces`.\n- Tap `Submit` to save or `Cancel` to discard.\n\n> **Tip:** Use phrases your team actually says out loud. The shorter and clearer the reason, the better the customer experience when they read the printout.\n\n---\n\n## Update Things We Liked\n\nThis is the positive companion to pass reasons — short, encouraging comments about what stood out about the customer\'s items. They print on the store receipt and give your team quick options to highlight strong brands, condition, or current trends.\n\nFrom the admin panel, tap `Update Things We Liked`.\n\n![Update Things We Liked screen](/images/support/articles/kiosk_update_things_liked.jpg)\n\n- Enter up to **10 positive comments**.\n- Keep each phrase under **20 characters**.\n- Good examples: `Cute Stuff`, `Current Styles`, `Brands We Love`, `High Need Items`, `Great Condition`.\n- Tap `Submit` to save or `Cancel` to discard.\n\n---\n\n## Update Most Wanted Items\n\nMost wanted items are the categories you\'re actively buying right now — the things your team wishes customers would bring in. Unlike the other lists, you can choose **which receipt(s)** they print on so customers know what to bring next time.\n\nFrom the admin panel, tap `Most Wanted Items`.\n\n![Most Wanted Items screen with receipt destination selector](/images/support/articles/kiosk_update_most_wanted.jpg)\n\n1. Enter up to **10 items** (under **20 characters** each). Examples: `High Chairs`, `Halloween Costumes`, `Strollers`, `Walkers`, `Jumperoos`.\n2. Choose where they print using the `Print Store Needs on Which Receipt(s)?` selector:\n\n| Option | Behavior |\n|---|---|\n| `Neither` | Suppress the list entirely (you can keep entries saved but hide them). |\n| `Store` | Print only on the store copy (helps your team during evaluation). |\n| `Customer` | Print only on the customer copy (encourages repeat visits). |\n| `Both` | Print on both receipts. |\n\n3. Tap `Submit` to save or `Cancel` to discard.\n\n> **Tip:** Setting this to `Customer` or `Both` is a great no-cost way to drive repeat buys — sellers leave knowing exactly what your store wants next time.\n\n---\n\n## Save Your Changes and Return to Normal Kiosk Mode\n\nWhen you\'re done editing, scroll to the bottom of the admin panel and tap `Start BuyerKiosk`.\n\nThe very next customer who checks in will see the updated language on their printed receipt. There is no separate \"publish\" step — `Submit` on each screen saves immediately.\n\n---\n\n## Troubleshooting\n\n### The logo-tap gesture isn\'t opening the admin panel\n- Make sure you\'re on the **manual** driver\'s license input screen (the one with the keyboard), not the scanner camera view. Tap `Cancel Scan` first if you see the camera.\n- Tap the logo at a steady pace — too slow and the kiosk treats them as separate taps.\n- Try 10–15 taps in case one didn\'t register.\n\n### Tapping the logo took me out of the BuyerKiosk app entirely\nThis is the most common mix-up we see. The kiosk has **two different logo-tap gestures** depending on which screen you\'re on:\n\n- **From the welcome screen (English / Español)** — 10 taps **exits the app** and drops you onto Android. This is intentional, so store staff can reach Android settings or switch devices. It is *not* the admin panel.\n- **From the manual driver\'s license screen** — 10 taps **opens the admin panel password prompt**. This is the one you want.\n\nIf you ended up on Android (you can see other app icons, the home screen, or settings), relaunch BuyerKiosk, tap `English` or `Español` to start a check-in, dismiss the DL scanner with `Cancel Scan` if it appears, and *then* tap the logo on the manual DL entry screen.\n\n### I forgot the kiosk admin password\nThe kiosk admin password is `kiosk` for every store. It\'s not unique per location, so there\'s nothing to look up — just enter `kiosk` and tap `OK`.\n\n### My text got cut off on the printed receipt\n- Customer receipt lines must be under **60 characters**.\n- Pass reasons, things we liked, and most-wanted items must be under **20 characters**.\n- Use the `Print` button (where available) to test before you commit changes with `Submit`.\n\n### I changed Store Hours but the receipt still shows the old hours\n- Confirm you tapped `Submit` on the `Update Store Info` screen.\n- Confirm you exited the admin panel via `Start BuyerKiosk` — changes don\'t apply to a customer mid-check-in.\n- Reprint a receipt for a fresh customer; the previous customer\'s already-printed receipt will not reprint with new hours.\n\n### I edited a list but old text still prints\n- Confirm you tapped `Submit` (not `Cancel`) on the edit screen.\n- Confirm you exited the admin panel with `Start BuyerKiosk` — changes don\'t apply to a customer who is *already* mid-check-in.\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),
    updated_at = NOW();

-- ---- Article #95: Customize the Customer Disclosure (slug=customize-customer-disclosure, status=published) ----
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 = 'kiosk' LIMIT 1),
    'Customize the Customer Disclosure',
    'customize-customer-disclosure',
    'The **disclosure** is the legal language a customer agrees to during the kiosk check-in — covering item ownership, designer authenticity, SMS opt-in terms, and other disclaimers your store needs in writing. Each store can edit the English and Spanish disclosure text independently from the kiosk\'s hidden admin panel. This article walks through how to reach that panel and update either language.\n\n![Update Disclosure screen with English/Spanish selector](/images/support/articles/kiosk_update_disclosure.jpg)\n\n*The Update Disclosure screen, showing the English version of the default text.*\n\n> **Note:** The disclosure is shown to the customer **on screen during check-in** for them to acknowledge — it does not print on the receipt. For receipt content, see [Customize the Printed Receipt](/support/articles/customize-printed-receipt).\n\n---\n\n## How to Reach the Kiosk Admin Panel\n\nThe admin panel is intentionally hidden from customers. Getting in is a four-step process.\n\n### Step 1 — Start a Check-In\n\nFrom the main kiosk screen, tap `English` or `Español` to begin a check-in.\n\n![BuyerKiosk main screen with English and Spanish buttons](/images/support/articles/kiosk_first_screen.jpg)\n\n*The starting screen customers see when they walk up to the kiosk.*\n\n### Step 2 — Get to the Manual Driver\'s License Screen\n\nYou need to land on the **manual** driver\'s license input screen — the one with a keyboard, *not* the scanner camera view.\n\n- **If your store has a DL scanner**, the scanning screen appears first. Tap `Cancel Scan` at the bottom to switch to manual entry.\n\n  ![Driver\'s license scanner screen with Cancel Scan button](/images/support/articles/kiosk_drivers_license_scan.jpg)\n\n- **If your store does not have a scanner**, the manual entry screen appears automatically.\n\nYou should now see a screen titled \"Please sign in...\" with a `Drivers License #` field and a state dropdown.\n\n![Manual driver\'s license entry screen — tap the logo at the top 10 times](/images/support/articles/kiosk_drivers_license_manual.jpg)\n\n*This is the screen where the hidden gesture works. Note the store logo banner at the top.*\n\n### Step 3 — Tap the Logo 10 Times\n\n> **Warning:** This gesture **only works on the manual driver\'s license screen** (the one with the keyboard). If you tap the logo from the very first English/Spanish welcome screen, it intentionally exits the BuyerKiosk app and drops you onto Android — that\'s a separate \"escape hatch\" for store staff who need to reach Android settings, not the admin panel. If you ended up at the Android home screen or seeing app icons, you tapped from the wrong screen. Relaunch BuyerKiosk and start over at Step 1.\n\nOnce you\'re on the manual DL screen, tap the **store logo at the top of the screen** about 10 times in a row. A password prompt will appear.\n\n![Administration password prompt](/images/support/articles/kiosk_admin_password.jpg)\n\n*The password dialog. Enter your admin password and tap `OK`.*\n\n> **Tip:** The admin password is `kiosk` for all stores.\n\n### Step 4 — You\'re In\n\nAfter entering the correct password, the full **BuyerKiosk Administration Functions** menu appears.\n\n![BuyerKiosk Administration Functions menu](/images/support/articles/kiosk_admin_panel.jpg)\n\n*The admin panel — tap `Update Disclosure Info` to start editing.*\n\n---\n\n## Update the Disclosure\n\nFrom the admin panel, tap `Update Disclosure Info`.\n\n![Update Disclosure screen with English/Spanish selector](/images/support/articles/kiosk_update_disclosure.jpg)\n\n### 1. Choose the Language\n\nAt the top of the screen, select either `English` or `Spanish`. Each language is stored separately — what you type in English will not affect Spanish, and vice versa.\n\n### 2. Edit the Body Text\n\nThe text area below shows the current disclosure. Edit it to match your store\'s legal language. The default BuyerKiosk disclosure covers:\n\n- **A)** Seller represents lawful ownership of the items.\n- **B)** Seller has disclosed defects and warrants that designer items are authentic (not replicas/counterfeits) and agrees to reimburse the Company if any are later determined to be fakes.\n- **C)** Terms for the SMS messaging program — opt-in/opt-out, message rates, and carrier disclaimers.\n\nReplace or add to these sections to fit your store\'s policies.\n\n### 3. Use the Action Buttons\n\n| Button | What It Does |\n|---|---|\n| `Cancel` | Discards your edits and returns to the admin panel. |\n| `Load Default` | Restores the BuyerKiosk default disclosure for the **currently selected language**. Useful if you\'ve made edits you want to undo. |\n| `Print` | Prints a test copy of the current disclosure text so you can preview formatting. |\n| `Submit` | Saves your changes for the currently selected language. |\n\n### 4. Repeat for the Other Language\n\nAfter saving English, switch the radio button at the top to `Spanish` and repeat. **Editing English does not auto-translate or update the Spanish version.**\n\n---\n\n## Save Your Changes and Return to Normal Kiosk Mode\n\nWhen you\'re finished, scroll to the bottom of the admin panel and tap `Start BuyerKiosk`. The next customer who checks in (in either language) will see your updated disclosure on screen.\n\n> **Warning:** Disclosure language often has legal implications — material changes (especially around authenticity warranties, SMS compliance, or refund policy) should be reviewed by your legal contact before going live.\n\n---\n\n## Troubleshooting\n\n### My English changes saved but Spanish customers still see the old text\n- Each language is stored independently. Switch the radio button at the top to `Spanish`, edit, and tap `Submit` again.\n\n### `Load Default` overwrote my custom edits\n- `Load Default` does exactly that — it restores the BuyerKiosk default for the selected language and replaces whatever was there. Re-type your changes and tap `Submit` to save (do not tap `Load Default` first if you want to keep your existing text).\n\n### The disclosure isn\'t showing up during check-in\n- Make sure you exited the admin panel via `Start BuyerKiosk` rather than just navigating away — changes are not applied to a customer who is mid-check-in.\n- Verify your kiosk is online (look for the \"Online\" indicator on the main screen).\n\n### The logo-tap gesture isn\'t opening the admin panel\n- Make sure you\'re on the **manual** DL entry screen (with the keyboard), not the scanner camera view. Tap `Cancel Scan` first if you see the camera.\n- Tap the logo at a steady pace — too slow and the kiosk treats them as separate taps.\n- Try 10–15 taps in case one didn\'t register.\n\n### Tapping the logo took me out of the BuyerKiosk app entirely\nThis is the most common mix-up we see. The kiosk has **two different logo-tap gestures** depending on which screen you\'re on:\n\n- **From the welcome screen (English / Español)** — 10 taps **exits the app** and drops you onto Android. This is intentional, so store staff can reach Android settings or switch devices. It is *not* the admin panel.\n- **From the manual driver\'s license screen** — 10 taps **opens the admin panel password prompt**. This is the one you want.\n\nIf you ended up on Android (you can see other app icons, the home screen, or settings), relaunch BuyerKiosk, tap `English` or `Español` to start a check-in, dismiss the DL scanner with `Cancel Scan` if it appears, and *then* tap the logo on the manual DL entry screen.\n\n### I forgot the kiosk admin password\nThe kiosk admin password is `kiosk` for every store. It\'s not unique per location, so there\'s nothing to look up — just enter `kiosk` and tap `OK`.\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),
    updated_at = NOW();

-- ---- Article #21: QuickBooks Online Integration (slug=quickbooks-online-integration, status=archived) ----
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 = 'quickbooks' LIMIT 1),
    'QuickBooks Online Integration',
    'quickbooks-online-integration',
    'Connect your BuyerKiosk account to QuickBooks Online to automatically sync your daily sales data, eliminating manual data entry and keeping your books accurate.\n\n![QuickBooks Connected Overview](/images/support/articles/quickbooks_connected_full.png)\n\n*The QuickBooks integration setup page showing a connected account with account mappings*\n\n***\n\n## What This Integration Does\n\nThe QuickBooks integration automatically creates **journal entries** in your QuickBooks Online account from your daily close data. This includes:\n\n| Data Type | What Gets Synced |\n| --------- | ---------------- |\n| **Payment Methods** | Cash, credit cards, checks, gift cards, store credits |\n| **Sales & Returns** | New merchandise sales, used merchandise sales, returns |\n| **Cost of Goods Sold** | COGS for new items, used items, and returns |\n| **Inventory Purchases** | Buy transactions (cash, check, trade-in, store credit) |\n| **Cash Management** | Cash over/short, paid in, paid out |\n\nEach field from your daily close report maps to a specific account in your QuickBooks chart of accounts, ensuring transactions are recorded exactly where they belong.\n\n***\n\n## Getting Started\n\n### Requirements\n\nBefore connecting, make sure you have:\n\n* ✅ A QuickBooks Online account (Simple Start, Essentials, Plus, or Advanced)\n* ✅ Admin access to your BuyerKiosk store\n* ✅ Your QuickBooks chart of accounts set up with the accounts you\'ll need\n\n### Accessing the Integration\n\n1. Log into your BuyerKiosk admin panel\n2. Click **Integrations** in the left sidebar\n3. Select **QuickBooks**\n\n![Navigation to QuickBooks](/images/support/articles/quickbooks_sidebar_navigation.png)\n\n*Find QuickBooks under Integrations in the sidebar menu*\n\n***\n\n## Connecting to QuickBooks\n\n### Step 1: Start the Connection\n\nWhen you first visit the QuickBooks setup page, you\'ll see the \"Not Connected\" status. Click the `Connect to QuickBooks` button to begin.\n\n![Not Connected State](/images/support/articles/quickbooks_not_connected.png)\n\n*The QuickBooks page before connecting*\n\n### Step 2: Sign In to Intuit\n\nA popup window will open to Intuit\'s secure login page. Sign in with your QuickBooks Online credentials.\n\n### Step 3: Select Your Company\n\nIf you have multiple QuickBooks companies, select the one you want to connect to BuyerKiosk.\n\n![Company Selection](/images/support/articles/quickbooks_oauth_company_select.png)\n\n*Select which QuickBooks company to connect*\n\n### Step 4: Authorize Access\n\nReview the permissions BuyerKiosk is requesting and click **Connect** to authorize. BuyerKiosk requests permission to:\n\n* Read your chart of accounts\n* Create journal entries\n* View company information\n\nOnce authorized, the popup will close and your BuyerKiosk page will refresh to show the connected status.\n\n***\n\n## Configuring Account Mappings\n\nAfter connecting, you need to map each BuyerKiosk field to the appropriate QuickBooks account. This is the most important step - proper mappings ensure your books are accurate.\n\n![Account Mappings Overview](/images/support/articles/quickbooks_connected_full.png)\n\n*The account mappings section with all categories*\n\n### Understanding the Interface\n\nEach mapping row shows:\n\n| Element | Description |\n| ------- | ----------- |\n| **Field Name** | The data field from your daily close (e.g., \"Cash Tendered\") |\n| **Field ID** | The technical identifier (e.g., `cashTendered`) |\n| **DR/CR Badge** | Whether this creates a debit or credit entry |\n| **Account Dropdown** | Select the QuickBooks account to map to |\n\n### Selecting Accounts\n\nClick any dropdown to see your available QuickBooks accounts, organized by account type:\n\n![Account Selection Dropdown](/images/support/articles/quickbooks_account_dropdown.png)\n\n*The dropdown shows all your QuickBooks accounts organized by type*\n\n> **Tip:** Accounts are grouped by type (Bank, Accounts Payable, Cost of Goods Sold, etc.) to help you find the right one quickly.\n\n### Mapping Categories\n\nFields are organized into categories to make setup easier:\n\n#### Payment Methods (Debits to Asset Accounts)\n\nThese track where your money comes from:\n\n* American Express, Visa, MasterCard, Discover → Credit Card in Transit\n* Cash Tendered → Cash Drawer\n* Check Payments → Checking Account\n* Gift Cards Redeemed → Gift Cards Payable\n\n#### Sales & Returns (Credits to Revenue Accounts)\n\n* New Merchandise Sales → Sales - New\n* Used Merchandise Sales → Sales - Used\n* Returns → Returns accounts\n\n#### Cost of Goods Sold (Debits to COGS Accounts)\n\n* Cost of New Goods Sold → COGS - New\n* Cost of Used Goods Sold → COGS - Used\n\n#### Inventory Purchases - Buys (Inventory/Cash Accounts)\n\n* Buys Paid - Cash → Cash Drawer\n* Buys Paid - Check → Checking Account\n* Buys Paid - Store Credit → Store Credits\n* Buys Paid - Trade-ins → Trade Credits\n\n#### Cash Management\n\n* Cash Over/Short → Cash Over/Short\n* Cash Paid In/Out → Cash Drawer\n\n### Saving Your Mappings\n\nAfter configuring all mappings, click `Save All Mappings` in the bottom right. You\'ll see a confirmation when saved successfully.\n\n> **Warning:** Make sure all important fields are mapped before your first sync. Unmapped fields with values will be skipped, which could create unbalanced entries.\n\n***\n\n## Syncing Data\n\n### Automatic Daily Sync\n\nOnce connected and configured, BuyerKiosk automatically syncs your daily close data to QuickBooks. This happens:\n\n* After you complete your daily close in BuyerKiosk\n* Via a background job that runs overnight\n\n### Manual Sync\n\nNeed to sync data for a specific date? Use the Manual Sync feature:\n\n1. In the **Manual Sync** section, select a date using the date picker\n2. Click `Sync Now`\n3. Wait for the sync to complete\n\n![Manual Sync Section](/images/support/articles/quickbooks_connected_account.png)\n\n*The Manual Sync section lets you sync any historical date*\n\n> **Note:** Manual sync is useful for re-syncing a date if there was an error, or for syncing historical data when you first set up the integration.\n\n### Viewing Sync History\n\nThe **Recent Sync History** section shows your last 10 sync attempts with status indicators:\n\n| Status | Meaning |\n| ------ | ------- |\n| 🟢 Green border | Sync completed successfully |\n| 🔴 Red border | Sync failed (hover for error message) |\n| 🟡 Yellow border | Sync pending or in progress |\n\n***\n\n## Managing Your Connection\n\n### Viewing Connection Status\n\nThe **Connected Account** section shows:\n\n* **Company**: Your QuickBooks company name\n* **Realm ID**: QuickBooks\' internal identifier\n* **Last Sync**: When data was last synced successfully\n\n### Disconnecting QuickBooks\n\nIf you need to disconnect:\n\n1. Click the red `Disconnect` button\n2. Confirm when prompted\n\n> **Warning:** Disconnecting will stop all automatic syncing. Your historical data in QuickBooks is not affected.\n\nAfter disconnecting, you can reconnect at any time by clicking \"Connect to QuickBooks\" again.\n\n***\n\n## Troubleshooting\n\n### Sync Failed - \"No daily close data found\"\n\n**Cause:** The date you\'re trying to sync doesn\'t have completed daily close data.\n\n**Solution:** Make sure your daily close is complete for that date before syncing.\n\n### Sync Failed - \"Journal entry unbalanced\"\n\n**Cause:** The total debits don\'t equal total credits (within rounding tolerance).\n\n**Solution:** Review your account mappings. Make sure all fields with values are mapped to appropriate accounts.\n\n### \"Connection expired\" Message\n\n**Cause:** Your QuickBooks authorization has expired (tokens last about 180 days).\n\n**Solution:** Click \"Connect to QuickBooks\" to re-authorize. Your mappings will be preserved.\n\n### Can\'t See Some QuickBooks Accounts\n\n**Cause:** Only active accounts appear in the dropdowns.\n\n**Solution:** In QuickBooks, make sure the accounts you need are marked as active (not hidden or inactive).\n\n***\n\n## Best Practices\n\n1. **Set up your QuickBooks chart of accounts first** \\- Create all the accounts you\'ll need before connecting\n2. **Map all fields** \\- Even if you don\'t use certain payment methods\\, map them in case they\'re used later\n3. **Review your first few syncs** \\- Check the journal entries in QuickBooks to make sure they look correct\n4. **Keep the same mappings** \\- Changing mappings mid\\-year can create inconsistencies in your reports',
    NULL,
    COALESCE((SELECT id FROM kiosk_users.users WHERE username = 'rvanvuren' LIMIT 1), 1),
    'archived',
    0,
    0,
    NOW(), NOW()
)
ON DUPLICATE KEY UPDATE
    content_md = VALUES(content_md),
    content_html = NULL,
    category_id = VALUES(category_id),
    updated_at = NOW();

-- ---- Article #39: QuickBooks Online Integration (slug=quickbooks-integration, status=published) ----
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 = 'quickbooks' LIMIT 1),
    'QuickBooks Online Integration',
    'quickbooks-integration',
    '# QuickBooks Online Integration\n\nConnect your BuyerKiosk store to QuickBooks Online and automatically sync daily DRS close data as journal entries — no manual data entry, accurate books, full audit trail.\n\n![QuickBooks Dashboard](/images/support/articles/quickbooks_dashboard_overview.png)\n\n*Your QuickBooks landing page — connection status, action items, and recent activity all in one view.*\n\n---\n\n## What this integration does\n\nThe integration creates **journal entries** in your QuickBooks Online company from your daily DRS close report. Every payment method, every sale, every return, every buy lands in the right account automatically.\n\n| Data | Examples |\n| ---- | -------- |\n| **Payments received** | Cash, credit cards, debit, checks, gift card redemptions, store credit redemptions |\n| **Sales & returns** | New merchandise, used merchandise, returns (new + used), sales tax |\n| **Cost of goods sold** | COGS for new items, used items, and returns |\n| **Buys (inventory purchases)** | Buys paid in cash, check, store credit, trade-ins, offsite, digital, gift cards |\n| **Cash drawer** | Cash paid in, cash paid out, cash over/short |\n| **Other** | Gift cards issued, store credits issued, freight collected, continuing fees |\n\nEach field maps to a specific account in your QuickBooks chart of accounts, posted with the right debit/credit treatment.\n\n---\n\n## The journey — five pages, one workflow\n\nThe integration spans seven admin pages. They\'re presented in roughly the order you\'ll use them:\n\n1. **[Connect & Configure](/support/article/quickbooks-connect-and-configure)** — link your QBO company, choose sync mode, set thresholds, customize the memo template.\n2. **[Account Mapping](/support/article/quickbooks-account-mapping)** — tell the system which DRS field posts to which QBO account. Includes AI-powered auto-mapping for fast setup.\n3. **[Dashboard](/support/article/quickbooks-dashboard)** — your daily landing page. Connection status, things needing attention, recent activity.\n4. **[Daily Approval Workflow](/support/article/quickbooks-daily-approval-workflow)** — review, edit, approve (or reject) each day\'s staged journal entry before it posts.\n5. **[Sync Log](/support/article/quickbooks-sync-log)** — unified history of every posted entry (auto + manual) with deep-links to QBO.\n6. **[Audit Log](/support/article/quickbooks-audit-log)** — append-only event history for compliance and debugging.\n7. **[Reconciliation](/support/article/quickbooks-reconciliation)** — periodic accuracy check that posted totals match DRS source data.\n\n---\n\n## Two ways to operate\n\nChoose one when you set up:\n\n| Mode | When to use | What happens |\n| ---- | ----------- | ------------ |\n| **Manual Approval** *(default, recommended)* | Always for the first 30 days. Long-term for stores that want eyes on every entry. | System stages each day\'s JE; you review and click Approve. Nothing posts to QBO without your action. |\n| **Automatic** | After your mappings are stable and variance is consistent. | Each night the system posts directly to QBO with no review step. |\n| **Disabled** | Pausing the integration without disconnecting. | Nightly job skips this store entirely. |\n\nYou can switch modes any time in **QuickBooks > Settings > Sync Mode**.\n\n---\n\n## Who can use this\n\nAdmins only. The Manager, Shift Lead, Buyer, and Cashier roles don\'t have QuickBooks access.\n\n---\n\n## What you need before starting\n\n- A QuickBooks Online subscription (Simple Start, Essentials, Plus, or Advanced — all work)\n- An Admin role in your BuyerKiosk store\n- A chart of accounts in QBO with the accounts you\'ll need (Cash, Sales Income, Sales Tax Payable, Cost of Goods Sold, Inventory Asset, Cash Over/Short — at minimum)\n\nIf your chart of accounts isn\'t ready, set it up in QBO first. Mapping uses your live chart, so a missing account is a missing dropdown option.\n\n---\n\n## Quick start\n\n1. **[Connect](/support/article/quickbooks-connect-and-configure#step-1--connect-to-quickbooks-online)** — go to QuickBooks > Settings, click Connect to QuickBooks, complete the Intuit OAuth flow.\n2. **[Map](/support/article/quickbooks-account-mapping)** — go to Account Mapping. Click Auto-map to fill in obvious matches, then review each row and tweak as needed. Mark any payment methods your store doesn\'t use as \"Not used at this store.\"\n3. **[Approve your first entry](/support/article/quickbooks-daily-approval-workflow#approving-an-entry)** — the next morning, go to the Approval Queue. Review the entry, check the totals, click Approve & Post.\n4. **[Verify in QBO](/support/article/quickbooks-sync-log#doc--is-a-qbo-link)** — open the Sync Log, click the Doc # of the posted entry. It opens directly in QuickBooks Online so you can confirm everything landed correctly.\n\n---\n\n## Help & support\n\nIf you run into issues:\n\n- **Dashboard \"Needs your attention\" panel** is the first place to look — it surfaces unmapped critical fields, pending approvals, and recent failures with one-click paths to fix.\n- **Audit Log** answers \"who/when/what\" questions for any action that happened on this store.\n- **Reconciliation** confirms what\'s actually in QBO matches what you have in BuyerKiosk.\n\nFor configuration help beyond what\'s documented here, contact BuyerKiosk support with your store typeNum.\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),
    updated_at = NOW();

-- ---- Article #87: QuickBooks: Connect & Configure (slug=quickbooks-connect-and-configure, status=published) ----
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 = 'quickbooks' LIMIT 1),
    'QuickBooks: Connect & Configure',
    'quickbooks-connect-and-configure',
    'The QuickBooks Online integration syncs your daily DRS close data to QuickBooks as journal entries. This article covers the one-time connection setup and ongoing configuration — choosing a sync mode, setting variance thresholds, and picking a memo template.\n\n![QuickBooks Settings overview](/images/support/articles/quickbooks_settings_overview.png)\n\n*The Settings page — connection, sync mode, approval thresholds, and memo template all in one place.*\n\n> **Who can use this:** Admins only. The Manager, Shift Lead, Buyer, and Cashier roles don\'t have QuickBooks access.\n\n---\n\n## Where to find it\n\nFrom any admin page, open **QuickBooks** in the left sidebar and click **Settings**. Direct URL: `/admin/{typeNum}/quickbooks/settings`.\n\n---\n\n## Step 1 — Connect to QuickBooks Online\n\n1. On the Settings page, find the **Connection** card at the top.\n2. If you\'re not connected yet you\'ll see a red dot and a `Connect to QuickBooks` button. Click it.\n3. You\'ll be redirected to Intuit\'s OAuth screen. Sign in with the bookkeeping account, choose the company file, and click **Connect**.\n4. You\'ll come back to the Settings page automatically. The Connection card now shows a green dot, the company name, and your QBO Realm ID.\n\n> **Tip:** The same Intuit account can be connected to multiple BuyerKiosk stores. Each store keeps its own tokens and its own sync settings.\n\nIf you ever need to reconnect (because Intuit invalidated tokens, or you swapped company files), use `Reconnect` next to the green dot. To unlink entirely, use `Disconnect` — note that disconnecting does **not** delete prior journal entries from QBO.\n\n---\n\n## Step 2 — Choose a sync mode\n\n![Sync mode options](/images/support/articles/quickbooks_settings_sync_mode.png)\n\nThe **Sync Mode** controls how journal entries reach QuickBooks. There are three options:\n\n| Mode | What happens each night |\n| ---- | ---------------------- |\n| **Manual Approval** *(default)* | The system stages a journal entry for the day and waits for you to review it in the Approval Queue. Nothing posts to QBO until you click `Approve`. |\n| **Automatic** | Entries post directly to QBO every night with no review step. |\n| **Disabled** | The nightly job skips this store entirely. No staging, no posting. |\n\n> **Recommendation:** Start in `Manual Approval` for the first 30 days. Once you\'ve confirmed the entries look right and your variance is consistent, switch to `Automatic` if you want hands-off operation.\n\n> **Warning:** You cannot switch to `Automatic` while there are pending entries in the Approval Queue. Approve or reject them first.\n\n---\n\n## Step 3 — Set the reminder cadence\n\nUnder Sync Configuration, two settings control reminders for pending approvals:\n\n- **Reminder Cadence** — how often the system can email/notify you about pending approvals. Options: Daily, Weekly, Monthly, On Demand.\n- **Pending Reminder Threshold** — only send a reminder once an entry has been pending for this many days. Default: 3 days.\n\nIf you tend to approve daily, set the threshold to 1. If you batch-approve weekly, set it to 7.\n\n---\n\n## Step 4 — Configure re-sync behavior\n\nThe **Re-Sync Behavior** dropdown controls what happens when the close data changes for a date you already synced:\n\n- **Require Manual Decision** *(default)* — flag the day for review; you decide whether to void & repost, edit, or accept the change.\n- **Void & Repost Automatically** — automatically void the prior JE and post a new one.\n- **Update In Place** — overwrite the existing JE without voiding (advanced; only safe in some accounting workflows).\n\nWhen in doubt, leave on `Require Manual Decision`.\n\n---\n\n## Step 5 — Set the approval thresholds\n\n![Approval thresholds](/images/support/articles/quickbooks_settings_approval_thresholds.png)\n\nThe **Approval thresholds** section controls how much cash-drawer variance the system tolerates before stopping to ask.\n\nEvery daily close has a cash over/short value — the difference between what the drawer counted and what the system expected. Two thresholds gate how that variance is handled:\n\n| Threshold | Default | What it does |\n| --------- | ------- | ------------ |\n| **Auto-approve cap** | `$10.00` | Variance up to this amount **auto-posts** to QuickBooks. The system silently plugs the difference to your Cash Over/Short account on approval. |\n| **Manual-review cap** | `$100.00` | Variance between auto-cap and hard-cap **requires a checkbox confirmation** in the Approve modal before the entry can post. |\n| (above hard-cap) | — | Variance over the manual-review cap is **hard-blocked**. The entry cannot post until you correct the underlying data or raise the cap. |\n\n> **Why three tiers?** Normal drawer count differences are tiny — $1-2 from rounding or a mis-keyed coin. Above ~$10 it\'s worth a second look. Above ~$100 it almost always points to a mapping problem (a payment method posting to the wrong account) rather than a real cash variance.\n\nIf you tighten the auto-cap (e.g. $5), more entries land in the manual-review tier — useful when you\'re new to the integration and want eyes on every variance. If you raise the hard-cap (e.g. $250), the system stops blocking on legitimate-but-large variances but trusts you to catch mapping issues yourself.\n\nThe page shows a soft warning if you set the manual-review cap lower than the auto-approve cap (which doesn\'t make sense logically) — you can save anyway, but the system falls back to the defaults at read time when the relationship is broken.\n\n---\n\n## Step 6 — Customize the memo template\n\n![Memo template with preview](/images/support/articles/quickbooks_settings_memo_template.png)\n\nEvery journal entry posted to QBO gets a memo (PrivateNote in the Intuit API). The default template is:\n\n```\nDaily Sales - {companyName} ({typeNum}) - {date}\n```\n\nYou can customize it with these placeholders:\n\n| Placeholder | Resolves to |\n| ----------- | ----------- |\n| `{typeNum}` | Store identifier (e.g. `pc00`) |\n| `{date}` | Sync date in `YYYY-MM-DD` |\n| `{companyName}` | The store\'s display name |\n| `{syncType}` | `daily_close`, `manual`, or `retry` |\n\nThe preview under the memo field shows what the next entry will look like. Memos are limited to 200 characters.\n\n---\n\n## What\'s next\n\nOnce you\'re connected:\n\n1. **[Set up account mappings](/support/article/quickbooks-account-mapping)** — tell the system which DRS field posts to which QuickBooks account. The integration cannot post until critical mappings are complete.\n2. **[Review the Dashboard](/support/article/quickbooks-dashboard)** — your at-a-glance view of connection status, pending approvals, and recent activity.\n3. **[Walk through your first daily approval](/support/article/quickbooks-daily-approval-workflow)** — see what a staged journal entry looks like and how to approve, edit, or reject it.\n\n---\n\n## Related articles\n\n- [QuickBooks Online Integration overview](/support/article/quickbooks-integration)\n- [Account Mapping](/support/article/quickbooks-account-mapping)\n- [Daily Approval Workflow](/support/article/quickbooks-daily-approval-workflow)\n- [Dashboard](/support/article/quickbooks-dashboard)\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),
    updated_at = NOW();

-- ---- Article #88: QuickBooks: Daily Approval Workflow (slug=quickbooks-daily-approval-workflow, status=published) ----
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 = 'quickbooks' LIMIT 1),
    'QuickBooks: Daily Approval Workflow',
    'quickbooks-daily-approval-workflow',
    'The Approval Queue is where you review, edit, and post the daily journal entries the system has staged from your DRS data. In Manual Approval mode (the default and recommended sync mode), no journal entry reaches QuickBooks until you act on it here.\n\n![Approval Queue overview](/images/support/articles/quickbooks_queue_overview.png)\n\n*The Approval Queue — each row is one business day\'s staged journal entry, with status, doc number, totals, and per-row actions.*\n\n> **Who can use this:** Admins only. The Manager, Shift Lead, Buyer, and Cashier roles don\'t have QuickBooks access.\n\n---\n\n## Where to find it\n\nFrom the admin sidebar: **QuickBooks** > **Approval Queue**. Direct URL: `/admin/{typeNum}/quickbooks/approval-queue`.\n\nThe Dashboard\'s \"Needs your attention\" panel surfaces a pending-approvals card with a shortcut. The page header also shows an amber **pending count chip** when there\'s work to do — at a glance, you know if the queue needs you.\n\n![Pending count chip in the page header](/images/support/articles/quickbooks_queue_pending_chip.png)\n\n---\n\n## How an entry gets here\n\nEvery night, BuyerKiosk reads your DRS daily close data and builds a balanced journal entry (debits = credits). In Manual Approval mode the entry is **staged** — written to a queue table with a `pending_approval` status and the full payload preserved — instead of being posted to QBO directly.\n\nYou\'ll see one row per business date.\n\n---\n\n## Reading the queue\n\nEach row shows:\n\n| Column | Meaning |\n| ------ | ------- |\n| Date | The business date this entry covers (DRS close date) |\n| Status | `pending_approval`, `approved`, `posted`, `rejected`, or `voided` |\n| Doc Number | The DocNumber that will be sent to QBO (format `BK-{typeNum}-{yyyymmdd}`) |\n| Total Debits / Credits | If they don\'t match, the difference is the variance — see [the balance banner](#understanding-the-balance-banner) below |\n| Actions | Per-status icon buttons (Approve, Edit, Reject, Void, Re-sync) |\n\nUse the **Status** filter above the grid to narrow the list. By default you see only `pending_approval` so the queue is focused on actionable work.\n\n---\n\n## The five statuses\n\n| Status | What it means | What you can do |\n| ------ | ------------- | --------------- |\n| `pending_approval` | Staged and waiting for review | Approve, Edit, Reject |\n| `approved` | Manually marked approved (rare — usually skipped via direct Approve & Post) | — |\n| `posted` | Successfully posted to QBO; you have a real journal entry | Void |\n| `rejected` | You said no — the original is preserved as an audit record | Re-sync (creates a fresh staged entry) |\n| `voided` | Was posted, then voided in QBO | — |\n\n---\n\n## Approving an entry\n\n![Row action buttons — Approve / Edit / Reject](/images/support/articles/quickbooks_queue_row_actions.png)\n\n*The three pending-row actions sit as compact icon buttons. Hover any one to see its color (green for Approve, red for Reject). The check icon already hints green even at rest.*\n\n1. Click the ✓ `Approve` button on a `pending_approval` row.\n2. The Approve & Post modal opens. Review the journal lines, totals, and the QBO target accounts.\n3. Review the **balance preview banner** — see [Understanding the balance banner](#understanding-the-balance-banner) below.\n4. Click `Approve & Post`. The system calls QBO and posts the journal entry.\n5. On success the row flips to `posted` and gets a QBO Journal Entry ID. The doc number in the Sync Log now links directly to the JE in QuickBooks Online.\n\n![Approve & Post modal](/images/support/articles/quickbooks_queue_approve_modal.png)\n\n*The Approve & Post modal: totals summary, balance preview banner (when variance exists), and the post button.*\n\n> **Tip:** Chronological ordering is enforced. You cannot approve March 5 if March 3 is still `pending_approval`. **Approve oldest first.** See [Stuck on chronological order](#stuck-on-chronological-order) below for the escape valve.\n\nIf QBO returns an error (period closed, account no longer exists, rate limit) the entry stays `pending_approval` and a clear toast appears with the actual error — fix the cause and try again.\n\n---\n\n## Understanding the balance banner\n\nWhen the staged journal entry\'s debits don\'t equal its credits, the system needs to add a balancing line (\"plug\") to make the entry valid in QuickBooks. The Approve modal makes this explicit so you always know what will post.\n\nThe plug goes to your `Cash Over/Short` account (the account configured for `cashOverShort` in Account Mapping). The amount is the difference between debits and credits.\n\n**Three banner colors based on the size of the imbalance:**\n\n| Banner | Imbalance range | What it means | Approve & Post button |\n| ------ | --------------- | ------------- | --------------------- |\n| (none) | ≤ $0.01 | Entry is balanced as-is. No plug needed. | Enabled |\n| 🟦 Blue (info) | ≤ auto-cap | Small imbalance — system silently plugs to Cash Over/Short. Normal drawer variance lives here. | Enabled |\n| 🟨 Yellow (warning) | between caps | Larger imbalance — review carefully. **Tick the confirmation checkbox** to proceed. | Enabled only after checkbox is ticked |\n| 🟥 Red (block) | > hard-cap | Very large imbalance. Almost always a mapping configuration error. | Disabled — fix mappings first |\n\n**Default thresholds:** auto-cap `$10.00`, manual-review hard-cap `$100.00`. Adjust per store in **QuickBooks > Settings > Approval thresholds** — see [Connect & Configure](/support/article/quickbooks-connect-and-configure) for the details.\n\n> **Why the plug matters:** prior to this transparency feature, the system silently injected the plug at post time without showing it in the queue. Configuration errors (e.g. an unmapped offset account causing one side of every transaction to be missing) hid behind the plug, posting as inflated Cash Over/Short on real books. Now you see the imbalance before you approve, and large imbalances block until you\'ve checked the underlying mappings.\n\n> **Tip:** If you see a yellow or red banner consistently across multiple days, the issue is almost certainly an **account mapping problem**, not real drawer variance. Open the [Account Mapping](/support/article/quickbooks-account-mapping) page and check that critical fields have the right accounts and (if applicable) offset accounts configured.\n\n---\n\n## Editing before approval\n\n![Edit modal](/images/support/articles/quickbooks_queue_edit_modal.png)\n\n*The Edit modal: balance indicator at the top, journal lines table in the middle, adjustment lines section below.*\n\nUse ✎ `Edit` to change values before posting — useful for one-off corrections (e.g. DRS reported wrong cash count) without going back to fix source data.\n\n- Edit the line amounts in the modal.\n- If any field changes by more than $1.00 the system requires an `Edit Reason` (10+ characters) for the audit trail.\n- You cannot zero out `taxCollected` — the system blocks this to prevent accidental tax reporting bugs.\n- A maximum of 20 adjustment lines per entry.\n- After saving, the entry is still `pending_approval` — click `Approve & Post` to send the *edited* version to QBO.\n\nThe Approve modal shows a **diff preview** for edited entries — old value → new value side-by-side, so the approver can see exactly what was changed and why.\n\n---\n\n## Rejecting an entry\n\nUse 👎 `Reject` when the entry is wrong and you don\'t want it posted at all (e.g. the close report itself is bogus and you\'ll re-import).\n\n1. Click `Reject` on the row. The Reject modal opens.\n2. Enter a rejection reason (10+ characters required).\n3. Click confirm. The row\'s status becomes `rejected`.\n\nRejected rows are **preserved as audit records** — they\'re never deleted. If you reject by mistake or get corrected source data, click `Re-sync` on the rejected row (or use the Sync Log\'s `Re-sync` action) to stage a fresh entry for the same date. The audit log shows the supersession chain.\n\n---\n\n## Stuck on chronological order\n\nPRD F5 Rule 1: **entries must be approved in chronological order.** If March 3 is pending and you try to approve March 5, the system blocks with:\n\n> Cannot approve out of chronological order. Earlier pending entries must be approved first.\n\n**Why:** posting out of order can break bank reconciliation in QBO (retroactive entries into closed periods), inventory running totals, and other downstream accounting workflows. Forcing chronological order mirrors how a manual bookkeeper would process the same pile.\n\n**The escape valve — reject + re-stage:**\n\nIf March 3 has a real problem you can\'t fix in 5 minutes, you can keep moving:\n\n1. **Reject** March 3 with a clear reason (\"data needs investigation, will re-stage later\").\n2. March 4, 5, 6 are now approvable (no earlier *pending* entry).\n3. Fix the underlying issue (mis-mapped account, corrupt source file, whatever).\n4. Once fixed, re-stage March 3 via the Sync Log\'s `Re-sync this date` action — server creates a fresh staged entry. Approve it as soon as it appears.\n\nSo the rule isn\'t \"approve in order or be stuck forever\" — it\'s \"either approve, reject, or fix the earliest pending entry before moving on.\"\n\n---\n\n## Voiding a posted entry\n\nUse ⊘ `Void` when an entry was already posted to QBO and needs to be reversed.\n\n1. Click `Void` on a `posted` row.\n2. Enter a void reason (10+ characters required).\n3. Confirm. The system calls QBO\'s void endpoint and updates the local row to `voided`.\n\nVoiding in QBO does not delete the journal entry — it stamps it Void in QuickBooks (preserving the audit trail there too) so reports correctly reflect the reversal.\n\n---\n\n## Bulk Approve\n\nTo approve multiple consecutive days in one click:\n\n1. Tick the checkbox on each pending row you want to approve. The **bulk action strip** appears above the toolbar showing the selection count.\n2. Click `Approve selected` in the strip.\n3. The Bulk Approve modal opens — confirm.\n4. Entries are processed **sequentially in chronological order**. If one fails, processing stops and you\'re told which succeeded.\n\n> **Tip:** Bulk approve is most useful after a vacation week. The chronological-order rule still applies — you can only select consecutive pending entries.\n\n---\n\n## Audit trail\n\nEverything you do here is logged to the [Audit Log](/support/article/quickbooks-audit-log):\n\n- `sync_staged` — system created the staged entry\n- `approved` → `sync_posted` — you approved, it posted to QBO\n- `sync_failed` — QBO rejected the post\n- `edited` — you modified the entry pre-approval (includes the edit reason + diff)\n- `rejected` — you rejected the entry (includes the reason)\n- `voided` — a posted entry was voided (includes the reason)\n\nThe audit log is append-only and includes user IDs, IP addresses, timestamps, and the full pre/post payload for every change.\n\n---\n\n## What\'s next\n\n- [Sync Log](/support/article/quickbooks-sync-log) — see every posted entry (auto + manual) in one history with deep-links to the JE in QBO.\n- [Audit Log](/support/article/quickbooks-audit-log) — the full event history for compliance and debugging.\n- [Reconciliation](/support/article/quickbooks-reconciliation) — periodic check that posted totals match DRS source data.\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),
    updated_at = NOW();

-- ---- Article #89: QuickBooks: Reconciliation (slug=quickbooks-reconciliation, status=published) ----
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 = 'quickbooks' LIMIT 1),
    'QuickBooks: Reconciliation',
    'quickbooks-reconciliation',
    'The Reconciliation page is your periodic accuracy check — it walks through a date range, compares your BuyerKiosk daily-close totals against what\'s actually in QuickBooks Online for each day, and reports any variance.\n\n![Reconciliation overview](/images/support/articles/quickbooks_reconciliation_overview.png)\n\n*Date range, run reconciliation, see per-day variance. The page is mostly empty until you run a check.*\n\n> **Who can use this:** Admins only. The Manager, Shift Lead, Buyer, and Cashier roles don\'t have QuickBooks access.\n\n---\n\n## Where to find it\n\nFrom the sidebar: **QuickBooks** > **Reconciliation**. Direct URL: `/admin/{typeNum}/quickbooks/reconciliation`.\n\nThe header subtitle shows your **Last reconciled** timestamp so you know when the check was last run.\n\n---\n\n## When to use it\n\nReconciliation is **not** a daily-ops tool — it\'s a periodic accuracy check. Common cadences:\n\n- **Monthly** — at the close of each accounting period, verify the month\'s QBO totals match DRS source data.\n- **Quarterly** — before quarterly statements or tax filings.\n- **After any mapping change** — to catch any \"did the change post correctly?\" issues.\n- **After an outage or restore** — to confirm no entries were silently lost.\n\nFor daily operations, the [Sync Log](/support/article/quickbooks-sync-log) and [Dashboard](/support/article/quickbooks-dashboard) are the right tools.\n\n---\n\n## Running a reconciliation\n\n![Filter toolbar](/images/support/articles/quickbooks_reconciliation_toolbar.png)\n\n1. Set the **From** and **To** dates. Default is the last 30 days; pick whatever range matters (e.g. a calendar month).\n2. Click **`Run reconciliation`**.\n\nThe system then walks through each day in the range, in chronological order:\n\n- Fetches the BuyerKiosk close totals for that day\n- Queries QuickBooks Online for the matching journal entry (by DocNumber: `BK-{typeNum}-{yyyymmdd}`)\n- Compares the totals and computes variance\n\nA progress strip shows live progress through the loop — typically a few seconds per day for cached QBO accounts, longer for ranges that span many uncached days.\n\n---\n\n## Reading the variance report\n\nWhen the reconciliation finishes, the grid populates with one row per day:\n\n| Column | Meaning |\n| ------ | ------- |\n| Date | Business date |\n| DRS Total | Sum from your daily close |\n| QBO Total | Sum from QuickBooks for the matching JE |\n| Variance | Difference (positive = DRS higher; negative = QBO higher) |\n| Status | OK (within tolerance), Variance (mismatch), Missing (no JE found in QBO), No data (DRS had no close) |\n\nDays within the configured variance tolerance show as **OK**. Days outside it (or missing entirely) are flagged.\n\n---\n\n## What variance means\n\nA perfect day reconciles to $0.00. Real days often won\'t — common variance causes in order of likelihood:\n\n1. **Voided JE** — you voided a posted entry; the QBO total is now $0 but DRS still shows the original close. This is correct behavior — variance just reflects the void.\n2. **Mid-period mapping change** — you changed a mapping after the entry posted. Earlier days used the old mapping; later days use the new one. Variance falls out of the difference.\n3. **Direct QBO edits** — someone edited the journal entry directly in QuickBooks (without going through BuyerKiosk). Always investigate these.\n4. **Missing data** — the day\'s close report had errors and the entry never staged. Status will be \"Missing\" — re-stage via Sync Log if you have the source data.\n\n---\n\n## Export\n\nClick **Export CSV** in the toolbar to download the current variance report as a CSV file. Hand to your accountant or bookkeeper for period close.\n\n---\n\n## What\'s next\n\n- [Sync Log](/support/article/quickbooks-sync-log) — re-stage a date with no QBO entry.\n- [Audit Log](/support/article/quickbooks-audit-log) — investigate when something changed and who did it.\n- [Account Mapping](/support/article/quickbooks-account-mapping) — fix mapping issues uncovered by reconciliation.\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),
    updated_at = NOW();

-- ---- Article #90: QuickBooks: Account Mapping (slug=quickbooks-account-mapping, status=published) ----
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 = 'quickbooks' LIMIT 1),
    'QuickBooks: Account Mapping',
    'quickbooks-account-mapping',
    'The Account Mapping page tells BuyerKiosk which QuickBooks Online account to use for each field on your daily DRS close report. Without complete mappings the system can\'t post journal entries, so this is the first thing to set up after connecting to QBO.\n\n![Account Mapping overview](/images/support/articles/quickbooks_mapping_overview.png)\n\n*Each DRS field on the left, mapped to a QBO account on the right. Sections group related fields (Payments, Sales & Returns, COGS, etc.).*\n\n> **Who can use this:** Admins only. The Manager, Shift Lead, Buyer, and Cashier roles don\'t have QuickBooks access.\n\n---\n\n## Where to find it\n\nFrom the sidebar: **QuickBooks** > **Account Mapping**. Direct URL: `/admin/{typeNum}/quickbooks/mapping`.\n\nThe page header shows your mapping progress (`X / Y fields mapped`) and a save button that activates when you have unsaved changes. The Dashboard\'s header shows the same count as a chip — when there are critical unmapped fields, it tints amber so you spot it at a glance.\n\n---\n\n## How mapping works\n\nYour nightly close report has ~40 financial fields (cashTendered, totalSales, taxCollected, newReturns, buyPaidCash, etc.). Each field needs to map to:\n\n1. A **QBO account** — where the value goes (e.g. `Cash`, `Sales Income`, `Sales Tax Payable`).\n2. An **entry type** — `debit` or `credit`. This is the *base* direction for a positive value.\n3. Optionally an **offset account** — when the field\'s accounting effect needs a balancing entry (e.g. cash + a corresponding revenue line).\n\nWhen the nightly job runs, the system reads each field, looks up its mapping, and emits a journal line. The lines are summed to verify debits = credits.\n\n---\n\n## Critical vs. optional fields\n\nFields are organized into **categories** that determine whether they block sync when unmapped:\n\n| Category | Examples | Sync-blocking? |\n| -------- | -------- | -------------- |\n| `payments` | cashTendered, visaActual, masterCardActual, gift card redemptions | ✅ Yes |\n| `sales` | newSales, usedSales, taxCollected, returns | ✅ Yes |\n| `cogs` | newCost, usedCost, returnsCost | ✅ Yes |\n| `buys` | buyPaidCash, buyPaidCheck, buyPaidOffsite, buyPaidDigital | ✅ Yes |\n| `cash` | cashPaidIn, cashPaidOut, cashOverShort | ✅ Yes |\n| `other` | giftCardsIssued, freightCollected, continuingFee | ✅ Yes |\n\nRows with unmapped **critical** fields show an amber ⚠️ warning icon next to the field name. Until every critical field is mapped (or marked \"Not used at this store\" — see below), the Approval Queue refuses to post.\n\n![Critical field warning](/images/support/articles/quickbooks_mapping_critical_warning.png)\n\n*The amber triangle marks a critical-category field that isn\'t mapped yet.*\n\n---\n\n## \"Not used at this store\" — the opt-out\n\nSome stores don\'t use every payment method. A store that doesn\'t take offsite buys never logs values to `buyPaidOffsite`. A store with no digital tender will never have a `buyPaidDigital` entry. These fields are technically in critical categories — but flagging them as \"unmapped\" forever would mean a permanent dashboard warning for fields you\'ll never use.\n\nEvery account dropdown\'s first option is **\"Not used at this store\"** — pick it to acknowledge the field as N/A.\n\n![Account dropdown with \"Not used at this store\" at the top](/images/support/articles/quickbooks_mapping_dropdown_open.png)\n\n*The first option in every account dropdown lets you mark the field as N/A for your store.*\n\nWhen set:\n- The row dims to indicate it\'s intentionally unmapped.\n- The amber critical-field warning disappears.\n- The dashboard\'s \"Critical fields unmapped\" count excludes it.\n- The Approval Queue stops blocking on it.\n\nIf you later realize the store DOES take that payment type, just pick a real account from the same dropdown — the opt-out flag flips back off automatically.\n\n> **Why this exists:** the system defaults conservatively (every financial-category field is critical) because unmapped values otherwise silently fall to Cash Over/Short, which is much worse than a vocal warning. \"Not used at this store\" is the explicit acknowledgement that says \"I know this field exists, my store doesn\'t use it, stop nagging.\"\n\n---\n\n## Setting a mapping\n\nFor each row in the table:\n\n1. Click the QBO account dropdown. The list is fetched live from your connected QBO company file and is searchable — start typing to filter.\n2. Select the correct account.\n3. Optionally set an offset account if the row needs one (see [Offset accounts](#offset-accounts) below).\n4. Hit `Save changes` in the header when you\'re done.\n\nMappings save in a batch, so you can edit several rows before committing.\n\n> **Tip:** Every mapping change is logged to the Audit Log as a `mapping_updated` event with the old and new account IDs. If a future variance turns out to be a mis-mapping, you can prove exactly when the mapping was changed and by whom.\n\n---\n\n## AI Auto-map — let the system suggest mappings\n\nSetting up mappings manually for a fresh store can take an hour. The **Auto-map** feature uses two layers of intelligence to suggest mappings for unmapped rows:\n\n1. **Heuristic engine** — fuzzy-matches field names to your chart of accounts (e.g. `cashTendered` → \"Cash\" or \"Cash on Hand\") plus a cross-store learning model that watches what other stores have mapped fields to. Fast (milliseconds), no AI cost.\n2. **LLM (gpt-5-mini)** — fills gaps the heuristic isn\'t confident about by reasoning over the field description and account names. Takes a few seconds, capped at 5 runs per store per day.\n\n![Section header with Suggest with AI button](/images/support/articles/quickbooks_mapping_section_header.png)\n\n*Each section header has its own \"Suggest with AI\" button so you can scope suggestions to one group at a time.*\n\n**Two ways to invoke:**\n\n- **Per-section** — each section header (Payments, Sales & Returns, etc.) has a `Suggest with AI` button that runs both engines scoped to just that section\'s fields.\n- **Page-level** — the top toolbar\'s `Auto-map` button runs the heuristic across every unmapped field on the page (no LLM cost). Click `Suggest with AI` in the suggestions banner to add the LLM pass.\n\n**Reviewing suggestions:**\n\nSuggestions appear as pre-filled dropdown values marked with a small ✨ sparkle indicator. They\'re not saved until you click `Save changes`. Review each one — the system is good but not perfect, and your chart of accounts is yours.\n\n![AI-suggested mapping with sparkle badge](/images/support/articles/quickbooks_mapping_ai_badge.png)\n\n*The sparkle marks a field that was filled in by Auto-map. Treat them as suggestions, not commitments.*\n\n> **Tip:** Auto-map is most useful on initial setup. Once your mappings are stable, you\'ll rarely need it again — and the cross-store learning model means future stores benefit from what your store mapped today.\n\n---\n\n## Sign semantics (read this if you map Returns or Refunds)\n\nDRS data uses **negative numbers as a labeling convention** for things like returns: `newReturns: -3.99` means \"a return of $3.99,\" not \"negative $3.99 owed somewhere.\"\n\nEach mapping row has a **`signSemantics`** column that controls how the system interprets the source value\'s sign:\n\n| Setting | Meaning | When to use |\n| ------- | ------- | ----------- |\n| `directional` *(default)* | The sign of the source value carries meaning. Negative source values flip the entryType (DR ↔ CR). | Fields where positive vs negative is real (e.g. `cashOverShort`: positive = drawer over, negative = drawer short). |\n| `absolute` | The sign of the source value is just a labeling convention and the entryType is the truth. The amount is always abs(value). | Fields where negative is just how the DRS labels something (returns, refunds, void counts). |\n\nThe `ABS` badge next to a row\'s DR/CR badge indicates an `absolute` mapping; everything else is `directional`.\n\n| Field | `entryType` | `signSemantics` | Source `-3.99` | Posted as |\n| ----- | ----------- | --------------- | -------------- | --------- |\n| `newReturns` | `debit` | `absolute` | ignored | DEBIT $3.99 to contra-revenue ✓ |\n| `usedReturns` | `debit` | `absolute` | ignored | DEBIT $3.99 to contra-revenue ✓ |\n| `totalSales` | `credit` | `directional` | (always positive) | CREDIT to Sales Revenue |\n| `cashOverShort` | `credit` | `directional` | matters: -$5 → DR (short = expense) | DEBIT $5 to Cash Over/Short |\n\n> **Note for accountants:** if a row\'s DR/CR badge looks \"backwards\" for what the field is, check whether it has an **ABS** badge. If yes, the entryType is the actual posting direction — the source value\'s sign is decoration only.\n\n---\n\n## Offset accounts\n\nMost DRS fields imply a paired entry. Cash paid for a buy debits the **Cash** asset account and credits the **Inventory** asset account; cash tendered for a sale debits **Cash** and credits **Sales Revenue**. The per-row **Offset** dropdown expresses this directly.\n\nWhen you set an offset account on a field, the daily sync emits **two** journal lines per non-zero source value: the primary line at the field\'s main account, and a mirror-direction offset line at the offset account. So a `$10.00 cashTendered` field with primary `Cash` (DR) and offset `Sales Revenue` (CR) posts:\n\n```\nDR  $10.00  Cash\nCR  $10.00  Sales Revenue\n```\n\nThat single field then balances internally — debits = credits for that pair — regardless of what\'s happening with other fields on the same close report.\n\n**When to leave the offset blank (\"single-sided\"):**\n\nSome fields don\'t have a natural double-entry partner inside the same close report. The dropdown\'s first option is `-- No offset (single-sided) --` for these. Examples:\n\n- `cashOverShort` — the partner is the cash account, but cashOverShort itself stands alone in QBO as either an expense or expense reduction.\n- Fields where another field on the same report is implicitly the partner (e.g. `cashTendered` and `totalSales` may already balance each other out without explicit offsets).\n\nIf you\'re not sure whether to set an offset, check whether your daily JE balances when you leave it single-sided. If you see consistent variance, the field probably needs an offset. The Approval Queue\'s balance banner flags imbalances explicitly so you can iterate.\n\n> **Tip:** Setting offsets on every field that has a logical partner makes your daily JE balance to the penny on day one, instead of leaning on Cash Over/Short to absorb the gap. Time well spent during initial setup.\n\n---\n\n## Mid-period changes\n\nYou *can* change a mapping at any time, but think about timing:\n\n- A mapping change applies to the **next** sync. Already-posted journal entries are not retroactively re-posted.\n- If you change a mapping mid-month and have already posted entries that used the old mapping, your books will have a mix. Either accept this and document the change date, or void the affected entries and re-post.\n\nThe Audit Log\'s `mapping_updated` event preserves the old → new account ids, so you can always reconstruct what was active when.\n\n---\n\n## Reconnecting QBO and your mappings\n\nIf you disconnect and reconnect to a *different* QBO company file, your mapped account ids will no longer be valid (each QBO company has its own account id space). The system shows a banner if any mapped account no longer exists in the connected file. You\'ll need to re-map fields one by one — or use Auto-map / Suggest with AI to bulk-suggest replacements.\n\n---\n\n## What\'s next\n\n- [Connect & configure](/support/article/quickbooks-connect-and-configure) — required first step before mapping.\n- [Daily approval workflow](/support/article/quickbooks-daily-approval-workflow) — what to do once mappings are complete.\n- [Reconciliation](/support/article/quickbooks-reconciliation) — verifying mappings produce correct totals over time.\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),
    updated_at = NOW();

-- ---- Article #91: QuickBooks: Dashboard (slug=quickbooks-dashboard, status=published) ----
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 = 'quickbooks' LIMIT 1),
    'QuickBooks: Dashboard',
    'quickbooks-dashboard',
    'The QuickBooks Dashboard is your at-a-glance view of the integration. It shows whether you\'re connected, what needs your attention, and what\'s happened recently — all on one page so you can spot problems fast and confirm the integration is doing its job.\n\n![QuickBooks Dashboard overview](/images/support/articles/quickbooks_dashboard_overview.png)\n\n*The Dashboard combines connection status, sync health, action items, and recent activity in a single view.*\n\n> **Who can use this:** Admins only. The Manager, Shift Lead, Buyer, and Cashier roles don\'t have QuickBooks access.\n\n---\n\n## Where to find it\n\nFrom the admin sidebar: **QuickBooks** > **Dashboard**. Direct URL: `/admin/{typeNum}/quickbooks`.\n\nThis is the landing page for the QuickBooks section — every other QuickBooks page links back here via the \"Back to Dashboard\" link in the page header.\n\n---\n\n## The header — status chips\n\nThe page title row shows three quick-glance chips:\n\n![Header status chips](/images/support/articles/quickbooks_dashboard_header_chips.png)\n\n*Three chips next to the title: Connected · Manual sync · N unmapped (only when something needs attention).*\n\n| Chip | When it appears | What it tells you |\n| ---- | --------------- | ----------------- |\n| **● Connected / Disconnected** | Always | Green dot = OAuth tokens are valid. Red dot = you need to reconnect in **Settings**. |\n| **● Manual sync / Auto sync / Disabled sync** | Always | Your current sync mode. Click into **Settings** to change. |\n| **● N unmapped** (amber) | Only when critical mapping fields are unmapped | The system is blocked from posting because critical fields don\'t have QBO accounts assigned. |\n\nThe subtitle line below the chips shows your QBO company name and the time since the last sync.\n\n---\n\n## \"Needs your attention\" — actionable items only\n\nThe first panel below the header is a dynamic card grid that surfaces only what you can act on right now:\n\n![Needs Your Attention panel](/images/support/articles/quickbooks_dashboard_attention_panel.png)\n\n*Three card types: warn (amber, critical), info (neutral, action available), quiet (gray, demoted).*\n\n| Card | When it appears | Click → |\n| ---- | --------------- | ------- |\n| 🟧 **Critical fields unmapped** | When unmapped fields are in critical categories (payments/sales/cogs/buys/cash/other) — sync is blocked | Account Mapping page |\n| 🟦 **Pending approvals** | When there are entries waiting in the queue | Approval Queue |\n| ⚪ **Failed syncs** | When auto-sync has failed in the last 7 days | Sync Log filtered to failures |\n\n> **When everything is clean, the entire panel hides.** If you don\'t see it, that means there\'s nothing actionable right now — the dashboard\'s body leads with the activity log instead.\n\nThe warn card names the specific fields so you can see what\'s flagged before clicking through (e.g. \"buyPaidOffsite, buyPaidDigital\" — useful for distinguishing genuine setup work from fields that should be marked \"Not used at this store\" — see the [Account Mapping](/support/article/quickbooks-account-mapping) article).\n\n---\n\n## Recent activity — what happened, by whom\n\nThe bottom panel is an event log of the last 10 audit events for this store. Each row shows:\n\n- The kind of event (Sync · Edit · Approval · Settings) as a colored pill\n- A short verb describing what happened (Posted, Failed, Edited, Approved, Rejected, Changed)\n- The business date or setting key\n- Who did it\n- When it happened (relative — \"23h ago\" / \"5d ago\" / a date for older)\n- A one-line detail summary (e.g. \"Auto → Manual\" for a settings change, or a clickable Doc # link for a sync_posted event)\n\n![Activity row showing a posted sync with a clickable Doc # link](/images/support/articles/quickbooks_dashboard_activity_row.png)\n\n*Posted sync events show \"Posted to QuickBooks · Doc #BK-pc00-20260501\" — the Doc # is a clickable link to the journal entry in QuickBooks Online.*\n\n### Filter tabs\n\nAbove the rows, five tabs filter the feed:\n\n![Activity log filter tabs](/images/support/articles/quickbooks_dashboard_activity_filters.png)\n\n| Tab | Shows |\n| --- | ----- |\n| **All** | Every event |\n| **Syncs** | sync_staged, sync_posted, sync_failed, re_synced, manual_sync, connected, disconnected |\n| **Edits** | edited, voided |\n| **Approvals** | approved, rejected |\n| **Settings** | settings_changed |\n\nThe tabs are client-side — they filter what\'s already loaded, no extra network call. The count next to the title updates to \"N of M\" when you have a filter active.\n\n> **Tip:** When you want to verify that auto-syncs are actually running, click `Syncs` and look for recent `Posted` events. When you want to see what an admin changed, click `Settings`.\n\n### Doc # → QBO deep-link\n\nWhenever a posted (or voided) event has a Doc # in its detail line, that Doc # is a clickable link that opens the journal entry directly in QuickBooks Online (right page in the right company). Hover for the cue underline + external-link icon.\n\nThe same deep-link pattern works on the [Audit Log](/support/article/quickbooks-audit-log) Details column and the [Sync Log](/support/article/quickbooks-sync-log) DocNumber column.\n\n---\n\n## Refresh\n\nThe Dashboard auto-refreshes every 60 seconds so the activity feed and counts stay current without you reloading. If you\'ve just performed an action elsewhere and want to see it immediately, just navigate away and back.\n\n---\n\n## What\'s next\n\n- [Connect & Configure](/support/article/quickbooks-connect-and-configure) — change sync mode, set variance thresholds, customize memos.\n- [Account Mapping](/support/article/quickbooks-account-mapping) — fix the \"N unmapped\" chip by completing critical field mappings.\n- [Daily Approval Workflow](/support/article/quickbooks-daily-approval-workflow) — what to do with pending approvals.\n- [Sync Log](/support/article/quickbooks-sync-log) — the unified history of every posted entry.\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),
    updated_at = NOW();

-- ---- Article #92: QuickBooks: Sync Log (slug=quickbooks-sync-log, status=published) ----
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 = 'quickbooks' LIMIT 1),
    'QuickBooks: Sync Log',
    'quickbooks-sync-log',
    'The Sync Log is the unified history of every journal entry that\'s reached (or attempted to reach) QuickBooks Online from this store — both automatic syncs and manually-approved entries, in one timeline.\n\n![Sync Log overview](/images/support/articles/quickbooks_synclog_overview.png)\n\n*One grid, both sync paths. The Source column tells you which one each row came from.*\n\n> **Who can use this:** Admins only. The Manager, Shift Lead, Buyer, and Cashier roles don\'t have QuickBooks access.\n\n---\n\n## Where to find it\n\nFrom the sidebar: **QuickBooks** > **Sync Log**. Direct URL: `/admin/{typeNum}/quickbooks/sync-log`.\n\nBy default the page loads the last 30 days.\n\n---\n\n## What \"sync\" means here — auto vs. manual\n\nThere are two ways a journal entry can land in QBO:\n\n| Source | Path | Logged as |\n| ------ | ---- | --------- |\n| 🔵 **AUTO** | Nightly cron job in Auto mode → direct post → success / failure record | A row in `qb_sync_log` |\n| 🟣 **MANUAL** | Daily close staged for approval → admin reviews + clicks Approve → post | A row in `qb_staged_entries` |\n\nFor a long time these had separate tables and the \"Sync Log\" only showed AUTO rows — so manual-mode stores saw an effectively empty Sync Log even though they were clearly approving entries through the queue.\n\nThe page now **unifies both** into one chronological history. The new **Source** column tells you at a glance which path produced each row.\n\n![Source chips — AUTO (neutral) and MANUAL (accent)](/images/support/articles/quickbooks_synclog_source_chips.png)\n\n*MANUAL chip carries a faint accent tint (the \"a human acted here\" rows). AUTO chips stay neutral — they\'re the background hum of the cron.*\n\n---\n\n## Filters\n\nThree filters compose at the top of the grid:\n\n![Filter toolbar — date range + source + status](/images/support/articles/quickbooks_synclog_filters.png)\n\n| Filter | Default | Options |\n| ------ | ------- | ------- |\n| **From / To** | Last 30 days | Any date range |\n| **Source** | All | All / Auto (cron) / Manual (approval) |\n| **Status** | All | All / Posted / Failed / Pending / Rejected / Voided |\n\nFilters compose — `Source: Manual` + `Status: Posted` shows you every manually-approved entry that successfully posted.\n\n---\n\n## Status meanings\n\nAfter the unification, statuses are normalized across both sources:\n\n| Status | Auto path | Manual path |\n| ------ | --------- | ----------- |\n| `Posted` | Successfully posted to QBO | Approved + posted to QBO |\n| `Failed` | Cron tried and QBO rejected | (rare — manual posts that fail usually become rejected) |\n| `Pending` | Auto job still processing (transient) | Waiting in the Approval Queue |\n| `Rejected` | (n/a) | Admin explicitly rejected the entry |\n| `Voided` | (rare) | Previously posted, then voided in QBO |\n\n---\n\n## Doc # is a QBO link\n\nEvery posted (or voided) row\'s DocNumber cell is a clickable link that opens the journal entry directly in QuickBooks Online — right page, right company, no searching.\n\n![Hover state on a clickable Doc # link](/images/support/articles/quickbooks_synclog_doc_link.png)\n\n*Dotted underline + tiny external-link icon = clickable. Opens in a new tab.*\n\nBehind the scenes the link targets:\n\n```\nhttps://{sandbox.}qbo.intuit.com/app/journal?txnId=N&realmId=M\n```\n\nThe same pattern works in the [Dashboard activity feed](/support/article/quickbooks-dashboard) and the [Audit Log Details column](/support/article/quickbooks-audit-log) — wherever a posted Doc # appears, it\'s a deep-link.\n\n---\n\n## Smart per-row actions\n\nActions are state-specific — only what makes sense for each row, no busywork navigation:\n\n![Per-row actions cluster](/images/support/articles/quickbooks_synclog_row_actions.png)\n\n| Row state | Action |\n| --------- | ------ |\n| Posted (any source) | (Doc # link IS the action — opens the JE in QBO) |\n| Voided | (Doc # link — view the voided state in QBO) |\n| Auto + Failed | **Retry** — re-runs the auto-sync for this date |\n| Manual + Rejected/Failed | **Re-sync this date** — creates a fresh staged entry for the approval queue |\n| Manual + Pending | **Open in Queue** — deep-link to the specific row in the Approval Queue with auto-scroll and highlight |\n\n> **Tip:** \"Open in Queue\" pulses the matching row\'s background for 2 seconds when you arrive, so you can spot the entry you came for even when the queue is long.\n\n---\n\n## Retry vs Re-sync\n\nBoth call the same backend endpoint (manual sync for a date), but the use cases differ:\n\n- **Retry** (auto + failed): the nightly cron tried, QBO returned an error (period closed, token expired, mapping deleted). Fix the cause, then click Retry to re-run.\n- **Re-sync** (manual + rejected/failed): you rejected an entry because the DRS data was bad, or you fixed an underlying mapping that produced the rejection. Click Re-sync once the source data or mapping is corrected — a new pending entry appears in the queue.\n\nIn both cases the system creates a fresh `qb_staged_entries` row (or directly posts in auto mode) for the date. The old rejected/failed row is preserved as an audit record — never deleted.\n\n---\n\n## How is this different from the Audit Log?\n\n**Sync Log = \"what entries posted (or tried to post) to QBO?\"**\n- One row per business date\n- Shows the final state (posted / failed / pending / etc.)\n- Optimized for \"did Tuesday\'s entry land?\"\n\n**Audit Log = \"every action that happened, in order\"**\n- Multiple rows per entry (one per event: staged, edited, approved, sync_posted, voided)\n- Append-only, immutable\n- Optimized for \"who changed what, when?\" — compliance and debugging\n\nUse the Sync Log for daily operational checks. Use the [Audit Log](/support/article/quickbooks-audit-log) when you need to trace the full lifecycle of a specific entry or answer a \"who/when?\" question.\n\n---\n\n## What\'s next\n\n- [Audit Log](/support/article/quickbooks-audit-log) — full event history, filterable + exportable.\n- [Daily Approval Workflow](/support/article/quickbooks-daily-approval-workflow) — the source of the Manual rows.\n- [Reconciliation](/support/article/quickbooks-reconciliation) — periodic check that posted totals match DRS source data.\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),
    updated_at = NOW();

-- ---- Article #93: QuickBooks: Audit Log (slug=quickbooks-audit-log, status=published) ----
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 = 'quickbooks' LIMIT 1),
    'QuickBooks: Audit Log',
    'quickbooks-audit-log',
    'The Audit Log is an immutable, append-only record of every QuickBooks-related action that\'s ever happened on this store — staged entries, edits, approvals, rejections, voids, settings changes, mapping updates, OAuth connects. It\'s the canonical source of \"who did what, when?\"\n\n![Audit Log overview](/images/support/articles/quickbooks_auditlog_overview.png)\n\n*Every QB action recorded. Filterable by date and event type, with humanized summaries in the Details column.*\n\n> **Who can use this:** Admins only. The Manager, Shift Lead, Buyer, and Cashier roles don\'t have QuickBooks access.\n\n---\n\n## Where to find it\n\nFrom the sidebar: **QuickBooks** > **Audit Log**. Direct URL: `/admin/{typeNum}/quickbooks/audit-log`.\n\nBy default the page loads the last 30 days, sorted newest first.\n\n---\n\n## What gets logged\n\nEvery meaningful action writes one row. The system never deletes or modifies a logged row — once a fact is recorded, it stays.\n\n| Event type | When it fires | Who triggers |\n| ---------- | ------------- | ------------ |\n| `sync_staged` | Nightly close ingest creates a pending entry | System |\n| `sync_restaged` | Re-sync from Sync Log creates a fresh entry | Admin (Re-sync) |\n| `sync_posted` | Entry successfully posts to QBO | System (auto) or Admin (manual) |\n| `sync_failed` | QBO rejects a post attempt | System / Admin |\n| `re_synced` | Auto retry of a previously-failed sync | System |\n| `manual_sync` | Admin-triggered ad-hoc sync | Admin |\n| `edited` | Pending entry was modified before approval | Admin |\n| `approved` | Admin approved a pending entry | Admin |\n| `rejected` | Admin rejected a pending entry | Admin |\n| `voided` | Posted entry voided in QBO | Admin |\n| `settings_changed` | Any setting changed (sync mode, thresholds, memo, etc.) | Admin |\n| `mapping_updated` | Account mapping for a field changed | Admin |\n| `connected` / `disconnected` | OAuth state changed | Admin |\n\nEach row captures: actor (user id + name), timestamp, IP address (when from a web action), business date the event relates to (if any), and a JSON payload with event-specific details.\n\n---\n\n## Filters\n\n![Filter toolbar — date range + event type + refresh + export](/images/support/articles/quickbooks_auditlog_filters.png)\n\n| Filter | Default | Notes |\n| ------ | ------- | ----- |\n| **From / To** | Last 30 days | Any range; older periods load on demand |\n| **Event** | All events | One of the 13 event types above |\n\nThe **Refresh** button reloads with the current filters. **Export CSV** downloads everything matching the current filters as a CSV — useful for handing to auditors or accountants.\n\n---\n\n## Humanized Details column\n\nRather than dumping raw JSON, the Details column shows a per-event-type summary in plain English:\n\n![Humanized Details summaries](/images/support/articles/quickbooks_auditlog_details_column.png)\n\n| Event | Old display | New summary |\n| ----- | ----------- | ----------- |\n| `settings_changed` | `{\"key\":\"syncMode\",\"oldValue\":\"auto\",\"newValue\":\"manual\"}` | `syncMode: auto → manual` |\n| `sync_posted` | `{\"journalEntryId\":\"160\",\"docNumber\":\"BK-pc00-20260501\"}` | `Posted to QBO · Doc #BK-pc00-20260501` *(clickable)* |\n| `sync_failed` | `{\"error\":\"...\"}` | `Error: <actual error message>` |\n| `edited` | `{\"editReason\":\"...\", \"changes\":{...}}` | `Reason: <editReason>` or `Changed N fields` |\n| `rejected` | `{\"reason\":\"...\"}` | `Reason: <rejection reason>` |\n| `voided` | `{\"reason\":\"...\",\"docNumber\":\"...\"}` | `Reason: <reason> · Doc #BK-pc00-...` *(clickable)* |\n\nThe summaries pull from the event\'s stored details JSON — same data, friendlier rendering.\n\n---\n\n## Doc # → QBO deep-link\n\nWherever a posted or voided event\'s Details column shows a `Doc #BK-...`, that doc number is a **clickable link** that opens the journal entry directly in QuickBooks Online — right page, right company. Hover for the cue underline + external-link icon.\n\nThe same deep-link pattern works in the [Sync Log DocNumber column](/support/article/quickbooks-sync-log) and the [Dashboard activity feed](/support/article/quickbooks-dashboard).\n\n---\n\n## Expanded row — full payload\n\nEvery row is expandable (click the ▶ chevron) to show the raw payload JSON in a tinted panel:\n\n![Expanded row showing pretty-printed JSON](/images/support/articles/quickbooks_auditlog_expanded_row.png)\n\n*Pretty-printed JSON for events that carry one; \"No additional details\" for events that don\'t (e.g. `approved` typically has no payload).*\n\nUse this when the Details summary doesn\'t have the specific field you need — the full payload is always there.\n\n---\n\n## CSV export\n\nThe **Export CSV** button downloads every row matching the current filters as a CSV file. Columns include the actor, timestamp, event type, business date, and the full details JSON flattened to a string.\n\nUseful for:\n- Handing to your accountant for a period review\n- Auditor requests (\"show me every action on Q1 close data\")\n- Long-term archival outside the BuyerKiosk database\n\nThe export is capped at 10,000 rows per file — use the date filters to narrow if you need more.\n\n---\n\n## How is this different from the Sync Log?\n\n**Audit Log = \"every action that happened, in order\"**\n- Multiple rows per entry (staged + edited + approved + sync_posted + voided = 5 rows for one business date if it went the full route)\n- Append-only, immutable, capturing actor + IP + timestamp\n- Optimized for compliance and \"who/when?\" investigations\n\n**Sync Log = \"what entries posted (or tried to post) to QBO?\"**\n- One row per business date\n- Shows the final state (posted / failed / etc.)\n- Optimized for \"did Tuesday\'s entry land?\"\n\nUse the [Sync Log](/support/article/quickbooks-sync-log) for daily operational checks. Use the Audit Log when you need the full lifecycle or an actor.\n\n---\n\n## What\'s next\n\n- [Sync Log](/support/article/quickbooks-sync-log) — operational view of what\'s actually in QBO.\n- [Reconciliation](/support/article/quickbooks-reconciliation) — periodic accuracy check.\n- [Daily Approval Workflow](/support/article/quickbooks-daily-approval-workflow) — most audit events originate here.\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),
    updated_at = NOW();

-- ---- Article #96: QuickBooks: Overview (slug=quickbooks-overview, status=draft) ----
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 = 'quickbooks' LIMIT 1),
    'QuickBooks: Overview',
    'quickbooks-overview',
    'Connect your BuyerKiosk store to QuickBooks Online and sync daily close activity as accounting journal entries.\n\n![QuickBooks connected full view](/images/support/articles/quickbooks_connected_full.png)\n\n## What the Integration Does\n\nThe integration turns a completed BuyerKiosk daily close into a QuickBooks journal entry. The journal entry summarizes the day instead of sending every individual sale or buy transaction.\n\nThe goal is to reduce manual data entry while keeping accounting review in place.\n\n## End-to-End Flow\n\n1. The store completes the daily close in BuyerKiosk.\n2. BuyerKiosk reads the daily close totals.\n3. Account mappings decide which QuickBooks accounts each total uses.\n4. BuyerKiosk builds a balanced journal entry.\n5. The entry is staged for approval or posted automatically, depending on settings.\n6. The sync log records the result.\n7. The audit log records important actions.\n8. Reconciliation helps confirm BuyerKiosk and QuickBooks agree.\n\n## Main Pages\n\n| Page | Purpose |\n| --- | --- |\n| Dashboard | Shows connection health, pending work, and recent activity. |\n| Account Mapping | Connects BuyerKiosk daily close fields to QuickBooks accounts. |\n| Approval Queue | Lets owners review, edit, approve, reject, or void staged entries. |\n| Sync Log | Shows posting attempts and errors. |\n| Audit Log | Shows who changed settings, mappings, and entries. |\n| Reconciliation | Compares BuyerKiosk close data with QuickBooks entries. |\n| Settings | Controls sync mode, approval rules, and memo behavior. |\n\n## Who Should Use It\n\nStore owners and accountants should both be involved at setup:\n\n- BuyerKiosk support can connect the store and explain the workflow.\n- The owner can confirm operational expectations.\n- The accountant should approve the chart of accounts and mapping choices.\n\n## Recommended Rollout\n\n1. Connect QuickBooks.\n2. Configure mappings with the accountant\'s account names.\n3. Use approval mode for the first few closes.\n4. Review staged entries before posting.\n5. Confirm posted entries in QuickBooks.\n6. Use reconciliation to verify the first week.\n7. Consider automatic posting only after the owner is comfortable with the results.\n\n## What It Does Not Do\n\nThe integration does not:\n\n- Replace the accountant.\n- Decide the chart of accounts for the store.\n- Reconcile bank deposits.\n- Fix incorrect daily close data.\n- Send every individual POS transaction to QuickBooks.\n\nIt moves daily totals into QuickBooks in a repeatable way, with logs and review tools around that process.\n',
    NULL,
    COALESCE((SELECT id FROM kiosk_users.users WHERE username = 'rvanvuren' LIMIT 1), 1),
    'draft',
    0,
    1,
    NOW(), NOW()
)
ON DUPLICATE KEY UPDATE
    content_md = VALUES(content_md),
    content_html = NULL,
    category_id = VALUES(category_id),
    updated_at = NOW();

-- ============================================================
-- 3. Verification (run after COMMIT, all values commented out)
-- ============================================================
-- SELECT id, name, slug, sort_order, is_active FROM support_categories WHERE slug = 'quickbooks';
--
-- SELECT a.id, a.title, a.slug, a.status, c.slug AS category, LENGTH(a.content_md) AS bytes, a.updated_at
--   FROM support_articles a
--   JOIN support_categories c ON c.id = a.category_id
--   WHERE a.slug IN (
--     'customize-printed-receipt',
--     'customize-customer-disclosure',
--     'quickbooks-online-integration',
--     'quickbooks-integration',
--     'quickbooks-connect-and-configure',
--     'quickbooks-daily-approval-workflow',
--     'quickbooks-reconciliation',
--     'quickbooks-account-mapping',
--     'quickbooks-dashboard',
--     'quickbooks-sync-log',
--     'quickbooks-audit-log',
--     'quickbooks-overview'
--   ) ORDER BY c.slug, a.slug;

-- If everything looks right, the trailing COMMIT applied your changes.
-- If something looks wrong, you'll need to restore from the backup table you
-- created before running this script (see the deploy doc / commit message).
COMMIT;
