Commit Graph

14 Commits

Author SHA1 Message Date
Scrubs McBarkley d1ab91adfa feat: appointment confirmation and cancellation (GH #98, GRO-153)
Add customer confirmation/cancellation flow for appointments:

- DB migration (0013): add confirmation_status, confirmed_at, cancelled_at,
  confirmation_token to appointments table with index on token column
- schema.ts + factories.ts + types: expose new columns and ConfirmationStatus type
- GET /api/book/confirm/:token — tokenized confirm via email link (redirects)
- GET /api/book/cancel/:token — tokenized cancel via email link, single-use token
- POST /api/appointments/:id/confirm — portal/staff confirm endpoint
- POST /api/appointments/:id/cancel — portal/staff cancel endpoint
- Reminder emails now include Confirm/Cancel CTA buttons with tokenized links
- Reminder service generates confirmation token if missing before sending
- Staff calendar shows confirmation status indicator on appointment cards
  and in the detail modal (confirmed ✓ / customer cancelled ✗)
- /booking/confirmed, /booking/cancelled, /booking/error redirect pages
- 23 new unit tests covering all new endpoints and edge cases

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-24 16:02:58 +00:00
Scrubs McBarkley 1380848aea feat: pet photo upload via presigned S3 URLs (GH #93, GRO-123)
- DB migration 0012: add photo_key and photo_uploaded_at columns to pets table
- S3 client utility (apps/api/src/lib/s3.ts): presigned PUT/GET, delete via Rook-Ceph RGW
- API photo routes on petsRouter:
  - POST /:petId/photo/upload-url — returns presigned PUT URL + object key
  - POST /:petId/photo/confirm    — records key in DB after successful upload
  - DELETE /:petId/photo          — deletes from storage and clears DB
  - GET /:petId/photo             — returns presigned GET URL
- RBAC: all staff roles (manager, receptionist, groomer) may upload/delete photos;
  restructured index.ts guards so groomer-accessible photo paths don't overlap
  with the manager/receptionist-only general pets write guard
- Frontend PetPhotoDisplay: responsive image with shimmer skeleton and paw placeholder
- Frontend PetPhotoUpload: client-side resize to max 1200px, XHR with progress,
  presigned PUT flow — binary data never passes through the API server
- Wired both components into Clients.tsx staff portal pet cards
- Unit tests: 14 test cases covering all four routes (happy path + error cases)

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-22 00:07:48 +00:00
Scrubs McBarkley 11c4f0a07b feat(db): add indexes on impersonation tables (GitHub #95)
Add three indexes to prevent full table scans as session volume grows:
- impersonation_sessions(staff_id, status) for active-session lookup
- impersonation_sessions(client_id) for existing-session check
- impersonation_audit_logs(session_id) for audit log lookup by session

Migration 0011 applied and verified on dev database.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-21 23:28:09 +00:00
Scrubs McBarkley ad6024f3d9 feat: deterministic seed, impersonation migration, test factories (GRO-110)
Phase 1 — Seed Hardening:
- Replace all Math.random() calls in seed.ts with a Mulberry32 seeded PRNG
  (seed 42) so the same data set is reproduced on every run
- Replace crypto.randomUUID() with a PRNG-based UUID v4 generator
- Add manager (Jordan Lee) and receptionist (Sam Rivera) staff members
  to seed — previously all staff were groomers
- New packages/db/src/reset.ts drops all tables/enums and re-runs
  migrate + seed; exposed as `pnpm db:reset` at root
- Generate migration 0010_impersonation_sessions.sql for the
  impersonation_sessions and impersonation_audit_logs tables that were
  already in schema.ts but had no corresponding migration

Phase 2 — Test Factories:
- New packages/db/src/factories.ts with buildStaff, buildClient, buildPet,
  buildService, buildAppointment and resetFactoryCounters helpers
- Exported via @groombook/db/factories subpath (package.json + vitest alias)
- impersonation.test.ts updated to use buildStaff instead of hand-rolled
  fixture objects

Closes #90 (Phases 1 + 2)

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-21 19:34:52 +00:00
groombook-paperclip[bot] 19e0f5e3ca feat: client disable/deletion with soft-delete (#69)
* feat: add client disable/deletion with soft-delete (#67)

Add soft-delete support for clients: disable is the default action
(hiding from client list and booking flow), with permanent deletion
requiring explicit type-to-confirm. Disabled clients remain in
reporting and can be re-enabled by staff.

- Add client_status enum (active/disabled) and disabled_at column
- API defaults GET /api/clients to active-only, ?includeDisabled=true shows all
- PATCH /api/clients/:id accepts status field for disable/enable
- DELETE requires ?confirm=true query param
- Booking flow skips disabled clients
- Frontend: show disabled toggle, disable/enable buttons, delete confirmation modal

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

* fix: remove unused updateClientSchema (lint error)

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

---------

Co-authored-by: Groom Book CTO <cto@groombook.app>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-19 20:03:18 +00:00
groombook-paperclip[bot] f2501d9972 feat: customizable business branding (name, logo, colors) (#63)
* feat: add customizable business branding (name, logo, colors)

Add admin settings for business branding with name, logo upload, and
color scheme via CSS custom properties. Includes database migration,
API endpoints, admin settings page, and dynamic branding in both
admin nav and customer portal.

Closes #61

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

* fix: address review feedback on branding PR

- Replace dynamic import with static import for @groombook/db in public branding endpoint
- Restore active nav item background highlight (bg-stone-100) in CustomerPortal
- Remove non-null assertion in settings route, add proper error handling

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

* chore: trigger CI

* fix: resolve lint error and test failure for branding feature

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

* fix: update E2E tests for branding changes

- Update navigation test to expect "GroomBook" (default branding) instead
  of hardcoded "Paws & Reflect" since CustomerPortal now uses dynamic branding
- Add /api/branding mock to shared E2E fixtures so BrandingProvider resolves
  immediately in all tests, preventing unhandled fetch interference

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

---------

Co-authored-by: GroomBook CTO <cto@groombook.dev>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: GroomBook CTO <cto@groombook.app>
2026-03-19 11:07:07 +00:00
groombook-paperclip[bot] 4ab5597fd5 feat: tip and payment splitting between staff roles (#34)
* feat: multi-groomer calendar view with per-groomer filtering

Add groomer view mode to the appointments calendar:
- Toggle between "Status" (existing) and "Groomer" color coding
- Per-groomer visibility toggles with color-coded buttons
- Appointments colored by assigned groomer in groomer view
- Groomer name shown on appointment blocks in groomer view
- Unassigned appointments shown in neutral gray

Satisfies groombook/groombook#11 requirements for side-by-side/unified
groomer schedule visibility and per-groomer filter/toggle.

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* 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>

* fix: remove unused staff import from invoices route

Co-Authored-By: Paperclip <noreply@paperclip.ing>

---------

Co-authored-by: Groom Book CTO <cto@groombook.app>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-03-17 22:03:46 +00:00
groombook-paperclip[bot] 14ed19497f feat: detailed pet profile attributes and grooming visit history (closes #13)
- Add cut_style, shampoo_preference, special_care_notes, custom_fields columns to pets table
- Add grooming_visit_logs table to track per-visit grooming details (cut, products, notes)
- Extend pets API to accept and return new profile fields
- Add /api/grooming-logs endpoint (GET by petId, POST, DELETE)
- Update Pet type with new fields; add GroomingVisitLog type
- Update Clients page: grooming preferences section in pet card, "Log visit" button,
  visit history panel showing last 3 visits, expanded pet form with grooming preferences

Co-authored-by: Groom Book CTO <cto@groombook.app>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-03-17 21:46:40 +00:00
groombook-paperclip[bot] f47717dfd8 feat: multi-pet client group booking (closes #10) (#31)
* feat: multi-pet client group booking (closes groombook/groombook#10) (GRO-27)

- Add appointment_groups table: links multiple appointments from one client visit
- Add group_id FK on appointments (nullable, backward-compatible)
- Add GET/POST/PATCH/DELETE /api/appointment-groups endpoints
  - POST creates group record + one appointment per pet atomically (with conflict checks)
  - DELETE soft-cancels all appointments in the group
- Add GroupBooking.tsx page at /group-bookings with:
  - Dynamic pet-slot form (min 2 pets, each with their own groomer/service/end time)
  - Auto-calculates end time from service duration
  - Group card list showing all pets, groomers, and statuses side-by-side
  - Client filter and cancel-all action
- Wire into nav and routing in App.tsx
- Export AppointmentGroup type; add groupId field to Appointment type

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix: remove eslint-disable for uninstalled react-hooks plugin; remove unused clientMap (GRO-27)

Co-Authored-By: Paperclip <noreply@paperclip.ing>

---------

Co-authored-by: Groom Book CTO <cto@groombook.app>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-03-17 21:36:03 +00:00
groombook-paperclip[bot] addcefe70b feat: automated appointment reminders via email (GRO-23) (#29)
Implements Phase 1 of groombook/groombook#4 — automated email reminders
for upcoming appointments, with booking confirmations sent immediately
on creation.

- **DB**: new `reminder_logs` table tracks sent reminders per appointment
  (unique on appointmentId+type prevents duplicates); `clients` gains
  `email_opt_out` boolean (migration 0004_reminder_logs)
- **Email service**: `apps/api/src/services/email.ts` — nodemailer SMTP
  transport (disabled when SMTP_HOST is unset, so self-hosted installs
  without email config are unaffected); confirmation and reminder email
  templates included
- **Reminder scheduler**: `apps/api/src/services/reminders.ts` — node-cron
  job runs every minute, checks for appointments in the upcoming reminder
  windows (default: 24 h and 2 h), sends emails for opted-in clients,
  and records sends in reminder_logs (idempotent via ON CONFLICT DO NOTHING)
- **Confirmation email**: sent fire-and-forget after successful appointment
  creation (both single and recurring); never blocks the API response
- **Config**: SMTP_HOST, SMTP_PORT, SMTP_SECURE, SMTP_USER, SMTP_PASS,
  SMTP_FROM, REMINDER_HOURS_EARLY, REMINDER_HOURS_LATE env vars documented
  in .env.example; all optional — feature is silently disabled without them
- **Types**: Client.emailOptOut field added to shared types package

Co-authored-by: Groom Book CTO <cto@groombook.app>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-03-17 20:46:49 +00:00
groombook-paperclip[bot] e7cf185d8c feat: recurring appointments with cascading change propagation (#28)
* feat: recurring appointments with cascading change propagation

Implements GitHub issue #9 — recurring appointment scheduling with
configurable frequency and cascade edit/cancel options.

Changes:
- DB: add `recurring_series` table (frequency_weeks) and series_id /
  series_index columns on appointments (migration 0003)
- API POST /appointments: accepts optional `recurrence` object
  (frequencyWeeks + count) that creates a full series in one transaction
- API PATCH /appointments/🆔 new `cascadeMode` field
  (this_only | this_and_future | all) applies time-delta shifts and
  field updates across the series
- API DELETE /appointments/🆔 new `?cascade=` query param cancels
  this_only / this_and_future / all series members
- Frontend: booking form gains a "Recurring appointment" checkbox with
  frequency and count pickers; calendar chips show a ↻ recurring label;
  detail modal shows "Recurring series" badge and a cascade-delete radio
  picker for series appointments

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix: resolve TypeScript errors in recurring appointments route

Guard against possibly-undefined results from Drizzle .returning()
destructuring — use indexed access + explicit null checks instead of
array destructuring for the recurring_series insert, and add an early
throw when the series or first appointment row is missing.

Co-Authored-By: Paperclip <noreply@paperclip.ing>

---------

Co-authored-by: Groom Book CTO <cto@groombook.app>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-03-17 20:37:33 +00:00
groombook-paperclip[bot] b767a00b5f feat: basic POS & invoicing (closes groombook/groombook#5) (#26)
- Add invoice_status and payment_method enums to schema
- Add invoices table: appointmentId, clientId, subtotal/tax/tip/total cents,
  status (draft/pending/paid/void), paymentMethod, paidAt, notes
- Add invoice_line_items table: invoiceId, description, qty, unitPrice, total
- Migration 0002_invoices.sql with FK constraints and journal entry
- POST /api/invoices — create invoice with line items
- POST /api/invoices/from-appointment/:id — one-click invoice from appointment,
  pre-populated with service name and price; returns 409 if already invoiced
- GET /api/invoices — list with optional ?status/clientId/appointmentId filters
- GET /api/invoices/:id — invoice with line items
- PATCH /api/invoices/:id — update status, payment method, tip, notes; auto-sets
  paidAt when marking paid; blocks edits on voided invoices
- Add Invoice/InvoiceLineItem types to @groombook/types
- InvoicesPage: list view with status filter, create from appointment modal,
  detail modal with tip input, payment method selector, Mark as Paid/Void actions
- Add Invoices nav link in App.tsx

Co-authored-by: Groom Book CTO <cto@groombook.app>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-03-17 20:02:04 +00:00
groombook-paperclip[bot] eb9255eee0 feat: add health alerts field and delete actions for pets and clients (#25)
- Add health_alerts column to pets table (migration 0001)
- Update DB schema, shared types, and API Zod validation
- Show health alerts prominently (red badge) in pet cards
- Add health alerts field to pet form with allergy/condition placeholder
- Add delete button per pet with confirmation dialog
- Add delete client button with cascade warning

Closes groombook/groombook#2

Co-authored-by: Groom Book CTO <cto@groombook.app>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-03-17 19:40:29 +00:00
groombook-paperclip[bot] 820a5240d1 feat: Docker self-hosting setup
- Fix Dockerfiles to copy pnpm-lock.yaml (frozen-lockfile compliance)
- Add migrate target to API Dockerfile using builder stage
- Add migrate service to docker-compose that runs before API starts
- Add AUTH_DISABLED env var bypass to auth middleware for dev/Docker
- Proxy /api/ from nginx to API container (no CORS needed)
- Include initial Drizzle migration (0000_colossal_colossus.sql)
- Add .env.example with all configurable variables
- Update README with Docker self-hosting instructions

Closes #7

Co-authored-by: Groom Book CTO <cto@groombook.app>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-03-17 18:50:07 +00:00