Skip to content

OUT-4003: bank deposit settings — dedicated toggle + bank account dropdown#267

Merged
SandipBajracharya merged 8 commits into
feature/payout-reconciliationfrom
OUT-4003
Jul 23, 2026
Merged

OUT-4003: bank deposit settings — dedicated toggle + bank account dropdown#267
SandipBajracharya merged 8 commits into
feature/payout-reconciliationfrom
OUT-4003

Conversation

@SandipBajracharya

@SandipBajracharya SandipBajracharya commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Stacked on #266 (OUT-3604). Base: OUT-3604-new-re.

What

Adds the settings-dashboard UI + API so a user can enable batched Stripe→QBO bank deposits and choose the deposit bank account. Without this, bankAccountRef is never set and the payout handler aborts with "bank account not configured."

Commits

  • schemaSettingRequestSchema + InvoiceSettingType gain bankDepositFeeFlag + bankAccountRef; invoice type requires the flag, and requires a non-empty bankAccountRef when the flag is on (surfaces as 422).
  • endpointGET /api/quickbooks/setting/bank-account (route → controller → BankAccountService) lists active QBO Bank accounts; selects the full account column set so it parses against real QBO, maxresults 100.
  • persistencesetting.controller GET returns bankAccountRef; POST persists it transactionally with the invoice flags (empty → null; setTransaction/unsetTransaction paired for both services).
  • UI — extract shared AccountSelect; independent bankDepositFeeFlag toggle + "Deposit bank account" dropdown in InvoiceDetail; inline hint/error; canSave disables the Invoice Update button until an account is chosen.

Verification

  • tsc 0 errors, lint clean, next build OK, full suite 92 files / 342 tests pass.
  • Manual UI verification done.
  • Full-stack reviewed; findings fixed (surface bank-account fetch errors, extract the service layer, maxresults cap, empty→null coercion).

https://www.loom.com/share/243d1bee5d7b446a8c29e3619c4cc692

Notes

  • Test files (schema unit test + setting integration tests) and their test-infra are managed separately and are not in these commits — CI on this branch won't exercise the new tests until that lands.
  • Deferred follow-ups: disambiguate the two "bank account" labels ("Deposit bank account" vs the asset "Bank account" in Account Mapping); collapse the getSettings double DB round-trip.

🤖 Generated with Claude Code

@linear-code

linear-code Bot commented Jul 23, 2026

Copy link
Copy Markdown

OUT-4003

@vercel

vercel Bot commented Jul 23, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
quickbooks-sync Building Building Jul 23, 2026 8:52am
quickbooks-sync (dev) Ready Ready Preview, Comment Jul 23, 2026 8:52am

Request Review

@greptile-apps

greptile-apps Bot commented Jul 23, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds a bank deposit settings feature: a dedicated toggle (bankDepositFeeFlag) and bank account dropdown so users can choose where Stripe payouts land in QBO. It introduces a new GET /api/quickbooks/setting/bank-account endpoint, extends the SettingRequestSchema + InvoiceSettingType, writes both the flag and the account ref transactionally, and extracts the shared AccountSelect component for reuse.

  • Backend: New BankAccountService + controller queries QBO for active Bank accounts; setting.controller GET returns bankAccountRef from QBPortalConnection; POST wraps both the QBSetting flag write and the QBPortalConnection.bankAccountRef write in a single DB transaction.
  • Schema: superRefine enforces bankDepositFeeFlag as a required boolean on invoice saves and rejects missing bankAccountRef when the flag is on, surfacing a 422 before any DB write.
  • UI: AccountSelect is extracted into its own file (loading vs. empty states distinguished), wired into InvoiceDetail behind the new toggle, and canSave disables the Invoice Update button until an account is chosen when the flag is enabled.

Confidence Score: 5/5

Safe to merge — the feature is self-contained, the DB writes are transactional, and both client-side and server-side guards prevent saving an incomplete bank-deposit configuration.

The schema validation, transactional persistence, loading/error state handling, and canSave guard all work together correctly. No logic gaps or data-loss paths were found across the full stack.

No files require special attention. The two deferred items noted in the PR description (double DB round-trip in getSettings, label disambiguation) are non-blocking and clearly documented.

Important Files Changed

Filename Overview
src/app/api/quickbooks/setting/setting.controller.ts Extends GET to return bankAccountRef from QBPortalConnection and wraps both DB writes in a single transaction; logic is sound and the writeBankAccountRef guard is correct.
src/app/api/quickbooks/setting/bank-account/bank-account.service.ts Simple service that queries QBO for active Bank accounts using the shared QB_ACCOUNT_COLUMNS set and parses through the existing response schema; maxresults 100 cap is intentional.
src/type/common.ts Schema additions are correct — bankDepositFeeFlag required as boolean on invoice type, bankAccountRef required (non-empty) only when the flag is on. InvoiceSettingType extended appropriately.
src/hook/useSettings.ts Adds bank-account SWR fetch (skipped when disconnected), generic changeSettings, canSave guard, and proper hydration of bankAccountRef from the API; all edge cases handled.
src/components/dashboard/settings/sections/account/AccountSelect.tsx Extracted shared component; correctly distinguishes loading (undefined) from empty (length 0) states, with appropriate ARIA attributes and click-outside handling.
src/components/dashboard/settings/sections/invoice/InvoiceDetail.tsx Bank deposit toggle + account dropdown wired correctly; error/loading states displayed; hint only shown once options have loaded (avoids false positives during fetch).

Sequence Diagram

sequenceDiagram
    participant UI as InvoiceDetail (UI)
    participant Hook as useInvoiceDetailSettings
    participant SettingAPI as GET /api/quickbooks/setting
    participant BankAPI as GET /api/quickbooks/setting/bank-account
    participant QBO as QuickBooks Online
    participant DB as Database

    UI->>Hook: mount
    Hook->>SettingAPI: "fetch settings (type=invoice)"
    SettingAPI->>DB: getOneByPortalId (QBSetting)
    SettingAPI->>DB: getPortalConnection (QBPortalConnection)
    DB-->>SettingAPI: "{setting, bankAccountRef}"
    SettingAPI-->>Hook: "{setting, bankAccountRef}"
    Hook->>Hook: hydrate settingState (bankAccountRef ?? '')

    Hook->>BankAPI: fetch bank accounts (if connected)
    BankAPI->>QBO: "SELECT ... FROM Account WHERE AccountType='Bank' AND Active=true maxresults 100"
    QBO-->>BankAPI: accounts[]
    BankAPI-->>Hook: "{accounts}"
    Hook->>UI: bankAccountOptions, canSave

    UI->>Hook: changeSettings('bankDepositFeeFlag', true)
    Hook->>Hook: "canSave = false (no account yet)"
    UI->>Hook: changeSettings('bankAccountRef', 'id-123')
    Hook->>Hook: "canSave = true"

    UI->>Hook: submitInvoiceSettings()
    Hook->>SettingAPI: "POST /api/quickbooks/setting?type=invoice"
    Note over SettingAPI,DB: DB Transaction
    SettingAPI->>DB: updateQBSettings (QBSetting)
    SettingAPI->>DB: updateQBPortalConnection (bankAccountRef)
    DB-->>SettingAPI: result
    SettingAPI-->>Hook: "{setting}"
    Hook->>Hook: mutate (refetch settings)
Loading

Reviews (2): Last reviewed commit: "fix(OUT-4003): address PR #267 review" | Re-trigger Greptile

Comment thread src/hook/useSettings.ts Outdated
SandipBajracharya and others added 4 commits July 23, 2026 10:59
…chema

SettingRequestSchema gains bankDepositFeeFlag and bankAccountRef; for
invoice type the flag is required and a non-empty bankAccountRef is
required when the flag is enabled. InvoiceSettingType carries both fields.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New endpoint backed by BankAccountService (route → controller → service).
Lists active QBO Bank-type accounts to populate the deposit-account
dropdown; selects the full account column set so the response parses
against real QBO output, capped at maxresults 100.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
getSettings returns bankAccountRef and bankDepositFeeFlag for invoice
type; updateSettings persists bankAccountRef transactionally with the
settings write (empty coerced to null), with setTransaction/unsetTransaction
paired for both services.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extract the shared AccountSelect dropdown; add an independent
bankDepositFeeFlag toggle and a "Deposit bank account" dropdown in
InvoiceDetail (fed by the bank-account endpoint), with an inline hint /
error and a canSave gate that disables the Update button until an account
is chosen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- AccountSelect distinguishes the loading state (options undefined →
  "Loading accounts…") from the genuinely-empty state ("No matching
  accounts"), and InvoiceDetail only shows the "select an account" hint
  once options have loaded — so enabling the toggle mid-fetch no longer
  shows a misleading empty message.
- changeSettings is now generic over the field key
  (<K extends keyof InvoiceSettingType>(flag: K, value: InvoiceSettingType[K])),
  so the value type is tied to the field and e.g. a string can't be passed
  for a boolean flag.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@SandipBajracharya

Copy link
Copy Markdown
Collaborator Author

@greptileai review whole thing again

@SandipBajracharya SandipBajracharya changed the title feat(OUT-4003): bank deposit settings — dedicated toggle + bank account dropdown OUT-4003: bank deposit settings — dedicated toggle + bank account dropdown Jul 23, 2026
@SandipBajracharya
SandipBajracharya changed the base branch from OUT-3604-new-re to feature/payout-reconciliation July 23, 2026 07:31
Comment thread src/app/api/quickbooks/setting/bank-account/bank-account.service.ts
Comment thread src/app/api/quickbooks/setting/setting.controller.ts Outdated
Comment thread src/type/common.ts
Comment thread src/hook/useSettings.ts Outdated
SandipBajracharya and others added 2 commits July 23, 2026 14:28
Add the missing effect dependencies flagged by react-hooks/exhaustive-deps,
hoist emptyMappedItem to a module singleton, and memoize the formatted
QuickBooks item list so useMapItem's effect keeps a stable reference.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
100 could miss active bank accounts; 1000 is QBO's max single-page size and
returns them all in one query.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Collapse the let + if into a ternary that reads bankAccountRef directly off
the portal connection for INVOICE settings, and drop the now-unused NotNull
import.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@priosshrsth priosshrsth left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm 🏆

@SandipBajracharya
SandipBajracharya merged commit f6f3cfe into feature/payout-reconciliation Jul 23, 2026
4 checks passed
SandipBajracharya added a commit that referenced this pull request Jul 23, 2026
- AccountSelect distinguishes the loading state (options undefined →
  "Loading accounts…") from the genuinely-empty state ("No matching
  accounts"), and InvoiceDetail only shows the "select an account" hint
  once options have loaded — so enabling the toggle mid-fetch no longer
  shows a misleading empty message.
- changeSettings is now generic over the field key
  (<K extends keyof InvoiceSettingType>(flag: K, value: InvoiceSettingType[K])),
  so the value type is tied to the field and e.g. a string can't be passed
  for a boolean flag.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants