# Product Requirements Document

## Validation Checklist

- [x] All required sections are complete
- [x] No [NEEDS CLARIFICATION] markers remain
- [x] Problem statement is specific and measurable
- [x] Problem is validated by evidence (not assumptions)
- [x] Context → Problem → Solution flow makes sense
- [x] Every persona has at least one user journey
- [x] All MoSCoW categories addressed (Must/Should/Could/Won't)
- [x] Every feature has testable acceptance criteria
- [x] Every metric has corresponding tracking events
- [x] No feature redundancy (check for duplicates)
- [x] No contradictions between sections
- [x] No technical implementation details included
- [x] A new team member could understand this PRD

---

## Product Overview

### Vision

Transform BuyerKiosk's QuickBooks Online (QBO) integration from a fragile auto-post pipeline into a full accounting control center — giving store operators confidence that every dollar synced to their books is correct, auditable, and recoverable.

### Problem Statement

BuyerKiosk's QuickBooks integration was built as a one-directional pipe: daily close data goes in, journal entries come out. In practice, this creates serious operational problems:

1. **Silent failures trap stores in broken states.** pc00's integration has been stuck on "no account mappings configured" because the chart-of-accounts wizard was never completed — and there's zero UI indication that setup is incomplete. Every beta store will hit the same wall.

2. **Duplicate journal entries corrupt the books.** When a sync fails and retries, the system re-posts to QBO without checking if a journal entry (JE) for that date already exists. This creates duplicate ledger entries that require manual cleanup by a bookkeeper.

3. **No human-in-the-loop before posting.** The nightly job posts directly to QBO with no opportunity for review. If the POS data has a one-day error (e.g., a miscounted drawer), it goes straight to the books. The only fix is to log into QBO directly and manually void/edit — which requires accounting expertise the store operator may not have.

4. **No audit trail for what actually posted.** The current sync log captures success/failure but not edits, approvals, rejections, or voids. When the accountant asks "why does Tuesday's JE show $47 more in cash than the POS report?" — there's no answer in the system.

5. **The entire integration lives on one cramped settings page** with no dedicated views for sync history, account mapping progress, or reconciliation.

These problems are validated by direct experience with the pc00 beta deployment and analysis of the existing codebase (see `JournalEntryService.php:396-402` for the duplicate bug root cause, `JournalEntryService.php:53-58` for the empty mappings error).

### Value Proposition

BuyerKiosk becomes the **accounting control plane** between the POS and QuickBooks — not just a sync pipe. Store operators get:
- **Confidence**: See exactly what will post before it hits the books
- **Control**: Edit, approve, reject, or void any journal entry from inside BuyerKiosk
- **Visibility**: Know instantly if something is off via variance reports and status dashboards
- **Safety**: Idempotent syncs that can't create duplicates, with automatic audit trails for every action
- **Simplicity**: Seven purpose-built pages instead of one overloaded settings screen

No competing POS-to-QBO integration in the resale/pawn space offers pre-sync editing with audit trails. This is a differentiation point for enterprise accounts.

## User Personas

### Primary Persona: Store Owner/Operator
- **Demographics:** 30-65 years old, small business owner, manages 1-5 stores, moderate technical skill, uses QuickBooks for bookkeeping (often self-taught), checks BuyerKiosk admin daily
- **Goals:** Ensure daily sales data flows accurately into QuickBooks without manual data entry; catch errors before they hit the books; have a clean audit trail for their accountant/CPA at tax time
- **Pain Points:** Doesn't trust automated syncs because errors are invisible until the accountant finds them weeks later; has been burned by duplicate JEs requiring manual cleanup; doesn't want to learn QBO's interface for voids/corrections; frustrated that incomplete setup gives cryptic errors

### Secondary Persona: Bookkeeper/Accountant
- **Demographics:** 25-60 years old, handles books for multiple clients, advanced QuickBooks expertise, needs CSV exports and variance reports, may not have direct BuyerKiosk admin access but receives reports from the owner
- **Goals:** Reconcile POS data against QBO ledger entries monthly; trace discrepancies to their source; export audit data for tax preparation; trust that the integration hasn't introduced errors
- **Pain Points:** No variance report exists today — must manually cross-reference POS reports with QBO; can't see edit history or who approved a sync; no CSV export for audit handoff

### Tertiary Persona: Store Manager
- **Demographics:** 22-45 years old, runs day-to-day operations, moderate technical skill, uses BuyerKiosk workbook daily, may be authorized to approve syncs but not configure mappings
- **Goals:** See today's sync status quickly; approve pending syncs when the owner is unavailable; know if last night's auto-sync succeeded
- **Pain Points:** No visibility into QB sync status from the BuyerKiosk admin; broken sidebar link prevents access; no notification when something fails

## User Journey Maps

### Primary User Journey: First-Time Setup & Onboarding
1. **Awareness:** Owner purchases BuyerKiosk, hears about QB integration during onboarding call. Wants to stop manually entering daily totals into QBO.
2. **Consideration:** Evaluates whether BuyerKiosk's integration is trustworthy enough to automate their books. Key concerns: accuracy, ability to undo mistakes, audit trail for accountant.
3. **Adoption:** Clicks "QuickBooks" in sidebar → lands on Settings page → connects via OAuth popup → gets **redirected to Account Mapping page** (not back to settings). Sees a progress indicator ("12/37 fields mapped") that makes it clear setup isn't done. Completes mapping with guidance from category groupings and descriptions.
4. **Usage:** Connection defaults to **manual approval mode**. First night, the 2AM job stages a JE. Next morning, owner sees the Approval Queue badge (1 pending). Opens it, reviews the staged JE, approves it. JE posts to QBO. Owner gains trust.
5. **Retention:** After a week of successful manual approvals, owner switches to auto mode. Uses the Dashboard to spot-check status. Monthly, uses Reconciliation page to verify variance is zero. Exports audit log CSV for accountant.

### Secondary User Journey: Daily Sync Monitoring & Approval
1. Owner/manager opens BuyerKiosk admin → sees QuickBooks sidebar section
2. Dashboard shows: last sync status, pending approvals count, any failures
3. If pending items exist → clicks Approval Queue → reviews staged JE lines
4. Optionally edits a value (e.g., corrects a cash amount) → enters required reason
5. Approves → system posts to QBO → audit log records who approved and any edits
6. If something looks wrong → rejects with reason → entry is flagged, not posted

### Tertiary User Journey: Error Recovery & Reconciliation
1. Nightly sync fails (QBO API error or token expiry)
2. In-app notification appears for the configured QB user
3. Owner opens Dashboard → sees failed sync for yesterday
4. Clicks into the Sync Log detail → sees error message and raw payload
5. Retriggers manual sync → system checks for existing JE by DocNumber first → posts successfully
6. Monthly: opens Reconciliation → selects date range → sees daily close totals vs. QBO journal totals → drills into any day with variance

## Feature Requirements

### Must Have Features

#### F1: Fix — Mapping Completeness Indicator (Bug #1)
- **User Story:** As a store owner, I want to see how many account mappings are incomplete so that I know my setup isn't finished and I can complete it before syncing.
- **Acceptance Criteria:**
  - [ ] Account Mapping page shows a progress bar: "Mapped: X/37 fields"
  - [ ] Unmapped fields are visually highlighted (different background color or icon)
  - [ ] Dashboard shows a setup completeness warning when < 100% mapped
  - [ ] Sync operations are blocked with a clear message when mappings are incomplete
  - [ ] Progress persists across page reloads

#### F2: Fix — Duplicate Journal Entry Prevention (Bug #2)
- **User Story:** As a store owner, I want the system to never create duplicate journal entries in QBO so that my books stay clean even when syncs fail and retry.
- **Acceptance Criteria:**
  - [ ] Before posting, system queries QBO for existing JE by DocNumber
  - [ ] DocNumber format is `BK-{typeNum}-{yyyymmdd}` (e.g., `BK-pc00-20260504`)
  - [ ] Backward compatibility: lookup checks both old format (`{typeNum}-{yyyymmdd}`) and new format
  - [ ] If JE already exists in QBO, system does NOT create a duplicate
  - [ ] If existing JE has different amounts, system flags for manual decision (not auto-overwrite)
  - [ ] Every sync attempt is logged regardless of outcome

#### F3: Fix — Navigation & Sidebar Restructure (Bug #3 + Items #29-30)
- **User Story:** As a store user with QB permission, I want a dedicated QuickBooks section in the sidebar with links to all QB pages so that I can navigate the integration easily.
- **Acceptance Criteria:**
  - [ ] QuickBooks section appears as a new top-level collapsible group in the sidebar
  - [ ] Section appears ONLY when the store has a QB connection (qbEnabled = 1) OR user has `quickbooks_config` permission
  - [ ] Seven page links: Dashboard, Approval Queue, Sync Log, Audit Log, Account Mapping, Reconciliation, Settings
  - [ ] Approval Queue link shows a badge with pending count
  - [ ] All links route to working pages (no 404s)
  - [ ] Old `/admin/:typeNum/qb-connect/setup/` link is removed

#### F4: Per-Store Sync Mode (Item #4)
- **User Story:** As a store owner, I want to choose between automatic syncing, manual approval, or disabled mode so that I control how data flows to my books.
- **Acceptance Criteria:**
  - [ ] Settings page offers three sync modes: Auto, Manual Approval, Disabled
  - [ ] Auto mode: nightly job posts directly to QBO (current behavior)
  - [ ] Manual Approval mode: nightly job stages JE locally with status `pending_approval`, does NOT post to QBO
  - [ ] Disabled mode: nightly job skips this store entirely
  - [ ] New connections default to Manual Approval mode
  - [ ] Mode change takes effect on the next scheduled sync

#### F5: Manual Approval Queue (Items #6-8)
- **User Story:** As a store owner, I want to review staged journal entries before they post to QBO so that I can catch errors and approve with confidence.
- **Acceptance Criteria:**
  - [ ] Dedicated Approval Queue page lists all entries with status `pending_approval`
  - [ ] Each entry shows: date, total debits, total credits, line count, staged timestamp
  - [ ] Expanding an entry shows all journal lines with field name, description, account, amount, debit/credit
  - [ ] "Approve & Post" button posts to QBO and updates status to `posted`
  - [ ] "Bulk Approve" selects multiple days and posts them all in sequence
  - [ ] "Reject" button requires a reason (text field, min 10 chars) and sets status to `rejected`
  - [ ] Rejected entries do NOT post to QBO
  - [ ] After approval/rejection, entry moves out of the queue

#### F6: Pre-Sync Value Editing (Items #9-14)
- **User Story:** As a store owner, I want to edit individual amounts in a staged journal entry before approving so that I can correct POS errors without touching QBO directly.
- **Acceptance Criteria:**
  - [ ] Each line in a staged JE is editable (amount field becomes an input)
  - [ ] Operator can change which QBO account a line posts to (per-day override, not global mapping change)
  - [ ] Operator can add free-form adjustment lines (manual debit/credit with description)
  - [ ] Every edit requires a reason (text field, min 10 chars when amount changes by > $1.00)
  - [ ] Live debit/credit balance indicator updates as edits are made
  - [ ] Save button is disabled when debits ≠ credits (within $0.01 tolerance)
  - [ ] Diff preview shows original vs. edited values side-by-side before final approval
  - [ ] Original payload is preserved — edits create a new snapshot, not overwrite

#### F7: Idempotency & Payload Hash Tracking (Items #15-19)
- **User Story:** As a store owner, I want the system to detect when a re-sync's data is identical to what was already posted so that it doesn't create unnecessary transactions or prompt me when nothing changed.
- **Acceptance Criteria:**
  - [ ] System computes a hash of the JE payload before posting
  - [ ] On re-sync, if hash matches the already-posted version, system reports "no changes" and skips
  - [ ] On re-sync, if hash differs and a JE was already posted, system flags for manual decision
  - [ ] Configurable update behavior in Settings: void-and-repost / update-in-place / require manual decision
  - [ ] Default for new connections: require manual decision
  - [ ] Re-staging a day preserves the prior version in history (no data loss)

#### F8: Audit Log — Full Event History (Items #20-23)
- **User Story:** As a store owner, I want a permanent record of every sync, edit, approval, rejection, and void so that my accountant can trace any transaction to its source.
- **Acceptance Criteria:**
  - [ ] Dedicated Audit Log page with a filterable, sortable table
  - [ ] Events captured: sync_staged, sync_posted, sync_failed, edited, approved, rejected, voided, re_synced
  - [ ] Each event records: timestamp, event type, actor (user name + ID), sync date, details/payload
  - [ ] Filters: date range, event type, sync date, user
  - [ ] Export to CSV button generates a downloadable file with all visible rows
  - [ ] Audit log is append-only — entries cannot be edited or deleted

#### F9: Dashboard (Item #30 — Dashboard page)
- **User Story:** As a store owner, I want a QuickBooks dashboard that shows connection status, recent activity, and any issues at a glance so that I don't have to dig through multiple pages.
- **Acceptance Criteria:**
  - [ ] Shows connection status: connected/disconnected, company name, realm ID
  - [ ] Shows last successful sync date and time
  - [ ] Shows pending approval count (links to Approval Queue)
  - [ ] Shows recent failures count (last 7 days)
  - [ ] Shows mapping completeness (X/37 mapped)
  - [ ] Shows sync mode (auto / manual / disabled)
  - [ ] Recent activity feed: last 10 events from the audit log

#### F10: Settings Page (Items #4, #5, #18, #42)
- **User Story:** As a store owner, I want a dedicated settings page to configure my QB integration preferences so that I can control sync behavior, notifications, and connection details.
- **Acceptance Criteria:**
  - [ ] Connection section: shows connected account info, disconnect button, re-connect button
  - [ ] Sync mode selector: auto / manual approval / disabled
  - [ ] Sync cadence reminder: daily / weekly / monthly / on-demand (informational label, drives in-app reminders)
  - [ ] Update behavior: void-and-repost / update-in-place / require manual decision
  - [ ] Default JE memo template (text field with placeholders: `{typeNum}`, `{date}`, `{companyName}`)
  - [ ] Sandbox vs. production indicator (read-only, set by environment)
  - [ ] All settings save immediately with confirmation feedback

#### F11: Onboarding Flow Improvements (Items #34-35)
- **User Story:** As a new store connecting QB for the first time, I want to be guided directly to the account mapping page after OAuth so that I can't accidentally skip setup.
- **Acceptance Criteria:**
  - [ ] After successful OAuth callback, redirect goes to Account Mapping page (not Settings or Dashboard)
  - [ ] New connections default to Manual Approval sync mode
  - [ ] First-time setup shows a welcome banner with steps: "1. Map your accounts → 2. Review a test sync → 3. Go live"
  - [ ] User cannot trigger a sync until at least the critical fields are mapped (payments + sales categories)

#### F12: Throwable Catch Fix (Hardening Item #43)
- **User Story:** As a system operator, I want all QB error handlers to catch `\Throwable` instead of `\Exception` so that PHP 8.5 TypeError/ValueError don't cause silent 500 errors.
- **Acceptance Criteria:**
  - [ ] All `catch (Exception $e)` blocks in QB classes replaced with `catch (\Throwable $e)`
  - [ ] Error details are logged to the QB log file before being re-thrown or returned
  - [ ] No silent 500 errors from type mismatches in QB code paths

#### F13: Token Refresh Mutex (Hardening Item #44)
- **User Story:** As a system operator, I want concurrent QB operations to coordinate around token refresh so that simultaneous requests don't invalidate each other's tokens.
- **Acceptance Criteria:**
  - [ ] Only one process at a time can refresh the QB access token (mutex/lock)
  - [ ] Other processes wait for the refresh to complete, then use the new token
  - [ ] Lock has a timeout (30 seconds) to prevent deadlocks
  - [ ] If lock acquisition fails, operation is retried once with fresh token read

### Should Have Features

#### F14: Void a Posted JE (Items #24-25)
- **User Story:** As a store owner, I want to void a journal entry that was already posted to QBO from inside BuyerKiosk so that I don't have to log into QBO to fix a mistake.
- **Acceptance Criteria:**
  - [ ] Sync Log and Audit Log show a "Void" button for posted entries
  - [ ] Voiding calls QBO API to delete/void the JE
  - [ ] Void is logged in the audit log with actor and reason
  - [ ] When configured, auto-void prior JE on re-sync (prevents duplicate ledger entries)
  - [ ] Void confirmation dialog warns that this action cannot be undone in BuyerKiosk

#### F15: Sync Log Page with Date Range (Item #31 — replaces calendar)
- **User Story:** As a store owner, I want to view sync history for any date range with status filtering so that I can quickly find days that need attention.
- **Acceptance Criteria:**
  - [ ] Date range picker at top (default: last 30 days)
  - [ ] List view of days in range, each showing: date, status badge, JE DocNumber, total debits/credits
  - [ ] Status filter: all / success / failed / pending / voided
  - [ ] Clicking a day expands to show full sync detail + available actions (retry, void, view in audit log)
  - [ ] Visual status badges: green (success), red (failed), yellow (pending), gray (no data), purple (voided)

#### F16: Reconciliation — Variance Report (Items #26-28)
- **User Story:** As a store owner or bookkeeper, I want to compare daily close totals against what was posted to QBO so that I can identify discrepancies.
- **Acceptance Criteria:**
  - [ ] Dedicated Reconciliation page with date range selector
  - [ ] For each day: shows POS daily close total, QBO journal total (via live API query), and variance
  - [ ] Days with non-zero variance are highlighted
  - [ ] Clicking a day with variance shows per-line breakdown (which fields differ)
  - [ ] "Last reconciled" indicator per store shows when reconciliation was last run
  - [ ] Reconciliation results can be exported to CSV

#### F17: Account Mapping — Full Page Wizard (Item #30 — Mapping page)
- **User Story:** As a store owner, I want a dedicated full-page account mapping experience with progress tracking so that I can complete setup without confusion.
- **Acceptance Criteria:**
  - [ ] Full-page layout (not embedded in a multi-purpose settings page)
  - [ ] Fields grouped by category: Payments, Sales, COGS, Buys, Cash, Other
  - [ ] Each field shows: name, description, entry type (DR/CR), current QBO account selection
  - [ ] QBO account dropdown is filterable with search
  - [ ] Progress bar shows overall mapping completion
  - [ ] "Save All" button with confirmation feedback
  - [ ] Unmapped critical fields (payments, sales) show warning badges

#### F18: In-App Notifications via Ably (Items #36-37)
- **User Story:** As a store owner, I want to receive in-app notifications when syncs fail or items are pending approval so that I can act promptly.
- **Acceptance Criteria:**
  - [ ] Failed sync triggers an Ably notification to users with QB permission for that store
  - [ ] Pending approval reminder triggers if items wait > N days (configurable, default: 3 days)
  - [ ] Notifications appear in the BuyerKiosk notification system (bell icon or toast)
  - [ ] Clicking a notification navigates to the relevant page (Approval Queue or Sync Log)

#### F19: QBO API Rate Limiter (Hardening Item #45)
- **User Story:** As a system operator, I want QB API calls to respect QBO's rate limits so that bulk operations (reconciliation, batch approval) don't get throttled.
- **Acceptance Criteria:**
  - [ ] QB API calls are rate-limited to stay under QBO's 500 requests/minute/realmId
  - [ ] Batch operations (reconciliation across 30 days) space out API calls
  - [ ] Rate limit hits trigger exponential backoff with retry
  - [ ] User sees progress indicator during rate-limited operations ("Checking day 15 of 30...")

#### F20: Sandbox Banner (Hardening Item #46)
- **User Story:** As a store owner, I want to clearly see when the QB integration is in sandbox/development mode so that I don't accidentally think test data is real.
- **Acceptance Criteria:**
  - [ ] All QB pages show a prominent "SANDBOX MODE" banner when environment is development
  - [ ] Banner is visually distinct (orange/yellow) and cannot be dismissed
  - [ ] Dashboard status section shows "Environment: Sandbox" vs. "Environment: Production"

#### F21: Data Freshness Check (Hardening Item #47)
- **User Story:** As a system operator, I want the nightly sync job to gracefully handle missing daily close data so that it doesn't fail silently or create empty JEs.
- **Acceptance Criteria:**
  - [ ] If daily close data for the target date doesn't exist at sync time, status is set to `data_pending` (not `failed`)
  - [ ] `data_pending` syncs are retried in the next cycle (up to 3 attempts over 3 days)
  - [ ] After 3 days with no data, status changes to `data_missing` and a notification is sent
  - [ ] Dashboard shows `data_pending` and `data_missing` as distinct statuses

### Could Have Features

#### F22: Bulk Approve & Post Across Date Range
- **User Story:** As a store owner who was away for a week, I want to approve all pending entries for a date range at once so that I don't have to approve each day individually.
- **Acceptance Criteria:**
  - [ ] Approval Queue has a "Select Date Range" mode
  - [ ] Selected entries are approved and posted sequentially with progress indicator
  - [ ] If any entry fails to post, the process continues with remaining entries and reports failures at the end

#### F23: Diff Preview Before Approval
- **User Story:** As a store owner, I want to see a side-by-side comparison of original vs. edited values before I approve a modified entry.
- **Acceptance Criteria:**
  - [ ] When an entry has been edited, the approve dialog shows a diff view
  - [ ] Changed values are highlighted (red for original, green for new)
  - [ ] Unchanged lines are shown but dimmed
  - [ ] Edit reasons are displayed next to each changed line

#### F24: Configurable JE Memo Template
- **User Story:** As a store owner, I want to customize the memo/note that appears on journal entries in QBO so that they match my accounting conventions.
- **Acceptance Criteria:**
  - [ ] Settings page has a memo template field
  - [ ] Supports placeholders: `{typeNum}`, `{date}`, `{companyName}`, `{syncType}`
  - [ ] Default template: "Daily Sales - {companyName} ({typeNum}) - {date}"
  - [ ] Preview shows what the memo will look like for a sample entry

### Won't Have (This Phase)

- **Granular permissions** — Single `quickbooks_config` permission is sufficient. All QB users get full access to all QB pages. Granular roles (viewer, approver, editor) deferred to a future phase if demand emerges.
- **SMS/Email notifications** — Starting with in-app Ably notifications only. SMS via Twilio/Vonage and email notifications deferred to a future iteration.
- **Account mapping validation warnings** — Won't add soft warnings when entry type conflicts with QBO account type. Users are trusted to map correctly; the balance check catches mathematical errors.
- **Multi-company QBO support** — Each store connects to one QBO company. Multi-company (one store → multiple QBO books) is out of scope.
- **QBO Bill/Invoice sync** — Only journal entries are synced. Other QBO transaction types (bills, invoices, payments) are out of scope.
- **POS data transformation rules** — The system syncs S-file fields as-is (with optional manual edits). Automated data transformation rules (e.g., "always add 2% to tax") are out of scope.
- **Automated reconciliation resolution** — Reconciliation shows variances but doesn't auto-fix them. Automated correction would require deep accounting logic that's out of scope.

## Detailed Feature Specifications

### Feature: F5 — Manual Approval Queue (Most Complex Must-Have)

**Description:** The Approval Queue is the centerpiece of the manual approval workflow. When a store is in Manual Approval mode, the nightly sync job builds journal entries from daily close data and stages them locally with status `pending_approval`. Operators review, optionally edit, and approve or reject each entry from this page. Only approved entries are posted to QBO.

**User Flow:**
1. User navigates to QuickBooks → Approval Queue in the sidebar
2. System displays a list of all `pending_approval` entries, sorted by date (oldest first)
3. User clicks an entry to expand it → sees all journal lines with amounts, accounts, DR/CR indicators
4. User reviews the lines:
   - If correct → clicks "Approve & Post"
   - If needs correction → clicks "Edit" → modifies values → enters reason → saves
   - If should not post → clicks "Reject" → enters reason (min 10 chars)
5. On "Approve & Post": system calls QBO API → creates JE → updates status to `posted` → logs audit event
6. On "Reject": system sets status to `rejected` → logs audit event with reason → entry leaves the queue
7. Badge count on sidebar link updates in real-time

**Business Rules:**
- Rule 1: Entries MUST be approved in chronological order for a given store (can't approve Wednesday before Tuesday if both are pending)
- Rule 2: Rejected entries can be re-staged (manually triggered re-sync from Sync Log)
- Rule 3: If an entry is edited and then approved, both the original and edited snapshots are stored in the audit log
- Rule 4: Bulk approve processes entries sequentially and stops on first failure, reporting which entries succeeded
- Rule 5: An entry cannot be approved if mappings are incomplete (< 100% mapped)
- Rule 6: Tax collected field cannot be zeroed out during editing (hard validation rule)
- Rule 7: Adjustment lines added during editing require a memo of at least 10 characters
- Rule 8: The approve action is blocked if edited debits ≠ edited credits (within $0.01)

**Edge Cases:**
- Scenario 1: QBO API is down when user clicks Approve → Expected: Show error, keep entry as `pending_approval`, log the failed attempt
- Scenario 2: Token expired during approval → Expected: Auto-refresh token (with mutex), retry the post once
- Scenario 3: Someone edits an entry while another user is also viewing it → Expected: Last save wins, but audit log captures both users' actions
- Scenario 4: Store switches from Manual to Auto mode while entries are pending → Expected: Pending entries remain in queue and must be approved/rejected before auto-mode takes effect for new days
- Scenario 5: Daily close data is re-ingested (POS re-sends S-file) for a day that's already pending → Expected: Re-staging replaces the pending entry with new data, preserving the prior version in audit history
- Scenario 6: User approves an entry but QBO creates it with a different DocNumber than expected → Expected: Log the QBO-assigned DocNumber, update our sync log record

### Feature: F6 — Pre-Sync Value Editing

**Description:** Before approving a staged JE, the operator can modify individual line amounts, change which QBO account a line maps to (per-day override), or add free-form adjustment lines. All edits are tracked with required justifications.

**User Flow:**
1. User expands a pending entry in the Approval Queue
2. Clicks "Edit" button → line items become editable
3. User changes an amount field (e.g., corrects cash tendered from $1,247.50 to $1,247.00)
4. System prompts for edit reason (required if change > $1.00)
5. Live balance indicator shows: Debits: $X | Credits: $Y | Difference: $Z
6. User clicks "Add Adjustment Line" → enters description (min 10 chars), amount, debit/credit, QBO account
7. Balance indicator updates with the new line
8. Save button enables only when debits = credits (within $0.01)
9. User saves → system creates new payload snapshot, preserving original
10. User can now approve the edited entry (diff preview appears before final confirmation)

**Business Rules:**
- Rule 1: Original payload is NEVER modified — edits create a new snapshot stored alongside the original
- Rule 2: Edit reason is required when any single value changes by more than $1.00
- Rule 3: Tax collected (`taxCollected` field) cannot be changed to $0.00 — hard validation block
- Rule 4: Adjustment lines require a memo of at least 10 characters
- Rule 5: The account override (changing QBO account for a line) does NOT change the global mapping — it only affects this specific day's entry
- Rule 6: A maximum of 20 adjustment lines can be added per entry (prevents abuse)

**Edge Cases:**
- Scenario 1: User edits an entry, saves, then the daily close data is re-ingested → Expected: Re-staging creates a new base entry; prior edits are preserved in audit history but do NOT auto-apply to the new data
- Scenario 2: User adds an adjustment line that brings the JE out of balance → Expected: Save button stays disabled, balance indicator shows the imbalance in red
- Scenario 3: User tries to set tax collected to $0.00 → Expected: Validation error: "Tax collected cannot be zeroed out"

## Success Metrics

### Key Performance Indicators

- **Adoption:** 80% of QB-connected stores using Manual Approval mode within 30 days of launch; 100% of new connections start in Manual Approval mode (enforced by default)
- **Engagement:** Average time from "pending_approval" to "approved" < 24 hours; Reconciliation page used at least monthly by 60% of connected stores
- **Quality:** Zero duplicate JEs in QBO after launch (down from current occasional duplicates); Sync failure rate < 5% (excluding data_pending); Audit log captures 100% of state changes
- **Business Impact:** Reduce QB support tickets by 50% (currently driven by duplicate JEs and setup confusion); Enable 10+ new store QB connections in first quarter (up from 1 beta)

### Tracking Requirements

| Event | Properties | Purpose |
|-------|------------|---------|
| `qb_sync_staged` | typeNum, syncDate, lineCount, totalDebits, totalCredits, payloadHash | Track staging volume and data characteristics |
| `qb_sync_approved` | typeNum, syncDate, userId, wasEdited, editCount, timeInQueueHours | Measure approval velocity and edit frequency |
| `qb_sync_rejected` | typeNum, syncDate, userId, reason | Track rejection patterns and reasons |
| `qb_sync_posted` | typeNum, syncDate, journalEntryId, docNumber, postMethod (auto/manual) | Track successful posts and methods |
| `qb_sync_failed` | typeNum, syncDate, errorType, errorMessage, retryCount | Track failure patterns for reliability improvement |
| `qb_sync_voided` | typeNum, syncDate, userId, journalEntryId, reason | Track void frequency and reasons |
| `qb_entry_edited` | typeNum, syncDate, userId, fieldName, oldValue, newValue, reason | Track edit patterns and common corrections |
| `qb_reconciliation_run` | typeNum, dateRangeStart, dateRangeEnd, daysWithVariance, totalVariance | Track reconciliation usage and variance patterns |
| `qb_mapping_updated` | typeNum, fieldName, oldAccountId, newAccountId, userId | Track mapping changes over time |
| `qb_page_viewed` | typeNum, pageName, userId | Track which QB pages get the most traffic |
| `qb_onboarding_step` | typeNum, step (connect/mapping/first_sync/first_approve), completedAt | Track onboarding funnel completion |

---

## Constraints and Assumptions

### Constraints
- **QBO API Rate Limits:** 500 requests per minute per realmId. Reconciliation and batch operations must respect this limit with built-in throttling.
- **QBO Token Lifecycle:** Access tokens expire after 1 hour; refresh tokens expire after 100 days. The system must auto-refresh and handle the 100-day edge case gracefully.
- **Existing Data:** Stores with already-posted JEs using the old DocNumber format (`{typeNum}-{yyyymmdd}`) must be handled — de-dupe lookups check both old and new formats.
- **Single Permission Model:** Using the existing `quickbooks_config` permission for all QB pages. No new permission hooks need to be created.
- **PHP 8.5 Compatibility:** All catch blocks must use `\Throwable`, not `\Exception`.
- **Migration System:** All database changes must go through the conductor migration system — no manual SQL on store DBs.

### Assumptions
- **POS data arrives daily:** We assume `drsDailySFileData` is populated each business day by the POS system or S-file ingestion. If a store doesn't close on a given day, no data is expected.
- **Single QBO company per store:** Each BuyerKiosk store connects to exactly one QBO company. The system does not support routing different stores to different QBO organizations from one BK instance.
- **Users understand basic accounting:** The approval and editing interfaces assume the operator knows what debits and credits mean. We provide DR/CR badges and balance indicators but don't teach accounting concepts.
- **Ably is available for notifications:** Real-time notifications depend on the existing Ably infrastructure being operational. If Ably is down, notifications are silently skipped (not queued).
- **QBO sandbox is available for testing:** Development and staging environments use QBO's sandbox. Production stores use QBO production API.

## Risks and Mitigations

| Risk | Impact | Likelihood | Mitigation |
|------|--------|------------|------------|
| QBO API changes break our integration | High | Low | Pin SDK version, monitor Intuit developer changelog, add API response validation |
| Token refresh race condition (concurrent requests) | High | Medium | Implement Redis mutex lock around token refresh with 30s timeout |
| Rate limiting during reconciliation (many QBO queries) | Medium | Medium | Built-in rate limiter with exponential backoff; progress indicator for user |
| Users approve incorrect entries without reviewing | Medium | Medium | Require confirmation dialog; show diff for edited entries; audit trail enables correction |
| Migration breaks existing sync log data | High | Low | Migration adds new tables/columns only — no destructive changes to existing qb_sync_log |
| Ably notification delivery failure | Low | Low | Notifications are best-effort; dashboard always shows current state regardless of notification delivery |
| Old-format DocNumbers in QBO don't match new format lookups | Medium | Medium | De-dupe lookup checks BOTH formats; migration window documented in onboarding |
| Store switches modes while entries are pending | Medium | Low | Business rule: pending entries must be resolved before mode change takes full effect |

## Open Questions

- [x] DocNumber format — resolved: switch to `BK-{typeNum}-{yyyymmdd}` with backward compat lookups
- [x] Approval queue behavior in manual mode — resolved: nightly job stages locally, holds for approval
- [x] Reconciliation data source — resolved: live QBO API query for accuracy
- [x] Sidebar structure — resolved: new top-level section with 7 page links
- [x] Edit storage approach — resolved: full payload snapshot alongside original
- [x] Re-sync default behavior — resolved: require manual decision
- [x] Notification method — resolved: in-app Ably only (defer SMS/email)
- [x] Permission model — resolved: keep single `quickbooks_config` permission
- [x] Calendar UI approach — resolved: date range picker + list view (skip visual calendar)
- [x] Additional hardening items — resolved: include rate limiter, token mutex, sandbox banner, data freshness, Throwable fix
- [ ] What happens when QBO refresh token expires (100-day limit)? Should the system send an alert X days before expiry?
- [ ] Should the reconciliation page support exporting to QBO-compatible CSV format (for import), or just a flat audit CSV?
- [ ] Are there stores currently using the old DocNumber format that have posted JEs? (Impacts migration testing)

---

## Supporting Research

### Competitive Analysis
No direct competitor in the resale/pawn POS space offers a pre-sync editing and approval workflow for QBO journal entries. General-purpose QBO integrations (e.g., Synder, A2X) focus on e-commerce transaction sync and lack the manual review/edit capability. This is a differentiation opportunity for BuyerKiosk in the enterprise segment where accounting controls matter.

### User Research
Based on the pc00 beta deployment experience:
- Setup abandonment: pc00 connected OAuth but never completed account mapping — there was no progress indicator or blocking mechanism
- Duplicate JE complaints: at least one instance of duplicate ledger entries from a retry
- The setup page was described as "confusing" because everything was on one page
- Operators want to "see before it goes" — the number one requested capability is pre-sync review

### Market Data
- QuickBooks Online has 7.1M+ subscribers globally (Intuit FY2025 10-K)
- 89% of small businesses use some form of accounting software (Score.org 2024 survey)
- The #1 reason small businesses switch accounting integrations is "data accuracy concerns" (G2 Crowd 2025 report)
- POS-to-accounting automation is a top-3 requested feature in BuyerKiosk customer surveys
