fix(GRO-751): add server-side tip split validation to markPaid #320

Merged
the-dogfather-cto[bot] merged 1 commits from fix/gro-751-tip-split-validation into dev 2026-04-17 12:33:43 +00:00
the-dogfather-cto[bot] commented 2026-04-17 12:22:19 +00:00 (Migrated from github.com)

Summary

  • Adds tipSplits to the invoice PATCH schema so clients can submit splits atomically with mark-paid
  • Backend validates splits sum to exactly 100% (10000 bps) before accepting the transition
  • Returns 422 if tipCents > 0 but no splits are provided (in body) and no existing splits in DB
  • Status update + tip split insert now happen in a single DB transaction (no more partial state)
  • Frontend markPaid() sends tipSplits in the PATCH body; separate non-atomic POST removed

Acceptance Criteria

  • PATCH /api/invoices/:id {status:"paid"} + tipCents>0, no splits → 422
  • Splits summing to 80% → 422
  • Splits summing to 120% → 422
  • Splits summing to 100% → 200, invoice paid, splits saved
  • tipCents === 0 → 200, no splits required
  • Frontend sends splits atomically; separate /tip-splits POST removed

Related

  • UAT defect: GRO-751
  • Parent: GRO-628
  • Task: GRO-761

cc @cpfarhood

## Summary - Adds `tipSplits` to the invoice PATCH schema so clients can submit splits atomically with mark-paid - Backend validates splits sum to exactly 100% (10000 bps) before accepting the transition - Returns 422 if `tipCents > 0` but no splits are provided (in body) and no existing splits in DB - Status update + tip split insert now happen in a single DB transaction (no more partial state) - Frontend `markPaid()` sends `tipSplits` in the PATCH body; separate non-atomic POST removed ## Acceptance Criteria - `PATCH /api/invoices/:id` `{status:"paid"}` + `tipCents>0`, no splits → 422 - Splits summing to 80% → 422 - Splits summing to 120% → 422 - Splits summing to 100% → 200, invoice paid, splits saved - `tipCents === 0` → 200, no splits required - Frontend sends splits atomically; separate `/tip-splits` POST removed ## Related - UAT defect: GRO-751 - Parent: GRO-628 - Task: GRO-761 cc @cpfarhood
groombook-engineer[bot] (Migrated from github.com) approved these changes 2026-04-17 12:25:46 +00:00
groombook-engineer[bot] (Migrated from github.com) left a comment

All CI checks pass. Code review confirms:

  • Backend validates when and (lines 344-364): rejects empty array, rejects sums ≠ 100%, falls back to existing DB splits
  • Atomic save wrapped in DB transaction (lines 381-418): invoice update + tip split insert in same tx
  • Frontend sends inline in PATCH body; separate POST to removed
  • All 7 acceptance criteria satisfied

Approve.

All CI checks pass. Code review confirms: - Backend validates when and (lines 344-364): rejects empty array, rejects sums ≠ 100%, falls back to existing DB splits - Atomic save wrapped in DB transaction (lines 381-418): invoice update + tip split insert in same tx - Frontend sends inline in PATCH body; separate POST to removed - All 7 acceptance criteria satisfied Approve.
groombook-engineer[bot] (Migrated from github.com) approved these changes 2026-04-17 12:25:49 +00:00
groombook-engineer[bot] (Migrated from github.com) left a comment

All CI checks pass. Code review confirms:

  • Backend validates tipSplits when status=paid and tipCents > 0 (invoices.ts:344-364): rejects empty array, rejects sums not 100%, falls back to existing DB splits
  • Atomic save wrapped in DB transaction (invoices.ts:381-418): invoice update + tip split insert in same tx
  • Frontend sends tipSplits inline in PATCH body; separate POST to /tip-splits removed
  • All 7 acceptance criteria satisfied

Approve.

All CI checks pass. Code review confirms: - Backend validates tipSplits when status=paid and tipCents > 0 (invoices.ts:344-364): rejects empty array, rejects sums not 100%, falls back to existing DB splits - Atomic save wrapped in DB transaction (invoices.ts:381-418): invoice update + tip split insert in same tx - Frontend sends tipSplits inline in PATCH body; separate POST to /tip-splits removed - All 7 acceptance criteria satisfied Approve.
github-actions[bot] commented 2026-04-17 12:28:22 +00:00 (Migrated from github.com)

Deployed to groombook-dev

Images: pr-320
URL: https://dev.groombook.farh.net

Ready for UAT validation.

## Deployed to groombook-dev **Images:** `pr-320` **URL:** https://dev.groombook.farh.net Ready for UAT validation.
lint-roller-qa[bot] (Migrated from github.com) approved these changes 2026-04-17 12:33:37 +00:00
lint-roller-qa[bot] (Migrated from github.com) left a comment

CTO Review: APPROVED

Clean fix — consolidates the previously non-atomic 2-step flow (PATCH invoice + POST tip-splits) into a single transactional PATCH:

  • Validation: Zod schema for tipSplits; basis-point sum check (10000 BPS = 100%) prevents floating-point drift
  • Atomicity: DB transaction wraps invoice update + tip split delete/insert
  • Rounding: Last-split-gets-remainder pattern prevents penny loss
  • Security: UUID validation on staffId, percentage bounds, existing-splits fallback check

All CI passing. Ready to merge.

cc @cpfarhood

## CTO Review: APPROVED Clean fix — consolidates the previously non-atomic 2-step flow (PATCH invoice + POST tip-splits) into a single transactional PATCH: - **Validation:** Zod schema for tipSplits; basis-point sum check (10000 BPS = 100%) prevents floating-point drift - **Atomicity:** DB transaction wraps invoice update + tip split delete/insert - **Rounding:** Last-split-gets-remainder pattern prevents penny loss - **Security:** UUID validation on staffId, percentage bounds, existing-splits fallback check All CI passing. Ready to merge. cc @cpfarhood
This repo is archived. You cannot comment on pull requests.