feat: tip and payment splitting between staff roles

Implements groombook/groombook#12 — track which staff worked on each
pet and calculate tip distribution based on who was involved.

Changes:
- DB: Add bather_staff_id to appointments (optional secondary staff)
- DB: Add invoice_tip_splits table (per-staff tip share ledger)
- API: appointments POST/PATCH accept batherStaffId
- API: GET /invoices/:id now includes tipSplits[]
- API: POST /invoices/:id/tip-splits — saves tip distribution
- API: GET /reports/tip-splits — payroll summary of tip earnings
- Frontend: Bather/Assistant select on New Appointment form
- Frontend: Tip Distribution section in Invoice Detail modal
  - Auto-populates 70%/30% split when bather is assigned
  - Editable percentages before payment; saved on Mark as Paid
  - Displays recorded splits on paid invoices

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Groom Book CTO
2026-03-17 22:01:05 +00:00
parent 93b738b613
commit 9b1ad6c4d8
9 changed files with 335 additions and 15 deletions
+12
View File
@@ -91,6 +91,7 @@ export interface Appointment {
petId: string;
serviceId: string;
staffId: string | null;
batherStaffId: string | null;
status: AppointmentStatus;
startTime: string;
endTime: string;
@@ -103,6 +104,16 @@ export interface Appointment {
updatedAt: string;
}
export interface InvoiceTipSplit {
id: string;
invoiceId: string;
staffId: string | null;
staffName: string;
sharePct: string;
shareCents: number;
createdAt: string;
}
export type InvoiceStatus = "draft" | "pending" | "paid" | "void";
export type PaymentMethod = "cash" | "card" | "check" | "other";
@@ -131,6 +142,7 @@ export interface Invoice {
createdAt: string;
updatedAt: string;
lineItems?: InvoiceLineItem[];
tipSplits?: InvoiceTipSplit[];
}
// Paginated list response