# Accounting Export Abstraction Plan

## Purpose & Overview
Deliver a reusable accounting export layer that transforms Alga PSA invoices into accounting-ready payloads and adapters for QuickBooks (Online/Desktop) and Xero. The solution must support configurable GL/item/tax mappings, batchable exports with auditability, and integration points for both file-based and API-driven handoffs.

Primary outcomes:
- Canonical “accounting export” schema that captures invoice headers, line items, tax, service-period metadata, and mapping resolutions in tenant scope.
- Mapping management UX so finance teams align PSA services, tax regions, payment terms, and tracking dimensions with accounting identifiers without engineering changes.
- Export engine that validates invoices, resolves mappings, produces adapter-ready DTOs, and records export batches/status for reconciliation.
- QuickBooks adapter(s) (Online API, Desktop IIF/CSV) and Xero adapter (API or CSV) that translate canonical payloads into package-specific formats while reusing credentials and throttling services.

---

## Discovery Highlights *(Completed)*
- **Billing data richness:** Current `invoice_items` / `invoice_item_details` tables—slated to become `invoice_charges` / `invoice_charge_details`—already store service, contract, tax, and service-period metadata (`server/migrations/20250412214804_add_invoice_item_details_tables.cjs`, `docs/billing.md`). Amounts are persisted in cents (integers) while `transactions.amount` stays decimal, so the export layer must normalize currency precision.
- **Mappings backbone:** `tenant_external_entity_mappings` tracks per-tenant relationships between Alga entities and external IDs with optional metadata (`server/migrations/20250502173321_create_tenant_external_entity_mappings.cjs`). UI scaffolding exists under `server/src/components/integrations/qbo/*`, and server actions (`server/src/lib/actions/externalMappingActions.ts`) expose CRUD, but lookups inside QBO workflows still rely on placeholders.
- **Event-driven QBO sync (retired):** Legacy workflows (`server/src/lib/workflows/qboInvoiceSyncWorkflow.ts`, `qboCustomerSyncWorkflow.ts`) have been removed in favor of the shared accounting export abstraction. `QboClientService` now serves only the adapter.
- **Client + tax metadata:** `clients`, `client_locations`, `client_tax_rates`, and `tax_rates` tables hold billing contacts, addresses, tax regions, and default tax codes (`server/migrations/20251003000001_company_to_client_migration.cjs`). Invoice generation already calls `TaxService` and stores `tax_rate` and `tax_region` on each line.
- **Gaps identified:**
  - No canonical export tables or status tracking exist today; reconciliations rely on `transactions`.
  - Invoice schema lacks currency code/precision fields despite multi-currency goals noted in `docs/overview.md`.
  - Mapping UIs retrieve QBO catalog data but assume a single realm and do not support fallback mapping by contract line/service category.
  - Xero integrations are absent; only QuickBooks Online scaffolding exists.

---

## Scope & Deliverables
- Canonical accounting export data model and services covering invoices, line items, taxes, and mapping resolutions.
- Mapping UX enhancements (service category fallbacks, tax code alignment, GL account mapping) leveraging `tenant_external_entity_mappings`.
- Export orchestration service with batch creation, validation, error reporting, and rerun controls.
- Adapter implementations:
  - QuickBooks Online API export (leveraging `QboClientService`).
  - QuickBooks Desktop GL export (IIF or CSV).
  - Xero API/CSV export with OAuth connection management.
- Operations UI for listing export batches, downloading files, inspecting errors, and marking batches as posted.
- Automated tests (unit/integration) and sandbox verification flows for each adapter.

Out of scope for this iteration: automatic payment imports, two-way sync of journal entries, and generalized ERP integrations beyond QuickBooks/Xero.

---

## Phase 1 – Data Model & Canonical Schema
- [ ] **Terminology alignment (Charges)**
  - Rename `invoice_items` table to `invoice_charges`; create compatibility view `invoice_items` for interim references and update ORM/Knex bindings.
  - Rename supporting detail tables (`invoice_item_details` → `invoice_charge_details`, `invoice_item_fixed_details` → `invoice_charge_fixed_details`) and adjust foreign keys.
  - Update TypeScript interfaces (`IInvoiceItem` → `IInvoiceCharge`), billing engine services, and API serializers to use “charge” terminology while maintaining backward-compatible DTO aliases where external integrations rely on the old name.
  - Migrate tests, seeds, and documentation to the new naming; publish release notes and migration guidance for self-hosted tenants.
- [x] **Add export tables**
  - `accounting_export_batches` (tenant, adapter, target_company/realm, export_type, filters, triggered_by, status timestamps, checksum).
  - `accounting_export_lines` (batch_id, tenant, invoice_id, invoice_charge_id, canonical amounts, currency, service dates, mapping references, tax breakdown, export payload snapshot).
  - `accounting_export_errors` (batch_id, line_id nullable, code, message, resolution_status).
- [ ] **Normalize currency handling**
  - Introduce `currency_code` + `exchange_rate` columns on `invoices` (defaulting to tenant currency) and extend billing engine to populate them.
  - Convert `transactions.amount` to an integer column (representing cents) via staged migration: add temp integer column, backfill by multiplying existing decimals, swap, and remove the legacy decimal.
  - Ensure `invoice_charges.unit_price/total_price` and `transactions.amount` share the same integer-based representation; add helper utilities when interfacing with external APIs that expect decimals.
- [x] **Canonical DTOs**
  - Create TypeScript interfaces (e.g., `AccountingInvoiceExport`, `AccountingLineExport`) under `server/src/interfaces/accountingExport.interfaces.ts`.
  - Implement a builder (e.g., `AccountingExportAssembler`) that reads invoices, joins `invoice_charge_details`, `client_contract_lines`, `services`, `tax_rates`, and emits canonical DTOs.
- [ ] **Audit trails**
  - Store rendered adapter payload (JSON blob / file path) per line or batch for traceability.
  - Add relationship from `transactions` to `accounting_export_batches` (nullable FK) for reconciliation reporting.

### Schema Overview (ASCII)
```
                             +-------------------------------+
                             | accounting_export_batches     |
                             |-------------------------------|
                             | batch_id (PK)                 |
                             | tenant                        |
                             | adapter_type                  |
                             | target_realm                  |
                             | export_type                   |
                             | filters_json                  |
                             | status                        |
                             | started_at / completed_at     |
                             | checksum                      |
                             +---------------+---------------+
                                             |
                              1 batch : N lines
                                             v
               +-----------------------------+-----------------------------+
               | accounting_export_lines                                  |
               |----------------------------------------------------------|
               | line_id (PK)                                             |
               | batch_id (FK -> accounting_export_batches.batch_id)      |
               | tenant                                                   |
               | invoice_id (FK -> invoices)                              |
               | invoice_charge_id (FK -> invoice_charges)                |
               | canonical_amount_cents                                   |
               | currency_code / exchange_rate                            |
               | service_period_start / service_period_end                |
               | mapping_resolution_json                                  |
               | delivery_status                                          |
               | external_document_ref                                    |
               +-----------------------------+----------------------------+
                                             |
                              1 line : N errors
                                             v
               +----------------------------------------------------------+
               | accounting_export_errors                                 |
               |----------------------------------------------------------|
               | error_id (PK)                                            |
               | batch_id (FK -> accounting_export_batches.batch_id)      |
               | line_id NULLABLE (FK -> accounting_export_lines.line_id) |
               | code                                                     |
               | message                                                  |
               | resolution_state                                         |
               | created_at / resolved_at                                 |
               +----------------------------------------------------------+
                                             ^
                                             |
                         nullable link from transactions ledger

 existing tables:
   invoices ---------+
                     |
   invoice_charges --+--> accounting_export_lines (line-level joins)

   tenant_external_entity_mappings --(
        referenced via mapping_resolution_json lookup metadata

   transactions -----(
        optional FK -> accounting_export_batches for reconciliation
```

## Phase 2 – Mapping & Configuration Enhancements
- [ ] **Extend mapping schema**
  - Allow hierarchical keys (service_id, contract_line_id, service_category) with priority ordering; store in `metadata` or add dedicated columns.
  - Introduce mapping types for GL account (`gl_account`), revenue class (`class`/`department`), and tax code fallback.
  - Track effective dates / versioning where needed (e.g., mapping changes mid-year).
- [ ] **Mapping UI upgrades**
  - Update QBO mapping tables to support fallback resolution order and surface validation warnings for unmapped entities.
  - Add Xero mapping pages (items/accounts, tax rates, tracking categories) reusing the same `externalMappingActions`.
- [x] **Lookup services**
  - Replace placeholder lookup actions with shared resolver (`AccountingMappingResolver`) that queries `tenant_external_entity_mappings`, supports fallback rules, and caches results during batch export.
- [x] **Validation hooks**
  - Add pre-export validation step that flags invoices/lines missing required mappings (service, tax code, payment term) and records them in the error table.
  - API append-line path now triggers validation immediately to transition batches to `ready` or `needs_attention`.

## Phase 3 – Export Engine & Workflow Integration
- [x] **Batch orchestration**
  - Service API (`createAccountingExportBatch`, `executeAccountingExportBatch`) handling filters (date range, invoice status, tenant, integration target).
  - Option to schedule recurring exports via Automation Hub or cron jobs; record source trigger.
- [x] **API surface**
  - Expose REST endpoints under `/api/accounting/exports` for batch CRUD, line/error append, and status updates to support UI and automation integrations.
  - Added `POST /api/accounting/exports/{batchId}/execute` route (Next controller delegates to `AccountingExportService.executeBatch`).
- [x] **Adapter interface**
  - New `AccountingExportAdapter` contract under `server/src/lib/adapters/accounting/accountingExportAdapter.ts` covers `capabilities`, `transform`, `deliver`, optional `postProcess` plus unified payload/result types.
  - `AccountingAdapterRegistry` now bootstraps QuickBooks Online/Desktop and Xero stubs (`server/src/lib/adapters/accounting/*Adapter.ts`) so integrations resolve by `adapter_type`.
- [x] **Workflow alignment**
  - `AccountingExportService.executeBatch` emits `ACCOUNTING_EXPORT_COMPLETED` / `ACCOUNTING_EXPORT_FAILED` via event bus once delivery succeeds or fails; events added to `server/src/lib/eventBus/events.ts` and shared workflow schemas.
  - Execution API path above allows workflows/Automation Hub to invoke the canonical exporter in place of bespoke scripts (hook-up pending but interface ready).
  - Registered workflow actions (`accounting_export.create_batch`, `accounting_export.execute_batch`) so Automation Hub/typed workflows can orchestrate new exporter without legacy QBO scripts.
- [x] **Status tracking**
  - Repository `updateBatchStatus` now preserves timestamps unless explicitly overwritten; service records `validating`, `ready`, `delivered`, `failed` transitions with `validated_at`/`delivered_at` stamps.
  - Delivery loop updates `accounting_export_lines.status` + `external_document_ref` and keeps batch notes for failure messaging.

## Phase 4 – QuickBooks Adapter Implementation
- [ ] **QuickBooks Online**
  - [x] Build adapter transform/deliver pipeline that assembles invoices from canonical export lines, hydrates service/customer mappings, and calls `QboClientService` for create/update with mapping persistence.
  - [x] Map tax codes and payment terms via extended resolver helpers; persist SyncToken + `last_exported_at` metadata (`tenant_external_entity_mappings.metadata`).
  - [ ] Implement rate limiting, partial failure retries per invoice, and richer status propagation back to batch tables.
  - [x] Retire placeholder workflow actions (`lookup_qbo_item_id`, `create_qbo_invoice`) in favor of adapter orchestration; route workflows through export service (actions now emit deprecation failures that instruct callers to use `accounting_export.*`).
  - [x] Update `qboInvoiceSyncWorkflow` to seed accounting export batches and call `AccountingExportService` instead of invoking legacy QBO invoice actions directly.
- [ ] **QuickBooks Desktop GL Export**
  - Define IIF (or CSV) writer that translates canonical batches into `TRNS`/`SPL` rows with account codes, classes, terms, and due dates.
  - Store generated file path in `accounting_export_batches` and expose Download action in UI.
  - Provide mapping for GL account codes (income, AR, tax liabilities) via new mapping types.

## Phase 5 – Xero Adapter Implementation
- [ ] **Connectivity**
  - [x] Introduce `XeroClientService` scaffold with OAuth placeholders and logging hooks for future API calls.
  - [x] Implement retrieval of Xero items, accounts, tax rates, and tracking categories for mapping UI (`listAccounts`, `listItems`, `listTaxRates`, `listTrackingCategories`).
- [ ] **Invoice payload**
  - [x] Group canonical export lines into per-invoice payload drafts and prime connection metadata for future API mapping.
  - [x] Handle multi-tax lines (GST/VAT) per Xero requirements and map contact references via resolver lookups (service + tax resolver integration, tracking normalization).
- [ ] **Error handling**
  - [x] Normalize Xero API errors into export error records and flag failed lines for targeted retries via batch metadata. *(UI trigger for manual retry remains to be implemented.)*

## Phase 6 – UI & Operational Tooling
- [ ] **Export dashboard**
  - Add “Accounting Exports” tab under billing settings showing batches, status, adapter, created by, exported totals, and links to download payload or view errors.
- [ ] **Invoice detail integration**
  - Surface export status per invoice (e.g., “Exported to QuickBooks on 2025-11-02”) with link to batch record.
  - Allow manual re-export for a single invoice after correcting mappings (creates new batch or appends to existing pending batch).
- [ ] **Notifications**
  - Trigger email/task notifications for failed batches or unmapped line items to assigned finance role.

## Phase 7 – Testing, QA, and Rollout
- [ ] **Automated tests**
  - Unit tests for mapping resolver, canonical assembler, adapter transformations.
  - Integration tests using sqlite/pg tenant fixtures to verify batch creation and error paths.
  - Sandbox tests hitting QuickBooks Online sandbox and Xero demo company; capture sample payloads for regression fixtures.
- [ ] **Data migration & backfill**
  - Backfill currency codes and service-period data for historical invoices before enabling exports.
  - Migrate existing QBO mapping data into new hierarchy/metadata structure.
- [ ] **Documentation & training**
  - Author admin guide covering mapping maintenance, export workflows, retries, and reconciliation steps.
  - Publish release notes and upgrade checklist for tenants (include mapping prerequisites).
- [ ] **Launch plan**
  - Enable feature flag for internal tenant; run pilot exports with finance team.
  - Roll out QuickBooks Online first, follow with Desktop and Xero after validation.

---

## Dependencies & Integration Points
- `tenant_external_entity_mappings` (extend schema and UI) and associated RLS policies (`server/migrations/20250512135501_update_constrains_and_fks.cjs`).
- Billing engine outputs (`server/src/lib/billing/billingEngine.ts`, `server/src/lib/services/invoiceService.ts`) for canonical data assembly.
- Event bus + Automation Hub if exports are scheduled or triggered by workflows (`server/src/lib/eventBus`, `shared/workflow/init/registerWorkflowActions.ts`).
- Event bus now exposes `ACCOUNTING_EXPORT_COMPLETED` / `ACCOUNTING_EXPORT_FAILED` types; downstream consumers must update catalogs (`shared/workflow/streams/eventBusSchema.ts`, `shared/workflow/types/eventCatalog.ts`).
- Secret management (`@alga-psa/core` secret provider) for QuickBooks/Xero credentials.
- QuickBooks Online adapter now persists invoice-level mappings in `tenant_external_entity_mappings` (`alga_entity_type='invoice'`); ensure migrations/cleanup scripts account for the new entity type.

---

## Risks & Mitigations
- **Mapping drift / missing references:** Mitigate with validation gates, batch-level blockers, and proactive UI warnings; consider nightly report of unmapped services.
- **Currency inconsistencies:** Normalize to cents in canonical DTOs, store currency code on invoices, and add regression tests around rounding.
- **Adapter throttling / rate limits:** Use per-tenant throttling queues and exponential backoff (reuse existing QBO throttling helpers).
- **Workflow overlap:** Coordinate with existing QBO workflow owners to deprecate placeholder logic and avoid duplicate pushes.
- **Tenant isolation in new tables:** Apply RLS and composite keys mirroring invoice tables to preserve tenancy guarantees.

---

## Open Questions
- Do we need to support QuickBooks Desktop first (file-based) or prioritize QuickBooks Online? Outputs differ (IIF vs. API).
- Should payments posted in accounting flow back into PSA (`transactions`) as part of this initiative or a follow-up?
- How many revenue recognition dimensions (department/class/location) must be supported at launch? Do we need additional PSA metadata to map them?
- What is the minimal viable currency story (single base currency vs. true multi-currency)? Do we require historical exchange rates per invoice?
- Are there regulatory requirements (e.g., VAT digital links) that dictate export format or audit storage beyond current design?

---

## Screen Architecture & User Flows

### Accounting Settings ▸ Accounting Integrations (`/msp/settings/accounting`)
- **Layout:** Two-column settings shell. Left nav highlights “Accounting”. Primary content area renders accounting integration cards and mapping workspace.
- **Adapter Tabs:** Tab bar (`id="accounting-integration-tabs"`) with entries for each connected adapter (QuickBooks Online, QuickBooks Desktop, Xero). Selecting a tab loads adapter-specific mappings panel.
- **Mappings Panel**
  1. Sections for **Service Items**, **Tax Codes**, **Payment Terms**, and optional **GL/Class Mappings** rendered as cards with tables (Radix `Table` component).
  2. Each table header includes `Add Mapping` button (`id="add-{entity}-mapping-button"`) that opens `QboMappingFormDialog`/`XeroMappingFormDialog`.
  3. Table rows provide `Edit` (`id="edit-{entity}-mapping-{rowId}"`) and `Remove` actions in dropdown menus (`{entity}-mapping-actions-menu`).
  4. Fallback order modal triggered by `Configure Fallback` chip; modal lists overrides (contract line, service, category) with drag handles to reorder and checkbox to enable fallback.
  5. CSV import available via `Import CSV` button which opens upload dialog; download template link included.
- **Bulk Operations:** Toolbar (`id="mapping-toolbar"`) with adapter realm selector, `Refresh from Adapter`, `Import CSV`, and `Export CSV`.
- **Permissions:** Non-finance roles see tables in read-only state (buttons disabled, actions hidden) with tooltip “Finance role required”.

### Accounting Exports Dashboard (`/msp/billing/accounting-exports`)
- **Filters Bar:** Date range picker (`id="export-date-filter"`), status multi-select, adapter select, client search. `Reset Filters` chip clears selections.
- **Batch Table:** Columns for Batch ID, Created At, Adapter, Invoice Count, Amount, Status, Created By. Row click opens side drawer.
- **Create Batch CTA:** `New Export` button triggers modal with filter form (date range, invoice status, adapter target, optional client filter). `Preview Invoices` step shows resolved invoice list before confirming.
- **Side Drawer (Batch Detail):**
  - Tabs for Overview (summary, totals, download links) and Line Items (table with invoice, amount, status, message).
  - `Mark as Posted`, `Cancel Batch`, `Retry Failed Lines` buttons shown based on status.
  - Activity log timeline showing state transitions and actor.

### Invoice Detail Augmentation (`/msp/billing/invoices/[invoiceId]`)
- **Export History Card:** Within right-hand sidebar. Lists export events (adapter, status, batch ID link, timestamp, actor). `Re-export Invoice` button shown when invoice eligible; opens modal referencing batch creation wizard with invoice preselected.
- **Status Chips:** When exported, invoice header shows chip “Exported to {Adapter} on {date}”.

### Notifications & Task Inbox
- **Toast/Email:** Failed batches raise toast with link to batch drawer and send email to finance distribution list.
- **Task Inbox Card:** Workflow-generated tasks for failures display `Review Accounting Export` template embedded with summary and quick actions (`Acknowledge`, `Open Batch`).

### Permissions
- Navigation and API routes are always available; access remains gated by finance-centric roles.
- Role matrix: `finance_admin` full access; `billing_manager` can run exports but not edit mappings; others view-only.

---

## Acceptance Tests
- **Mapping Management**
  - `MappingCRUD#L1`
    1. Log in as `finance_admin`; navigate to `/msp/settings/accounting`.
    2. Select “QuickBooks Online” tab (`accounting-integration-tabs`).
    3. In Service Items section, click `Add Mapping` button; complete dialog (select PSA service `svc-001`, select QBO item `Consulting`, save).
    4. Verify new row shows PSA Service Name and QBO Item Name.
    5. Open row actions (`service-mapping-actions-menu-{rowId}`) → Edit; change QBO item to `Consulting - Premium`; save; verify table updates.
    6. Open row actions → Delete; confirm removal; entry disappears.
    7. Open Audit Log (existing system) and confirm create/update/delete entries referencing mapping ID.
    - *Integration coverage:* `server/src/test/integration/accounting/mappingCrud.integration.test.ts` exercises create/list/update/delete flows via server actions.
    - *Playwright coverage:* `ee/server/src/__tests__/integration/mapping-crud.playwright.test.ts` now drives the real `/msp/settings` screen with Playwright-provided mocks to verify mapping CRUD.
  - `MappingFallback#L1`
    1. From same screen, click `Configure Fallback` chip in Service Items card.
    2. Reorder fallback list to `Contract Line`, `Service`, `Category` by dragging handles.
    3. Enable category fallback checkbox and select category `Managed Services`.
    4. Save; run test export (see `ValidationUnmapped#L1` steps) with invoice covering contract line, confirm exported payload uses contracted mapping.
    5. Move category fallback to top; run batch preview again; verify preview now shows category-based mapping.
    6. Remove category fallback, save; attempt batch creation; ensure validation error highlights unmapped line with instruction to add mapping.
    - *Integration coverage:* `server/src/test/integration/accounting/accountingMappingResolver.integration.test.ts` validates service vs. category fallback and realm-specific resolution behaviour.
    - *Playwright coverage:* `ee/server/src/__tests__/integration/mapping-fallback.playwright.test.ts` exercises the UI harness for fallback ordering, category overrides, and validation feedback.
  - `XeroMappings#L1`
    1. Navigate to Xero tab; click `Refresh from Adapter` to pull latest accounts/tax/tracking; ensure spinner resolves without error.
    2. Add Service mapping selecting PSA service and Xero revenue account; ensure `AccountCode` column populated.
    3. Click `Import CSV`, upload sample mapping file; verify rows added without duplicates.
    4. Mark Xero account inactive externally; click `Refresh from Adapter`; verify affected row shows warning badge and validation message.
  - `MappingPermissions#L1`
    1. Log in as `billing_manager`; confirm Add/Edit/Delete buttons enabled.
    2. Log in as `support_agent`; navigate to same screen; verify controls disabled and tooltip text “Finance role required”.
    3. Attempt to POST via API as support agent; expect 403 and audit log entry for denied attempt.
    - *Integration coverage:* `server/src/test/integration/accounting/mappingPermissions.integration.test.ts` verifies mapping CRUD is permitted for finance users (`billing_settings` update) and denied for read-only users.
- **Export Validation & Execution**
  - `ValidationUnmapped#L1`
    1. Create invoice with service lacking mapping.
    2. Go to `/msp/billing/accounting-exports`; click `New Export`.
    3. Select adapter, date range covering invoice, proceed to Preview; expect preview table shows red badge “Mapping Required”.
    4. Attempt to confirm; modal blocks with error banner referencing missing mapping.
    5. After adding mapping, reopen modal, preview shows green check, confirmation succeeds.
    - *Integration coverage:* `server/src/test/integration/accounting/validationUnmapped.integration.test.ts` seeds an invoice with no service mapping, verifies `ensureMappingsForBatch` records the error/status, then confirms the batch flips to `ready` once the mapping exists.
    - *Playwright coverage:* `ee/server/src/__tests__/integration/mapping-validation-unmapped.playwright.test.ts` exercises the export wizard harness, ensuring unmapped services block confirmation until a mapping is added.
  - `ValidationCurrency#L1`
    1. Create invoice in EUR with stored exchange rate on invoice record.
    2. Run export wizard; on preview confirm displayed home currency totals match converted values.
    3. After batch delivery, inspect batch detail drawer to verify captured `currency_code`, `exchange_rate`, converted amount.
  - `BatchLifecycle#L1`
    1. Create batch via wizard and confirm; batch row shows `pending`.
    2. Trigger worker to process; observe status transitions to `validating`, `ready`, `delivered`.
    3. From drawer, click `Mark as Posted`; status updates.
    4. Attempt to re-run same filter range; wizard displays warning “Batch already exists”; prevents duplicate creation.
    5. Create second batch, cancel from drawer before delivery; status becomes `cancelled`; confirm actions disabled thereafter.
    - *Integration coverage:* `server/src/test/integration/accounting/batchLifecycle.integration.test.ts` exercises lifecycle transitions, duplicate guard, and cancellation execution blocks.
    - *Playwright coverage:* `ee/server/src/__tests__/integration/batch-lifecycle.playwright.test.ts` drives the lifecycle harness (worker simulation, duplicate guard, posting, and cancellation).
  - `InvoiceSelection#L1`
    1. Use filters: set date range, choose invoice statuses, select specific client.
    2. Click `Preview Invoices` to ensure only matching invoices appear.
    3. Verify manual invoices, multi-period items, credit memos, zero-dollar lines display with correct metadata.
    4. On confirm, ensure resulting batch references proper `transaction_id` links.
    - *Integration coverage:* `server/src/test/integration/accounting/invoiceSelection.integration.test.ts` validates filtered previews, metadata flags, and transaction linkage on batch creation.
  - `Concurrency#L1`
    1. Start batch creation for Tenant A; simultaneously attempt same range for Tenant B; both succeed.
    2. Attempt to create overlapping batch for Tenant A before first finishes; wizard shows blocking message referencing existing batch.
    3. Verify same invoice ID cannot enter two `pending` batches by inspecting batch detail data.
  - `AuditTrail#L1`
    1. Open delivered batch drawer; download canonical JSON snapshot via link.
    2. Confirm `transactions` row now stores integer `amount` matching invoice charge totals (in cents) and contains `accounting_export_batch_id` referencing batch.
    3. Run reporting query to ensure batch ID appears in finance audit view.
    - *Integration coverage:* `server/src/test/integration/accounting/auditTrail.integration.test.ts` exercises batch delivery, transaction linkage, and repository CRUD paths (status updates, line/error management).
- **Adapters – QuickBooks Online**
  - `QBOInvoiceCreate#L1`
    1. Ensure tenant connected to QBO via OAuth and mappings configured.
    2. Run export with new invoices; monitor job logs for create API call payload; confirm includes Line ServiceDate per invoice item.
    3. After success, open mapping table and confirm metadata column shows QBO invoice ID + SyncToken.
    4. In QBO sandbox, verify invoice exists with matching totals and tax.
    5. Inspect payload/assertions to ensure `Line[].SalesItemLineDetail.TaxCodeRef` matches mapped tax code and `SalesTermRef` reflects mapped payment term.
    6. Check `tenant_external_entity_mappings` has `alga_entity_type='invoice'` row for exported invoice with `sync_token` metadata set.
  - `QBOInvoiceUpdate#L1`
    1. Modify exported invoice in PSA (e.g., adjust quantity).
    2. Initiate re-export via Invoice detail `Re-export` button.
    3. Confirm system performs update call (sparse, includes SyncToken).
    4. Validate QBO invoice reflects change and mapping metadata updated with new SyncToken timestamp.
  - `QBOErrorHandling#L1`
    1. Configure mock to respond 429; execute export; ensure adapter retries with exponential backoff and records attempt log.
    2. Force 401 (expire token); verify adapter refreshes token and retries once; if refresh fails, batch flagged failed with reconnect prompt.
    3. Create invoice with missing mapping to cause validation error; ensure batch line enters `failed` with message referencing missing mapping.
  - `QBOPermissions#L1`
    1. Revoke OAuth consent from Intuit portal.
    2. Trigger export; verify batch stops with status `failed`, UI shows “Reconnect QuickBooks” CTA linking to OAuth flow.
    3. After reconnection, rerun export and confirm success.
  - `QBOClassTracking#L1`
    1. Add class/location mapping in settings.
    2. Export invoice; inspect QBO payload includes `ClassRef`/`DepartmentRef`.
    3. Remove mapping; export again; confirm fields omitted.
- **Adapters – QuickBooks Desktop**
  - `QBDFileGeneration#L1`
    1. On batch drawer, click `Download IIF`; verify file includes TRNS/SPL rows with invoice numbers.
    2. Import into QuickBooks Desktop sample company; ensure no import errors and totals match.
    3. Confirm batch record captures checksum and download timestamp.
  - `QBDAccountMapping#L1`
    1. Remove GL account mapping; attempt export; wizard blocks with error referencing missing account.
    2. Add mapping; export again; ensure generated file uses mapped account codes.
  - `QBDMultiInvoice#L1`
    1. Create batch covering >1 invoice; after delivery confirm single artifact produced.
    2. Download twice; ensure each download logged in batch activity without duplicating batch.
  - `QBDRetry#L1`
    1. After correcting mapping, click `Retry Failed Lines`; confirm new file generated and appended to activity log.
    2. Verify previous failed lines now marked delivered.
- **Adapters – Xero**
  - *Spec compliance:* `server/src/test/unit/accounting/xeroAdapter.spec.ts` asserts invoice payload construction and delivery stubs align with https://developer.xero.com/documentation/accounting/invoices (required fields, line structure, date formatting).
  - *Spec coverage (API client):* `server/src/test/unit/accounting/xeroClientService.spec.ts` verifies POST `/Invoices` payload/response handling and OAuth token refresh headers follow Xero API documentation.
  - `XeroConnectivity#L0`
    1. Provision demo credentials; call `listAccounts`, `listItems`, `listTaxRates`, and `listTrackingCategories`; verify non-empty payloads cached per tenant.
    2. Force access-token expiry; re-run `listAccounts`; confirm refresh token flow executes once and persistence updates secret store.
    3. Inspect structured logs to ensure tenant/connection metadata logged without leaking secrets.
    - *Unit coverage:* `server/src/test/unit/accounting/xeroClientService.spec.ts` stubs `/Accounts`, `/Items`, `/TaxRates`, `/TrackingCategories`, enforces refresh-on-401, and asserts logging/secret persistence behavior.
  - `XeroInvoiceCreate#L1`
    1. Connect tenant to Xero; ensure client/service/tax mappings exist.
    2. Run export; capture payload ensuring `AccountCode`, `TaxType`, and tracking categories align with mapping metadata.
    3. Verify in Xero sandbox invoice posts successfully with correct totals, currency, and reference number.
    - *Unit coverage:* `server/src/test/unit/accounting/xeroAdapter.spec.ts` + `xeroClientService.spec.ts` validate payload composition, mapping metadata, and successful API response handling.
  - `XeroMultipleTax#L1`
    1. Prepare invoice mixing GST/PST components; export.
    2. Confirm adapter assembles `taxComponents` metadata and resulting Xero invoice splits tax detail correctly.
    - *Unit coverage:* `server/src/test/unit/accounting/xeroAdapter.spec.ts` ensures multi-component tax metadata flows into invoice payload before delivery.
  - `XeroErrorHandling#L1`
    1. Disable mapped tax rate in Xero; execute export; expect service to raise `XERO_VALIDATION_ERROR` with per-document detail.
    2. After remapping, rerun export and ensure success path clears prior validation error (batch currently fails fast pending error persistence automation).
    - *Unit coverage:* `server/src/test/unit/accounting/xeroClientService.spec.ts` exercises validation error normalization and subsequent success retry.
  - `XeroCreditNote#L1`
    1. Create PSA credit memo linked to original invoice; export via Xero adapter.
    2. Validate Xero credit note references source invoice and issues negative line totals with matching tracking metadata.
    - *Status:* Credit-note export path not yet implemented; add adapter support before finalizing tests.
- **User Interface & Operations**
  - `ExportDashboard#L1`
    1. Navigate to dashboard; apply filters; confirm table updates.
    2. Use pagination to view additional batches; ensure data persists.
    3. As finance admin, download payload from drawer; as support agent, verify download button hidden/disabled.
    - *Integration coverage:* `server/src/test/integration/accounting/exportDashboard.integration.test.ts` validates dashboard filters, batch detail retrieval, and re-export dataset preparation.
  - `InvoiceDetail#L1`
    1. Open exported invoice; view Export History card listing batch links.
    2. Click batch link to open drawer; verify context slides in.
    3. Use `Re-export Invoice` button to start guided wizard prefilled with invoice; complete run and confirm new batch appended to history.
    - *Integration coverage:* `server/src/test/integration/accounting/exportDashboard.integration.test.ts` builds drawer detail data and exercises invoice-selector driven re-export batch creation.
  - `Notifications#L1`
    1. Cause batch failure; ensure toast displays within 5 seconds with “View Batch” CTA.
    2. Check finance inbox for email summary containing batch ID.
    3. Open Task Inbox, locate “Review Accounting Export” task, mark as resolved; confirm toast disappears and task closed.
  - `FeatureFlag#L1`
    1. Sign in as a non-finance role; confirm Accounting Exports navigation is visible but actions are disabled with “Finance role required” tooltips.
    2. Re-enable; ensure dashboard reappears with prior data intact.
- **Security & Isolation**
  - `TenantIsolation#L1`
    1. Log in as Tenant A user; ensure only Tenant A batches visible.
    2. Switch to Tenant B; confirm Tenant A data not present.
    3. Execute API call for foreign batch ID; expect 404/403.
  - `SecretManagement#L1`
    1. Rotate OAuth secret via secret provider; execute export; confirm new token used.
    2. Attempt to read secret via unauthorized user; expect denial with audit entry.
  - `LoggingPII#L1`
    1. Enable debug logging; run successful export; inspect logs for absence of tokens.
    2. Force failure; confirm error logs scrub sensitive fields.
- **Regression & Legacy Compatibility**
  - `LegacyReexport#L1`
    1. Select invoice created prior to feature rollout; add necessary mappings.
    2. Use invoice `Re-export` button; ensure service period defaults to invoice header and export succeeds.
  - `BillingEngineCompat#L1`
    1. Export batch; trigger next billing run generating new invoice.
    2. Verify original invoice still marked exported; new invoice listed as unexported until next batch.
  - `ArrearsAdvanceMix#L1`
    1. Invoice with both advance and arrears lines; export.
    2. In accounting payload, confirm service dates reflect respective periods; downstream system displays two distinct service windows.
- **Performance & Resilience**
  - `LargeBatch#L1`
    1. Seed 500+ invoices; run export; capture processing time (< threshold) and monitor resource metrics.
    2. Verify adapter paginates API calls to stay within vendor limits.
  - `RetryPolicy#L1`
    1. Configure retry count to 2; induce transient failures; ensure system retries twice then fails with aggregated reason message.
    2. Update policy to 5; confirm new limit applied on next run.
  - `SystemRestart#L1`
    1. Launch export; midway, restart worker service.
    2. Confirm batch resumes automatically and delivered payload contains no duplicates.
- **API & Tooling**
  - `APIExports#L1`
    1. Call `POST /api/accounting/exports` to create a batch and verify response schema matches DTO definitions.
    2. Call `GET /api/accounting/exports` with filters to confirm listings respect status/adapter parameters.
  - `APIExportDetails#L1`
    1. Append lines/errors via `POST /api/accounting/exports/{batchId}/lines` and `/errors`; ensure subsequent batch fetch returns appended data.
    2. Update batch status using `PATCH /api/accounting/exports/{batchId}` and verify state transitions recorded.
  - `ValidationMissingMapping#L1`
    1. Create batch, append a line lacking service mapping, and confirm validation marks batch `needs_attention` with error record.
    2. Add mapping and re-run validation (re-append or trigger) to see batch flip to `ready`.
  - `APIExecute#L1`
    1. Call `POST /api/accounting/exports/{batchId}/execute`; expect 200 with delivery summary containing delivered line IDs.
    2. Retrieve batch and lines; confirm statuses updated to `delivered` and `external_document_ref` populated with adapter stub values.
    3. Repeat call on already delivered batch; expect idempotent failure response (status remains `failed` or `delivered` without duplicate lines).
  - `ExportEvents#L1`
    1. Subscribe test consumer to event bus stream.
    2. Execute export via API; inspect emitted event includes `ACCOUNTING_EXPORT_COMPLETED`, correct tenant, adapter type, and delivered line IDs.
    3. Force adapter error (throw in stub) to confirm `ACCOUNTING_EXPORT_FAILED` fires with error payload and batch marked failed.
  - `CLITrigger#L1`
    1. Run `scripts/trigger-accounting-export.ts` and confirm it creates a placeholder batch/line.
    2. Validate seeded data appears through the API and can be managed alongside UI-driven batches.
  - `WorkflowActions#L0`
    1. Open Automation Hub template builder; search action registry for `accounting_export.create_batch` and `accounting_export.execute_batch`.
    2. Add each action to a draft workflow and verify parameter prompts align with adapter, dates, and batch id expectations.
