Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
065b6fa
feat(OUT-3604): apply migrations one-per-transaction via db:migrate
SandipBajracharya Jul 22, 2026
5fc4434
feat(OUT-3604): add payout/settled enums, bank-deposit columns, idemp…
SandipBajracharya Jul 22, 2026
b4d6860
feat(OUT-3604): type the QBO createDeposit response
SandipBajracharya Jul 22, 2026
8439c5f
feat(OUT-3604): resolve the bank account ref for payout deposits
SandipBajracharya Jul 22, 2026
b100966
feat(OUT-3604): create one batched bank deposit per Stripe payout
SandipBajracharya Jul 22, 2026
28a74e3
fix(OUT-3604): serialize per-file migrations and use them in test setup
SandipBajracharya Jul 23, 2026
93bdd8b
refactor(OUT-3604): address PR #266 review nits
SandipBajracharya Jul 23, 2026
20acfaa
Merge pull request #266 from assemblycom/OUT-3604-new-re
priosshrsth Jul 23, 2026
9d4724a
feat(OUT-4003): add bankDepositFeeFlag + bankAccountRef to settings s…
SandipBajracharya Jul 23, 2026
47184f7
feat(OUT-4003): GET /setting/bank-account lists QBO bank accounts
SandipBajracharya Jul 23, 2026
b4a60fd
feat(OUT-4003): return + persist bankAccountRef in invoice settings
SandipBajracharya Jul 23, 2026
9d1e258
feat(OUT-4003): bank-deposit toggle + deposit bank account dropdown
SandipBajracharya Jul 23, 2026
d2c23b2
fix(OUT-4003): address PR #267 review
SandipBajracharya Jul 23, 2026
6268fab
fix(OUT-4003): resolve exhaustive-deps warnings in useSettings
SandipBajracharya Jul 23, 2026
6dc3b17
fix(OUT-4003): raise bank account query cap to 1000
SandipBajracharya Jul 23, 2026
f6f3cfe
fix(OUT-4003): fold getSettings bankAccountRef into a single expression
SandipBajracharya Jul 23, 2026
cf7f0c8
fix(OUT-3604): select full account columns in getUndepositedFundsAcco…
SandipBajracharya Jul 24, 2026
fbd4c7f
fix(OUT-3604): route paid-on-create payments through Undeposited Funds
SandipBajracharya Jul 24, 2026
1b45d7f
test(OUT-4006): add shared payout test infra
SandipBajracharya Jul 23, 2026
542a108
test(OUT-4006): cover payout reconciliation flow
SandipBajracharya Jul 23, 2026
e57966b
test(OUT-4006): cover batched-mode no-op and stale payout reaping
SandipBajracharya Jul 23, 2026
9df35a8
test(OUT-4006): assert bankAccountRef carried on every token path
SandipBajracharya Jul 23, 2026
2dbbe54
test(OUT-4003): cover bank-account listing, invoice settings, and req…
SandipBajracharya Jul 23, 2026
553b8e4
test(OUT-4006): strengthen payout/payment webhook assertions
SandipBajracharya Jul 27, 2026
c3f9cea
fix(OUT-4010): freeze batched-deposit intent per invoice
SandipBajracharya Jul 27, 2026
9a10409
test(OUT-4010): cover frozen batched-deposit intent
SandipBajracharya Jul 27, 2026
8ea1f95
fix(OUT-4010): address review — remove debug endpoint, reconcile payo…
SandipBajracharya Jul 27, 2026
04b36f1
fix(OUT-4010): clarify status-blind duplicate short-circuit (greptile…
SandipBajracharya Jul 27, 2026
e66aafa
style(OUT-4010): trim verbose comments to one or two lines
SandipBajracharya Jul 27, 2026
d899747
refactor(OUT-4010): replace non-null assertion operator with optional…
SandipBajracharya Jul 28, 2026
9f7274c
feat(OUT-4011): add mixed-payout notification code, context, and copy
SandipBajracharya Jul 28, 2026
58f5ca1
feat(OUT-4011): detect mixed-intent payouts and dispatch the notifica…
SandipBajracharya Jul 28, 2026
6f116d2
test(OUT-4011): cover mixed-payout invoice numbers and stale-object void
SandipBajracharya Jul 28, 2026
81a43de
fix(OUT-4011): address review — passive retry copy, nullable context …
SandipBajracharya Jul 29, 2026
413dd1a
feat(OUT-4012): warn before changing the bank-deposit flag
SandipBajracharya Jul 29, 2026
523392d
fix(OUT-4012): add focus management to the confirm modal
SandipBajracharya Jul 29, 2026
bbd57c4
refactor(OUT-4012): split confirm-modal effects per review
SandipBajracharya Jul 31, 2026
ce487c0
feat(OUT-4005): add qb_payout_sync table schema and migration
SandipBajracharya Jul 31, 2026
ed4c2cf
feat(OUT-4005): add payout reconciliation service and deposit lookup
SandipBajracharya Jul 31, 2026
028757f
refactor(OUT-4005): use constructor to instantiate service
SandipBajracharya Jul 31, 2026
736a8fa
feat(OUT-4005): reconcile payouts into batched deposits via webhook a…
SandipBajracharya Jul 31, 2026
f1bf9d1
refactor(OUT-4005): insert payout sync and log in parallel in seedFai…
SandipBajracharya Aug 3, 2026
3e98ab3
chore(OUT-4005): add test typecheck script + CI job, fix stale test t…
SandipBajracharya Jul 31, 2026
102df07
feat(OUT-3617): add bank-deposit AB gate primitive and config
SandipBajracharya Aug 3, 2026
0ff0907
feat(OUT-3617): gate bank-deposit backend paths behind the AB allowlist
SandipBajracharya Aug 3, 2026
e02a235
feat(OUT-3617): hide bank-deposit settings UI when the AB gate is off
SandipBajracharya Aug 3, 2026
e3c4e4f
test(OUT-3617): cover the bank-deposit AB gate end to end
SandipBajracharya Aug 3, 2026
937e2e2
feat(OUT-4030): add ground-truth lookup for invoices with a recorded fee
SandipBajracharya Aug 7, 2026
8765fad
feat(OUT-4030): warn IUs which mixed-payout fees are already recorded
SandipBajracharya Aug 7, 2026
d73f5ab
refactor(OUT-4030): extract mixed-payout invoice resolution into a he…
SandipBajracharya Aug 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ VERCEL_URL=localhost:3000
VERCEL_ENV=development
CRON_SECRET=vercel cron secret

# Comma-separated portalIds the bank deposit feature is limited to. Empty/unset = all portals.
AB_FEATURE_TESTING_PORTALS=

SENTRY_ORG=
SENTRY_PROJECT=
NEXT_PUBLIC_SENTRY_DSN=
Expand Down
21 changes: 21 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,27 @@ name: CI
on: pull_request

jobs:
typecheck:
name: Typecheck tests
runs-on: ubuntu-latest

steps:
- name: Check out Git repository
uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 22.14.0
cache: yarn
cache-dependency-path: './yarn.lock'

- name: Install dependencies
run: yarn install

- name: Typecheck test files
run: yarn typecheck:test

run-tests:
name: Run tests
runs-on: ubuntu-latest
Expand Down
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,4 @@ next-env.d.ts
.trigger

# local decision notes (not published)
/docs
/supabase/snippets
157 changes: 157 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## What this app is

A multi-tenant Next.js (App Router) service that synchronizes Copilot / Assembly workspaces with QuickBooks Online (QBO). It runs on Vercel, persists state in Postgres (Supabase in prod, Drizzle ORM throughout), and reacts to Copilot webhooks (`invoice.created/updated/paid/voided/deleted`, `product.updated`, `price.created`, `payment.succeeded`) by mirroring those entities into the corresponding QBO realm.

A "portal" is one Copilot/Assembly workspace bonded to one QuickBooks realm. Almost every table is keyed by `portalId`; almost every service derives `this.user.workspaceId` from the request token and scopes everything to that portal.

## Common commands

Package manager is **Yarn 4 (Berry)**, Node **22.14.0** (`.nvmrc`).

```bash
yarn install # install
yarn dev # Next dev (Turbopack)
yarn build # next build (CI uses build.sh which also runs drizzle-kit migrate)
yarn lint:check # ESLint over src/ and test/
yarn prettier:check # Prettier check
yarn lint:fix # ESLint --fix
yarn prettier:fix # Prettier write

# Tests (Vitest, two projects defined in vitest.config.ts)
yarn test # both: unit then integration (groupOrder enforces this)
yarn test:watch # watch
yarn test:coverage # v8 coverage
npx vitest run --project unit # only unit
npx vitest run --project integration # only integration
npx vitest run test/integration/quickbooks/priceCreated/happyPath.test.ts # single file
npx vitest run -t 'happy path' # by test-name pattern

# Trigger.dev (background task runtime)
yarn trigger:dev # local dev worker
yarn trigger:deploy # deploy tasks

# DB migrations (Drizzle Kit, schema lives at src/db/schema/)
npx drizzle-kit generate # create new migration from schema changes
npx drizzle-kit migrate # apply pending migrations to DATABASE_URL

# One-off operational scripts (tsx, see src/cmd/*)
yarn cmd:rename-qb-accounts
yarn cmd:backfill-product-info
yarn cmd:sync-missed-invoices
yarn cmd:sync-missed-products
```

Husky `pre-commit` runs `lint-staged` (eslint --fix + prettier --write on `src/**/*.{ts,tsx}`). CI (`.github/workflows/test.yml`) runs `yarn test` on PRs; `.github/workflows/lint.yml` runs lint+prettier on every push. CI assumes the testcontainers Postgres image is available (Docker is preinstalled on `ubuntu-latest`).

## Architecture

### Request → handler shape

Every API route follows the same skeleton:

```
src/app/api/<area>/<feature>/
route.ts # exports { POST/GET } = withErrorHandler(controllerFn); sets maxDuration
<feature>.controller.ts # auth + Sentry scope + parse + delegate to service
<feature>.service.ts # extends BaseService; orchestrates DB + external APIs
```

Controllers call `authenticate(req)` (`src/app/api/core/utils/authenticate.ts`), which reads `?token=…`, asks Copilot to decrypt it, and returns a `User` (`src/app/api/core/models/User.model.ts`). `User` carries `workspaceId` (= portalId), role, and the lazily-attached `qbConnection` (service-item / client-fee refs).

`withErrorHandler` (`src/app/api/core/utils/withErrorHandler.ts`) is the **only** error path. It maps `ZodError` / `APIError` / `CopilotApiError` / `RetryableError` / Intuit OAuth + Axios errors to HTTP responses and forwards categorized exceptions to Sentry. Don't add try/catch in route handlers — throw and let this wrapper format.

### BaseService and the DB singleton

Services extend `BaseService` (`src/app/api/core/services/base.service.ts`), which holds:

- `this.db` — the **module-level Drizzle singleton** from `src/db/index.ts` (`DBClient.getInstance()`); `casing: 'snake_case'`.
- `this.user` — the authenticated `User` for the request.
- `setTransaction(tx)` / `unsetTransaction()` — swap `this.db` for a transaction handle inside a `db.transaction(...)` callback, then restore.

**Pitfall (known, see `memory/project_unsetTransaction_bug.md`):** `unsetTransaction()` is sometimes called inside the transaction callback or skipped on error paths — across `BaseService` subclasses this leaves the singleton pointed at a closed tx. When introducing or modifying transactional code, audit that `setTransaction` / `unsetTransaction` are paired in `try/finally` and that nested service calls share the tx handle.

The DB singleton is also why test helpers (`test/helpers/seed.ts`, `test/helpers/testDb.ts`) import `@/db` directly — see `docs/why-test-helpers-use-the-app-db-singleton.md`. Don't introduce a separate test-only Drizzle client; tests must read what the app writes.

### Webhook flow (the central path)

`POST /api/quickbooks/webhook` → `WebhookService.handleWebhookEvent` (`src/app/api/quickbooks/webhook/webhook.service.ts`) is a switch on `payload.eventType` that dispatches to `InvoiceService` / `ProductService` / `PaymentService`. A few things to know before changing it:

1. **Idempotency is enforced via `qb_sync_logs` claim rows.** `SyncLogService.claimWebhookEvent({ copilotId, entityType, eventType, … })` returns `{ claimed: false }` if a row already exists; handlers exit early. Any new webhook handler must call `claimWebhookEvent` before doing real work or duplicate processing will leak into QBO.
2. **`qb_sync_logs.quickbooks_id` is polymorphic.** Its meaning depends on `(entityType, eventType)` — for `INVOICE/PAID` it stores the QBO **Payment** ID, not the Invoice ID. See `memory/project_qb_sync_logs_semantics.md`.
3. **Pre-claim sleeps for ordering.** `INVOICE_UPDATED` / `INVOICE_VOIDED` / `PAYMENT_SUCCEEDED` sleep before `claimWebhookEvent` so a companion event (e.g., `INVOICE_CREATED`) can claim first. The `delayMs` lives in the handler, not the caller — keep it that way; moving the sleep after the claim re-opens the race.
4. **Setting flags gate handlers.** `PRICE_CREATED` / `PRODUCT_UPDATED` no-op when `createNewProductFlag` is false; `PAYMENT_SUCCEEDED` no-ops when `absorbedFeeFlag` is false or there's no platform-paid fee. Read `qb_settings` via `SettingService` rather than passing flags around.
5. **There's a known TOCTOU race on `claimWebhookEvent`** — accepted, parked, will be addressed with an advisory lock + dedupe job, not a rewrite. See `memory/project_qb_sync_logs_toctou_parked.md`.

### Token refresh

QBO access tokens expire in ~1h, refresh tokens in ~100 days. `src/utils/intuitAPI.ts` sends authenticated requests; `src/utils/tokenRefresh.ts` (`getValidQbTokens`) refreshes when stale. The `vercel.json` cron `/api/quickbooks/refresh-tokens` runs daily at 06:00 UTC to keep refresh tokens warm. There's a known silent-401 bug — expired tokens cause `null` returns from `getFetchWithHeader/postFetchWithHeaders`; the planned fix is auto-refresh inside those helpers (design at `docs/intuit-api-token-refresh.md`, summary in `memory/project_intuit_api_token_refresh.md`).

### Background work

- **Vercel crons** (`vercel.json`):
- `/api/quickbooks/cron` every 12h — kicks off `processResyncForFailedRecords` (Trigger.dev task) to retry failed sync logs. Auth via `Bearer ${CRON_SECRET}`.
- `/api/quickbooks/refresh-tokens` daily 06:00 UTC.
- **Trigger.dev** tasks live in `src/trigger/` (config at `trigger.config.ts`, runtime: node, default 3 retries, `maxDuration: 3600s`). Sentry source maps are uploaded only when `VERCEL_ENV === 'production'`.

## Multi-tenancy invariant

Every `WHERE` clause that touches a portal-scoped table needs `portalId = this.user.workspaceId`. Forgetting this leaks one tenant's data into another. The unique indexes on `qb_sync_logs` and `qb_invoice_sync` (see migrations 20260427100328 / 20260427055352) enforce some of this at the DB level, but most of it is service-layer discipline.

## Database & schema

- Drizzle schemas in `src/db/schema/*` registered in `src/db/schema/index.ts`. Relations in `relation.ts`.
- Migrations in `src/db/migrations/` (prefix `supabase`, generated by drizzle-kit). The `init.sql` (20250701) defines all enums; subsequent files alter.
- Custom column helpers in `src/db/helper/column.helper.ts` (`timestamps`) and enum bridge in `drizzle.helper.ts` (`enumToPgEnum`).
- `qb_payments` table exists but is currently unused (reserved for future) — no rows in prod. See `memory/project_qb_payments_unused.md`.
- Type-safe Zod schemas come from `drizzle-zod` (`createInsertSchema` / `createSelectSchema`); reuse those rather than hand-rolling Zod for DB rows.

## Testing

- Two Vitest **projects** in `vitest.config.ts` — `unit` (mock-heavy, isolated) and `integration` (real Postgres via testcontainers). Run order is enforced via `sequence.groupOrder` (unit=0, integration=1).
- Integration project is configured **`pool: 'forks'` + `fileParallelism: false` + `isolate: false`** so all integration tests share one Postgres container _and_ one app DB connection. Don't change these without reading `docs/vitest-gotchas.md` and `docs/why-test-helpers-use-the-app-db-singleton.md`.
- `.env.test` is loaded by `test/integration/globalSetup.ts` with `override: true` so a developer's local `.env` can't leak into tests. `DATABASE_URL` is intentionally **not** in `.env.test` — globalSetup sets it from the container's URI before any worker imports `src/config`.
- Module mocks for integration are in `test/integration/setup.ts` — `@/utils/copilotAPI`, `@/utils/intuitAPI`, and `@sentry/nextjs` must be mocked with **explicit factories** (and Intuit/Copilot mock implementations must use `function`, not `=>`, because the code does `new IntuitAPI(...)`). See `docs/vitest-gotchas.md` items 1–3.
- Test helpers in `test/helpers/`: `seed.ts` (`seedHealthyPortal`, `TEST_PORTAL_ID`, etc.), `webhook.ts` (`postWebhook` via `next-test-api-route-handler`), `testDb.ts` (`truncateAllTestTables`).
- Test-data philosophy in `docs/test-data-dos-and-donts.md`: static fixtures for the thing under test, factories with explicit overrides for single-dimension variants, **no faker** in fixtures or assertions.

## Path aliases

```
@/* → src/*
@test/* → test/*
```

Configured in `tsconfig.json` and propagated to Vitest via `vite-tsconfig-paths` (per-project in `vitest.config.ts`).

## Style notes

- Prettier: single quotes, no semis, trailing comma all (`.prettierrc`).
- ESLint: `next/core-web-vitals` + TypeScript; `prefer-const` and `no-var` are errors; unused-var underscore prefix is exempt; `@typescript-eslint/no-explicit-any` is disabled (the codebase uses `any` deliberately at framework boundaries).
- Tailwind v4 + `copilot-design-system`. UI surface is small (settings dashboard + OAuth callback) — most work happens in the API/service layer.
- The `docs/` folder is **gitignored** (per `.gitignore`) and used for local decision notes — design docs, post-mortems, comparison tables. Save non-trivial tradeoff discussions there rather than in code comments or commit messages.

## Things to read before non-trivial changes

- `docs/testcontainers-vs-local-supabase.md` — why integration tests use testcontainers, not the local Supabase stack.
- `docs/why-test-helpers-use-the-app-db-singleton.md` — why test helpers import `@/db` and what would break if you opened a separate client.
- `docs/vitest-gotchas.md` — the five real traps already hit in this project.
- `docs/test-data-dos-and-donts.md` — the test-data rules.
- `docs/intuit-api-token-refresh.md` — design for the silent-401 fix.

## What this repo doesn't have

- No design system / shared component library — UI is a thin dashboard, mostly settings forms.
- No GraphQL, no tRPC — plain Next.js Route Handlers + service classes.
- No DI container — `BaseService` reads `db` from a module singleton; tests work _with_ that constraint, not around it.
- No existing CLAUDE.md until this one.

## Engineering notes

- After a successful implementation, the changes will be reviewed by the team lead and greptileAI in github.
- Do not use let unless absolutely necessary. Use const instead.
- Always keep the comments short, on point and easy to understand with easy wordings. This is must.
- Follow DRY, KISS, SOLID, YAGNI principles.
60 changes: 60 additions & 0 deletions docs/stripe reconciliation/batched-deposit-fee-edge-case.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Batched-deposit fee edge case (OUT-4009)

## The setup

Two settings control how Stripe fees land in QuickBooks:

- **`absorbedFeeFlag`** — book the Stripe fee as an expense.
- **`bankDepositFeeFlag`** (the "batched-deposit" flag) — decides **who** books that fee:
- **OFF** → fee booked immediately at `payment.succeeded` as an individual QBO **Purchase**.
- **ON** → fee is deferred; the payment parks in **Undeposited Funds (UF)**, and one QBO **Bank Deposit** per Stripe payout books the fee later.

> **UF (Undeposited Funds)** is a QuickBooks holding account — a "waiting room". Payments sit there until a Bank Deposit sweeps them into the real bank account. That deposit is what matches the bank feed 1:1.

## The bug

The flag is read **live** at two different moments — once at `payment.succeeded`, once at `payout.reconciliation`. If the user toggles it in between, the two disagree:

- **OFF → ON:** fee booked at payment time **and** again in the deposit → **fee booked twice.**
- **ON → OFF:** payment parked in UF, but the payout handler returns early → **fee never booked + payment stranded in UF.**

```mermaid
sequenceDiagram
participant U as User (settings)
participant P as payment.succeeded
participant PO as payout.reconciliation

Note over P,PO: OFF→ON ⇒ double fee
P->>P: flag OFF → book individual fee Purchase
U->>U: toggle ON
PO->>PO: flag ON → deposit ALSO books the fee
Note over PO: ❌ same fee booked twice

Note over P,PO: ON→OFF ⇒ missed fee
P->>P: flag ON → defer fee, payment → UF
U->>U: toggle OFF
PO->>PO: flag OFF → early return, no deposit
Note over PO: ❌ fee never booked + payment stranded in UF
```

## The fix

**Freeze the decision per invoice.** When the payment routing is decided, store `isBatchedDeposit` on the invoice-sync row. Both handlers read that frozen value instead of the live flag.

At payout time the decision is **all-or-nothing** by frozen intent:

```mermaid
flowchart TD
A["payout.reconciliation"] --> B["Read frozen isBatchedDeposit<br/>for every line item"]
B --> C{All batched?}
C -->|Yes| D["✅ Create one deposit<br/>fees folded in → 1:1 bank match"]
C -->|No| E{All non-batched?}
E -->|Yes| F["✅ No deposit<br/>fees already booked at payment"]
E -->|"No — mixed"| G["⚠️ Skip deposit<br/>log FAILED + notify → manual reconciliation"]
```

## Why "mixed" can't be auto-handled

A single Stripe payout can straddle a toggle, mixing batched and non-batched invoices. There's no way to render that as one balanced deposit without either double-booking a fee, breaking the 1:1 bank match, or destructively deleting already-booked Purchases (and there is **no `deleteDeposit`** to undo mistakes). So a mixed payout is quarantined for manual reconciliation instead of guessed.

A settings dialog warns users that toggling mid-cycle can leave one payout needing manual reconciliation — but that's UX only; correctness comes from the frozen intent above.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"lint-staged": "npx lint-staged",
"prepare": "husky",
"supabase:dev": "supabase start --ignore-health-check",
"db:migrate": "tsx src/db/migrate.ts",
"cmd:rename-qb-accounts": "tsx src/cmd/renameQbAccount/index.ts",
"patch-assembly-node-sdk": "cp ./lib-patches/assembly-js-node-sdk.js ./node_modules/@assembly-js/node-sdk/dist/api/init.js",
"patch-copilot-node-sdk": "cp ./lib-patches/copilot-node-sdk.js ./node_modules/copilot-node-sdk/dist/api/init.js",
Expand All @@ -25,7 +26,8 @@
"cmd:sync-missed-products": "tsx src/cmd/syncMissedProducts/index.ts",
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage"
"test:coverage": "vitest run --coverage",
"typecheck:test": "tsc --noEmit -p test/tsconfig.json"
},
"dependencies": {
"@sentry/nextjs": "^9.13.0",
Expand Down
4 changes: 2 additions & 2 deletions scripts/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ else
echo "[1/3] Skipping copilot-node-sdk patch (production)"
fi

echo "[2/3] Running drizzle-kit migrate"
yarn drizzle-kit migrate
echo "[2/3] Running db:migrate"
yarn db:migrate

echo "[3/3] Running next build"
next build
Expand Down
2 changes: 2 additions & 0 deletions src/app/api/core/types/log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export enum EntityType {
INVOICE = 'invoice',
PRODUCT = 'product',
PAYMENT = 'payment',
PAYOUT = 'payout',
}

export enum LogStatus {
Expand All @@ -20,6 +21,7 @@ export enum EventType {
SUCCEEDED = 'succeeded',
MAPPED = 'mapped',
UNMAPPED = 'unmapped',
SETTLED = 'settled',
}

/**
Expand Down
19 changes: 14 additions & 5 deletions src/app/api/core/types/notification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export enum NotificationActions {
QB_TXN_LINK_FAILED = 'qb_txn_link_failed',
QB_ITEM_INCOME_ACCOUNT_MISSING = 'qb_item_income_account_missing',
QB_INVALID_ACCOUNT_TYPE = 'qb_invalid_account_type',
QB_PAYOUT_MIXED_INTENT = 'qb_payout_mixed_intent',
}

/**
Expand All @@ -24,9 +25,17 @@ export interface NotificationContext {
entityType?: string
eventType?: string
entityKey?: string
invoiceNumber?: string
customerName?: string
productName?: string
qbItemName?: string
errorMessage?: string
// Nullable string fields mirror their nullable qb_sync_logs columns, so
// callers can pass log values directly. Consumers treat null/undefined alike.
invoiceNumber?: string | null
// Comma-joined invoice numbers for a multi-invoice failure (mixed payout),
// where the single invoiceNumber above can't hold them all.
invoiceNumbers?: string | null
// Subset of invoiceNumbers whose absorbed fee is already recorded in QBO, so a
// mixed-payout body can tell IUs which fees not to record a second time.
invoiceNumbersWithFee?: string | null
customerName?: string | null
productName?: string | null
qbItemName?: string | null
errorMessage?: string | null
}
1 change: 1 addition & 0 deletions src/app/api/core/types/webhook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ export enum WebhookEvents {
INVOICE_VOIDED = 'invoice.voided',
INVOICE_UPDATED = 'invoice.updated',
PAYMENT_SUCCEEDED = 'payment.succeeded',
PAYOUT_RECONCILIATION_COMPLETED = 'payout.reconciliation_completed',
}
Loading
Loading