# 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.