Conversation
There was a problem hiding this comment.
Sorry @Connorbelez, you have reached your weekly rate limit of 500000 diff characters.
Please try again later or upgrade to continue using Sourcery
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (6)
📝 WalkthroughWalkthroughIntroduces a daily lender payout batch system and admin-triggered immediate payout functionality. Adds schema fields for payout configuration to lenders table and payout tracking to dispersalEntries. Implements Convex queries for eligible dispersal entries and active lenders, mutations for marking entries as disbursed and updating lender payout dates, an admin action for immediate payouts with optional mortgage filtering, and a daily batch processor wired into the cron scheduler at 08:00 UTC. Changes
Sequence Diagram(s)sequenceDiagram
participant Admin as Admin/Cron
participant Query as Queries
participant Mutation as Mutations
participant CashLedger as Cash Ledger
participant DB as Database
Admin->>Query: getActiveLenders() or getLenderById()
Query->>DB: Fetch lender(s) with payout config
DB-->>Query: Lender document(s)
Query-->>Admin: Active lender(s)
Admin->>Query: getEligibleDispersalEntries(lenderId, today)
Query->>DB: Query pending dispersalEntries by status
DB-->>Query: Pending entries
Query->>Query: Filter by payoutEligibleAfter ≤ today
Query-->>Admin: Eligible entries
Admin->>Admin: Group entries by mortgageId
Admin->>Admin: Validate sum ≥ minimumPayoutCents
Admin->>Mutation: markEntriesDisbursed(entryIds, payoutDate)
Mutation->>DB: Verify status === "pending"
Mutation->>DB: Patch status to "disbursed"
DB-->>Mutation: Updated entries
Mutation-->>Admin: Success
Admin->>CashLedger: postLenderPayout(idempotencyKey, ...)
CashLedger->>DB: Create ledger entry
DB-->>CashLedger: Ledger created
alt Ledger Post Fails
Admin->>Mutation: revertClaimedEntries(entryIds)
Mutation->>DB: Revert status to "pending"
DB-->>Mutation: Reverted
else Ledger Post Succeeds
Admin->>Mutation: updateLenderPayoutDate(lenderId, today)
Mutation->>DB: Update lastPayoutDate
DB-->>Mutation: Updated
end
Admin-->>Admin: Return payout summary or error
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
This stack of pull requests is managed by Graphite. Learn more about stacking. |
There was a problem hiding this comment.
Connorbelez has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.
There was a problem hiding this comment.
Pull request overview
Implements lender payout scheduling in the Convex backend by adding per-lender payout configuration, new payout processing actions (scheduled batch + admin-triggered), and supporting queries/mutations/tests, plus cron registration to run the batch daily.
Changes:
- Extend
lendersanddispersalEntriesschema to support payout configuration + payout audit date. - Add payout module (
config, validators, queries, mutations) plus batch and admin payout actions. - Register a daily cron at 08:00 UTC and add initial Vitest/convex-test coverage for payout helpers.
Reviewed changes
Copilot reviewed 22 out of 22 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| specs/ENG-182/tasks.md | High-level task checklist for ENG-182. |
| specs/ENG-182/chunks/manifest.md | Chunk breakdown and dependencies for implementation plan. |
| specs/ENG-182/chunks/chunk-01-schema-config/tasks.md | Tracks schema/config chunk tasks. |
| specs/ENG-182/chunks/chunk-01-schema-config/context.md | Implementation notes for schema/config chunk. |
| specs/ENG-182/chunks/chunk-02-backend-core/tasks.md | Tracks backend core chunk tasks. |
| specs/ENG-182/chunks/chunk-02-backend-core/context.md | Implementation notes for backend core chunk. |
| specs/ENG-182/chunks/chunk-03-batch-cron/tasks.md | Tracks batch/cron chunk tasks. |
| specs/ENG-182/chunks/chunk-03-batch-cron/context.md | Implementation notes for batch + cron registration. |
| specs/ENG-182/chunks/chunk-04-tests/tasks.md | Tracks tests chunk tasks. |
| specs/ENG-182/chunks/chunk-04-tests/context.md | Testing guidance and intended integration test cases. |
| convex/schema.ts | Adds lender payout config fields and dispersal entry payoutDate. |
| convex/payments/payout/validators.ts | Introduces payout frequency validator + derived TS type. |
| convex/payments/payout/config.ts | Defines frequency defaults/min threshold and isPayoutDue. |
| convex/payments/payout/queries.ts | Adds internal queries for eligible dispersals and lender lookup(s). |
| convex/payments/payout/mutations.ts | Adds internal mutations for marking entries disbursed and updating lender payout date. |
| convex/payments/payout/refs.ts | Centralizes string-based function refs for payout module calls. |
| convex/payments/payout/batchPayout.ts | Implements daily batch payout action logic. |
| convex/payments/payout/adminPayout.ts | Implements admin-triggered immediate payout action. |
| convex/payments/payout/tests/config.test.ts | Unit tests for payout config constants and isPayoutDue. |
| convex/payments/payout/tests/batchPayout.test.ts | Component tests for payout query/mutation helpers (not full batch action). |
| convex/payments/payout/tests/adminPayout.test.ts | Component tests for scoping/threshold logic (not the admin action). |
| convex/crons.ts | Registers a daily 08:00 UTC payout cron (via function reference). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Aggregate per-entry console.warn into a single warning per query call to prevent log spam during batch payout runs with many legacy entries - Rename getLendersWithPayableBalance → getActiveLenders since the query returns all active lenders without filtering by payable balance Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…API refs Root cause: `convex codegen` failed because `e2eHelpers.ts` (a non-test utility file) imported vitest's `expect` at the top level. Convex's bundler skips files with multiple dots (*.test.ts) but analyzed e2eHelpers.ts as a normal module, triggering vitest's "failed to access internal state" error. Fix: - Rename e2eHelpers.ts → e2eHelpers.test-utils.ts (second dot makes Convex skip it during bundling) - Delete refs.ts — it used makeFunctionReference with string-based paths that bypassed API type safety - Update adminPayout.ts and batchPayout.ts to use internal.payments.payout.* references directly from the generated API - Update crons.ts to use internal.payments.payout.batchPayout.processPayoutBatch instead of makeFunctionReference Addresses PR #282 review comments on threads 3 and 7. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Implement claim-before-post pattern: call claimEntriesForPayout (pending
-> disbursed) BEFORE postLenderPayout. If ledger posting fails, revert
claimed entries via revertClaimedEntries. This prevents double payouts
when admin and batch cron run concurrently on the same entries.
- Replace ad-hoc idempotency keys (payout-batch:...) with
buildIdempotencyKey("lender-payout-sent", "batch", ...) to use the
standard cash-ledger: prefix and eliminate noisy log warnings.
- Extract processMortgageGroup helper to reduce handler complexity.
- Fix unused variable lint errors in test scaffolding.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…nal.* The internal.payments.payout.* namespace does not exist in the generated Convex API types, so direct references cause TypeScript errors. Restore imports from ./refs.ts which uses makeFunctionReference to work around the codegen limitation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Addresses PR review comment requesting end-to-end coverage of the batch payout action. Adds two integration tests that run the full processPayoutBatch action via convex-test and assert: 1. LENDER_PAYOUT_SENT journal entry exists with correct amount and idempotency key format (cash-ledger: prefix) 2. Dispersal entries are marked disbursed with payoutDate set 3. Lender's lastPayoutDate is updated 4. Entries from different mortgages produce separate journal entries (grouping-by-mortgage validation) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Addresses PR review feedback requesting tests that invoke the action end-to-end rather than simulating behavior in test code. The new integration test suite covers: - LENDER_PAYOUT_SENT journal entry creation with correct idempotency key - Dispersal entries marked as disbursed with payoutDate - Lender lastPayoutDate updated after successful payout - mortgageId scoping (only targeted mortgage entries are paid out) - Minimum threshold skipping (entries below MINIMUM_PAYOUT_CENTS) - Zero-payout early return for lenders with no eligible entries - Inactive lender rejection - Partial failure reporting with ConvexError details - Multi-entry summation per mortgage group Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (5)
specs/ENG-182/chunks/chunk-02-backend-core/tasks.md (1)
4-4: Minor documentation inconsistency: function was renamed.The PR commits indicate
getLendersWithPayableBalancewas renamed togetActiveLendersto better reflect the actual query intent. Consider updating the task description to match the final implementation name.📝 Suggested documentation update
-- [ ] T-005: Create `convex/payments/payout/queries.ts` — `getLendersWithPayableBalance` internal query +- [ ] T-005: Create `convex/payments/payout/queries.ts` — `getActiveLenders` internal query🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@specs/ENG-182/chunks/chunk-02-backend-core/tasks.md` at line 4, Update the task description for T-005 to use the final function name: replace references to getLendersWithPayableBalance with getActiveLenders and adjust the brief phrasing to say "Create convex/payments/payout/queries.ts — getActiveLenders internal query" so the task matches the implemented function name; ensure any mentions in the task list or surrounding text are also updated to avoid inconsistent naming.convex/payments/payout/__tests__/config.test.ts (1)
64-72: Consider adding bi_weekly "never paid out" test for completeness.The "no previous payout" section tests
monthlyandweeklybut notbi_weekly. For full coverage consistency, consider adding:it("never paid out (bi_weekly) — always due", () => { expect(isPayoutDue("bi_weekly", undefined, "2026-03-15")).toBe(true); });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@convex/payments/payout/__tests__/config.test.ts` around lines 64 - 72, The tests for "never paid out" cover monthly and weekly but miss bi_weekly; add a new test in convext/payments/payout/__tests__/config.test.ts alongside the existing "never paid out" cases that calls isPayoutDue("bi_weekly", undefined, "2026-03-15") and asserts toBe(true) so the bi_weekly schedule has the same coverage as monthly/weekly.convex/payments/payout/__tests__/adminPayout.test.ts (1)
15-23: Consider aligning handler return type with actual query return.The
GetEligibleHandlerinterface declares_id: stringbut the actual dispersal entry_idisId<"dispersalEntries">. While theas unknown ascast makes this work, aligning the type would improve type safety:interface GetEligibleHandler { _handler: ( ctx: QueryCtx, args: { lenderId: Id<"lenders">; today: string } ) => Promise<Array<{ _id: Id<"dispersalEntries">; mortgageId: Id<"mortgages">; amount: number }>>; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@convex/payments/payout/__tests__/adminPayout.test.ts` around lines 15 - 23, The GetEligibleHandler return type is too loose: it declares _id as string and mortgageId as string while the real query returns typed IDs; update the interface used for getEligibleQuery (GetEligibleHandler) to return Promise<Array<{ _id: Id<"dispersalEntries">; mortgageId: Id<"mortgages">; amount: number }>> so it matches the actual getEligibleDispersalEntries output and remove the need for the unsafe as unknown as cast when assigning getEligibleQuery to getEligibleDispersalEntries.convex/payments/payout/__tests__/batchPayout.test.ts (1)
55-486: Tests comprehensively cover existing mutations but miss coverage for claim/revert mutations.The tests correctly validate:
getEligibleDispersalEntries: hold period filtering, legacy entries, status filteringgetActiveLenders: active-only filteringmarkEntriesDisbursed: status update and concurrency guardHowever, once
claimEntriesForPayoutandrevertClaimedEntriesare implemented, tests should be added to verify:
- Claim transitions entries from
pendingtodisbursed- Revert transitions entries back to
pending- The full claim→post→revert failure path
Would you like me to generate test cases for the claim/revert mutations once they're implemented?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@convex/payments/payout/__tests__/batchPayout.test.ts` around lines 55 - 486, Tests are missing coverage for the new claim/revert flow: add tests exercising claimEntriesForPayout and revertClaimedEntries that (1) assert claimEntriesForPayout transitions entries from "pending" to the claimed state used by your implementation (e.g., "claimed" or "disbursed" depending on how claimEntriesForPayout marks them), (2) assert revertClaimedEntries transitions those entries back to "pending", and (3) simulate the full claim→post→revert failure path (claim, attempt post that fails, then call revertClaimedEntries) verifying statuses and persisted fields at each step; reference the functions claimEntriesForPayout and revertClaimedEntries and reuse the existing test harness patterns (seedMinimalEntities, ctx.db.insert into "dispersalEntries", and calling mutation handlers via claimEntriesForPayout._handler / revertClaimedEntries._handler) so tests mirror markEntriesDisbursed/expect patterns.specs/ENG-182/chunks/chunk-02-backend-core/context.md (1)
53-72: Spec defines onlymarkEntriesDisbursedbut implementation expects additional mutations.The spec documents two mutations:
markEntriesDisbursed: patches entries todisbursedstatusupdateLenderPayoutDate: patches lender'slastPayoutDateHowever, both
batchPayout.tsandadminPayout.tsexpect:
claimEntriesForPayout: claim entries before posting (concurrency lock)revertClaimedEntries: revert claims if posting failsThe spec should be updated to include these mutations, or the implementation should be revised to use
markEntriesDisbursedfor both claiming and reverting.Would you like me to draft the additional mutation specifications for
claimEntriesForPayoutandrevertClaimedEntries?🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@specs/ENG-182/chunks/chunk-02-backend-core/context.md` around lines 53 - 72, The spec currently documents markEntriesDisbursed and updateLenderPayoutDate but the code expects two additional internal mutations used for concurrency control: claimEntriesForPayout and revertClaimedEntries; add spec entries for both in convex/payments/payout/mutations.ts: declare them as internalMutation with args { entryIds: v.array(v.id("dispersalEntries")), lockId: v.string() } (or similar unique lock token) for claimEntriesForPayout and { entryIds: v.array(v.id("dispersalEntries")) } for revertClaimedEntries, and describe behavior: claimEntriesForPayout must atomically mark each dispersal entry as a claimed state (e.g., set status to "claimed" and record lockId/claimedAt) to act as a concurrency lock, while revertClaimedEntries must clear that claim (restore status to "eligible" and remove lockId/claimedAt) so batchPayout.ts and adminPayout.ts can safely claim and rollback entries; alternatively, if you prefer changing code instead of spec, update batchPayout.ts/adminPayout.ts to use markEntriesDisbursed and updateLenderPayoutDate only.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@specs/ENG-182/tasks.md`:
- Line 12: Update the T-005 task to reference the actual exported function name
getActiveLenders instead of the outdated getLendersWithPayableBalance; locate
the task entry labeled "T-005" in the spec and replace the function name so the
spec matches the implementation (export getActiveLenders in
convex/payments/payout/queries.ts).
---
Nitpick comments:
In `@convex/payments/payout/__tests__/adminPayout.test.ts`:
- Around line 15-23: The GetEligibleHandler return type is too loose: it
declares _id as string and mortgageId as string while the real query returns
typed IDs; update the interface used for getEligibleQuery (GetEligibleHandler)
to return Promise<Array<{ _id: Id<"dispersalEntries">; mortgageId:
Id<"mortgages">; amount: number }>> so it matches the actual
getEligibleDispersalEntries output and remove the need for the unsafe as unknown
as cast when assigning getEligibleQuery to getEligibleDispersalEntries.
In `@convex/payments/payout/__tests__/batchPayout.test.ts`:
- Around line 55-486: Tests are missing coverage for the new claim/revert flow:
add tests exercising claimEntriesForPayout and revertClaimedEntries that (1)
assert claimEntriesForPayout transitions entries from "pending" to the claimed
state used by your implementation (e.g., "claimed" or "disbursed" depending on
how claimEntriesForPayout marks them), (2) assert revertClaimedEntries
transitions those entries back to "pending", and (3) simulate the full
claim→post→revert failure path (claim, attempt post that fails, then call
revertClaimedEntries) verifying statuses and persisted fields at each step;
reference the functions claimEntriesForPayout and revertClaimedEntries and reuse
the existing test harness patterns (seedMinimalEntities, ctx.db.insert into
"dispersalEntries", and calling mutation handlers via
claimEntriesForPayout._handler / revertClaimedEntries._handler) so tests mirror
markEntriesDisbursed/expect patterns.
In `@convex/payments/payout/__tests__/config.test.ts`:
- Around line 64-72: The tests for "never paid out" cover monthly and weekly but
miss bi_weekly; add a new test in
convext/payments/payout/__tests__/config.test.ts alongside the existing "never
paid out" cases that calls isPayoutDue("bi_weekly", undefined, "2026-03-15") and
asserts toBe(true) so the bi_weekly schedule has the same coverage as
monthly/weekly.
In `@specs/ENG-182/chunks/chunk-02-backend-core/context.md`:
- Around line 53-72: The spec currently documents markEntriesDisbursed and
updateLenderPayoutDate but the code expects two additional internal mutations
used for concurrency control: claimEntriesForPayout and revertClaimedEntries;
add spec entries for both in convex/payments/payout/mutations.ts: declare them
as internalMutation with args { entryIds: v.array(v.id("dispersalEntries")),
lockId: v.string() } (or similar unique lock token) for claimEntriesForPayout
and { entryIds: v.array(v.id("dispersalEntries")) } for revertClaimedEntries,
and describe behavior: claimEntriesForPayout must atomically mark each dispersal
entry as a claimed state (e.g., set status to "claimed" and record
lockId/claimedAt) to act as a concurrency lock, while revertClaimedEntries must
clear that claim (restore status to "eligible" and remove lockId/claimedAt) so
batchPayout.ts and adminPayout.ts can safely claim and rollback entries;
alternatively, if you prefer changing code instead of spec, update
batchPayout.ts/adminPayout.ts to use markEntriesDisbursed and
updateLenderPayoutDate only.
In `@specs/ENG-182/chunks/chunk-02-backend-core/tasks.md`:
- Line 4: Update the task description for T-005 to use the final function name:
replace references to getLendersWithPayableBalance with getActiveLenders and
adjust the brief phrasing to say "Create convex/payments/payout/queries.ts —
getActiveLenders internal query" so the task matches the implemented function
name; ensure any mentions in the task list or surrounding text are also updated
to avoid inconsistent naming.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 13735075-c873-4ace-bdd5-1cf855f9d0d1
📒 Files selected for processing (23)
convex/crons.tsconvex/payments/cashLedger/__tests__/e2eHelpers.test-utils.tsconvex/payments/cashLedger/__tests__/e2eLifecycle.test.tsconvex/payments/payout/__tests__/adminPayout.test.tsconvex/payments/payout/__tests__/batchPayout.test.tsconvex/payments/payout/__tests__/config.test.tsconvex/payments/payout/adminPayout.tsconvex/payments/payout/batchPayout.tsconvex/payments/payout/config.tsconvex/payments/payout/mutations.tsconvex/payments/payout/queries.tsconvex/payments/payout/validators.tsconvex/schema.tsspecs/ENG-182/chunks/chunk-01-schema-config/context.mdspecs/ENG-182/chunks/chunk-01-schema-config/tasks.mdspecs/ENG-182/chunks/chunk-02-backend-core/context.mdspecs/ENG-182/chunks/chunk-02-backend-core/tasks.mdspecs/ENG-182/chunks/chunk-03-batch-cron/context.mdspecs/ENG-182/chunks/chunk-03-batch-cron/tasks.mdspecs/ENG-182/chunks/chunk-04-tests/context.mdspecs/ENG-182/chunks/chunk-04-tests/tasks.mdspecs/ENG-182/chunks/manifest.mdspecs/ENG-182/tasks.md
|
|
||
| ### Chunk 2: Backend Queries & Mutations ✅ | ||
| - [x] T-004: Create `convex/payments/payout/queries.ts` — `getEligibleDispersalEntries` (internal query: pending entries past hold period for a lender) | ||
| - [x] T-005: Create `convex/payments/payout/queries.ts` — `getLendersWithPayableBalance` (internal query: active lenders) |
There was a problem hiding this comment.
Task T-005 references outdated function name.
The task references getLendersWithPayableBalance, but the implementation in convex/payments/payout/queries.ts exports getActiveLenders (per commit history, this was renamed). Update the spec to reflect the actual function name for consistency.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@specs/ENG-182/tasks.md` at line 12, Update the T-005 task to reference the
actual exported function name getActiveLenders instead of the outdated
getLendersWithPayableBalance; locate the task entry labeled "T-005" in the spec
and replace the function name so the spec matches the implementation (export
getActiveLenders in convex/payments/payout/queries.ts).
- Replace makeFunctionReference with internal.* API refs in refs.ts now that codegen is fixed - Fix ConvexError<unknown> → ConvexError<Record<string, string | number>> to satisfy Value type constraint Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
# Lender Payout Scheduling & Frequency Configuration This pull request implements automated lender payout scheduling with configurable frequency settings, enabling both scheduled batch payouts and admin-triggered immediate payouts. ## Schema Changes Added three new optional fields to the `lenders` table: - `payoutFrequency`: Configurable frequency (`monthly`, `bi_weekly`, `weekly`, `on_demand`) - `lastPayoutDate`: Tracks the last payout execution date (YYYY-MM-DD format) - `minimumPayoutCents`: Per-lender override for minimum payout threshold Added `payoutDate` field to `dispersalEntries` table for audit traceability. ## Core Features ### Batch Payout Processing - Daily cron job at 08:00 UTC evaluates all active lenders for payout eligibility - Respects individual lender frequency settings (defaults to monthly) - Groups dispersal entries by mortgage and applies minimum threshold checks - Posts lender payouts via the existing cash ledger system with proper idempotency ### Admin Immediate Payout - Admin action to trigger immediate payouts bypassing frequency schedules - Optional mortgage-specific scoping for targeted payouts - Still respects hold periods and minimum thresholds ### Configuration & Logic - `isPayoutDue()` function determines payout eligibility based on frequency and last payout date - Monthly frequency uses 28-day intervals, weekly uses 7-day intervals - Default minimum payout threshold of 100 cents ($1.00) to prevent micro-payouts - `on_demand` frequency excludes lenders from automated batch processing ## Data Flow 1. **Batch Processing**: Cron queries eligible dispersal entries (past hold period, pending status) 2. **Grouping**: Entries grouped by mortgage ID for separate payout transactions 3. **Threshold Check**: Only processes groups meeting minimum payout amounts 4. **Payout Execution**: Calls `postLenderPayout` with unique idempotency keys 5. **Status Updates**: Marks dispersal entries as `disbursed` and updates lender payout dates ## Concurrency Safety Optimistic concurrency guard in `markEntriesDisbursed` prevents double-payout scenarios when admin actions run concurrently with batch processing. Each dispersal entry must have `pending` status before being marked `disbursed`. ## Testing Comprehensive test coverage includes: - Unit tests for frequency calculation logic - Integration tests for batch payout workflows - Admin payout functionality with mortgage scoping - Hold period enforcement and minimum threshold validation - Concurrency protection and idempotency verification <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit # Release Notes * **New Features** * Automatic daily lender payouts at 08:00 UTC with configurable frequency options (monthly, bi-weekly, weekly, on-demand) * Admin capability to trigger immediate payouts for specific lenders or mortgages * Customizable minimum payout thresholds and hold period enforcement * Robust idempotent payout processing with automatic failure recovery <!-- end of auto-generated comment: release notes by coderabbit.ai -->

Lender Payout Scheduling & Frequency Configuration
This pull request implements automated lender payout scheduling with configurable frequency settings, enabling both scheduled batch payouts and admin-triggered immediate payouts.
Schema Changes
Added three new optional fields to the
lenderstable:payoutFrequency: Configurable frequency (monthly,bi_weekly,weekly,on_demand)lastPayoutDate: Tracks the last payout execution date (YYYY-MM-DD format)minimumPayoutCents: Per-lender override for minimum payout thresholdAdded
payoutDatefield todispersalEntriestable for audit traceability.Core Features
Batch Payout Processing
Admin Immediate Payout
Configuration & Logic
isPayoutDue()function determines payout eligibility based on frequency and last payout dateon_demandfrequency excludes lenders from automated batch processingData Flow
postLenderPayoutwith unique idempotency keysdisbursedand updates lender payout datesConcurrency Safety
Optimistic concurrency guard in
markEntriesDisbursedprevents double-payout scenarios when admin actions run concurrently with batch processing. Each dispersal entry must havependingstatus before being markeddisbursed.Testing
Comprehensive test coverage includes:
Summary by CodeRabbit
Release Notes