diff --git a/obsidian/Crussell/Admin Manual.md b/obsidian/Crussell/Admin Manual.md index e64ca4d..9feb1d8 100644 --- a/obsidian/Crussell/Admin Manual.md +++ b/obsidian/Crussell/Admin Manual.md @@ -14,7 +14,7 @@ Go to the website and log in with your admin email and password. Once logged in - **Today** — your daily operations hub. This is where you manage today's appointments, approve new bookings, and handle walk-ins. - **Admin** — the management dashboard. This is where you set up services, manage customers, adjust schedules, and handle the salon's settings. -- **Schedule** — a weekly calendar view showing all appointments across the week in a Google Calendar-style layout. Access it from the navigation bar. +- **Schedule** — a weekly calendar view showing all appointments across the week in a Google Calendar-style layout. Closed days are shown with a striped overlay; any bookings that exist on closed days (from historical data or system bookings) are still visible above the stripes. Access it from the navigation bar. --- @@ -47,6 +47,16 @@ At the very top of the page, you'll see two cards: - Shows how long until they arrive - Lists their services +**Summary Card (End of Day / Closed Days):** + +When all today's appointments are done, the current/next cards are replaced by a **summary card** that shows: + +- **All done for today!** — shown on a regular open day when all appointments are finished. Displays daily stats: customers served, total amount taken, tips, and bookings made since yesterday's closing time. + +- **Closed today** — shown when the salon is closed (regular closed day or a holiday). Shows the same stats but covering the full working period before the closure (e.g., Monday's summary covers the previous Tuesday–Saturday). This includes any activity that happened on closed days (online bookings, gift card purchases, etc.). + +The summary card also appears on an open day if **tomorrow is closed**, showing both today's daily summary and an additional week summary covering the period up to today. + #### Buttons on the Current Appointment Card **Edit** — Opens a window where you can change which services are included in the appointment. You can add services, remove services, or swap them out. You can also adjust the price or duration of individual services if needed (for example, if a service took longer than expected last time). You can also update the appointment notes here. diff --git a/obsidian/Crussell/Technical Manual.md b/obsidian/Crussell/Technical Manual.md index c2d9e93..e6f3c33 100644 --- a/obsidian/Crussell/Technical Manual.md +++ b/obsidian/Crussell/Technical Manual.md @@ -52,7 +52,7 @@ Backend (:8080) | `handlers/payments` | handlers.go, service.go, validators.go, giftcards.go, till.go | Square payments: terminal, online, refunds, tips, saved cards, gift cards (CRUD, topup, transfer, redeem, buy, expired balances, till sales) | | `handlers/webhooks` | square.go | Square webhook handler for payment status updates | | `handlers/admin` | users.go, analytics.go, custom_services.go, discount_campaigns.go, settings.go | Admin user management, custom services CRUD (list/create/get/update/promote/delete), discount campaigns, analytics (stub), business settings (GET/PUT with VAT, gift card config) | -| `handlers/today` | today.go | Current/next appointment, today's grid, pending approvals | +| `handlers/today` | today.go | Current/next appointment, today's grid, pending approvals, `DoneForDay` state with daily/weekly summary (`DailySummary` with `total_bookings`, `customers_served`, `summary_scope`), auto-status transitions, closed-day aggregation via `findWeekSummaryRange` + `computeAggregateSummary`. Exceptional hours lookup uses `exceptional_group_applications.week_start` (0=Monday). | | `handlers/user` | profile.go, account.go, guest.go, loyalty.go, customer_relationship.go, gdpr_export.go | User profile, guest creation (with CheckEmailHandler for registered-email detection), loyalty, contact info, GDPR export (async with 12h cache) | | `handlers/services` | services.go | Service catalog, eligibility filtering, patch_test_duration_hours auto-creates patch test records | | `handlers/scheduling` | default-hours.go, exceptional-hours.go, time-blockers.go | Working hours, exceptional groups, time blockers, gift card expiry cleanup (24-month rolling), idle account cleanup (2yr/5yr) | @@ -268,7 +268,7 @@ src/lib/components/ | POST | `/api/admin/services` | Create service | | DELETE | `/api/admin/services/{id}` | Delete service | | PUT | `/api/admin/services/{id}/toggle` | Toggle active status | -| GET | `/api/admin/bookings` | List all bookings | +| GET | `/api/admin/bookings` | List all bookings. Supports `per_page` up to **500** (values over 500 default to 10), `page`, `start_date`, `end_date`, `status` filters. Pagination formula: `totalPages = ceil(total / perPage)`. If `per_page` is omitted, defaults to 10. | | POST | `/api/admin/bookings` | Create booking for user (with `Idempotency-Key` header) | | GET | `/api/admin/bookings/search` | Search bookings | | GET | `/api/admin/bookings/by-created-range` | Get bookings by created_at range (query: `start`, `end` ISO 8601) | @@ -290,8 +290,8 @@ src/lib/components/ | GET | `/api/admin/users/{id}/relationship` | Customer relationship data (spend, visits, top services) | | GET | `/api/admin/users/{id}/patch-tests/eligible` | Eligible patch test services | | POST | `/api/admin/users/{id}/patch-tests` | Record patch test | -| GET | `/api/admin/today/current-next` | Current and next appointment | -| GET | `/api/admin/today/appointments` | Today's appointments | +| GET | `/api/admin/today/current-next` | Current and next appointment. Returns `done_for_day`, `closing_time`, `summary` (DailySummary: `customers_served`, `total_bookings`, `summary_scope`). When today is closed: `summary_scope="week"`, range covers `[weekStart, todayEnd)`. When tomorrow is closed: additional `week_summary` with `scope="week"`. | +| GET | `/api/admin/today/appointments` | Today's appointments (all statuses) | | GET | `/api/admin/today/pending-approvals` | Pending approval queue | | GET | `/api/admin/notifications` | List notifications | | GET | `/api/admin/notifications/unread-count` | Unread count for bell icon | @@ -589,7 +589,7 @@ src/lib/components/ **Exceptional Groups:** Three-table design: 1. `exceptional_working_hours_groups` — group metadata 2. `exceptional_working_hours` — 7 days of hours per group -3. `exceptional_group_applications` — which weeks the group applies to (week_start = Monday) +3. `exceptional_group_applications` — which weeks the group applies to (`week_start` = Monday date). Column is `week_start` (not `monday_week_start`). **Working Hours Merge:** `GetWorkingHours` loads default hours, overlays exceptional groups for applicable weeks. Returns `DayWorkingHours[]` with `source` field ("default" or "exceptional"). diff --git a/obsidian/Crussell/Testing Architecture & DB Management.md b/obsidian/Crussell/Testing Architecture & DB Management.md index 97b8b26..53dbadc 100644 --- a/obsidian/Crussell/Testing Architecture & DB Management.md +++ b/obsidian/Crussell/Testing Architecture & DB Management.md @@ -168,7 +168,7 @@ When implementing new features or endpoints, follow this checklist to implement ### Checklist for Adding New Tests: -- [ ] **File Location:** Put test files in the same directory as the handlers they test, named `*_test.go`. +- [ ] **File Location:** Put test files in the same directory as the handlers they test, named `*_test.go`. Tests for admin-facing handlers may go in `backend/handlers/admin/` (see existing patterns in `today_test.go` and `bookings_test.go`). - [ ] **Tags:** Ensure the top of the file has the `//go:build test` constraint. - [ ] **Reset State:** Always call `resetTestData(t)` at the very beginning of your test: ```go @@ -181,6 +181,79 @@ When implementing new features or endpoints, follow this checklist to implement - [ ] **Close Rows & Transactions:** Never leak a connection! Always use `defer rows.Close()` and commit or rollback transactions (`defer tx.Rollback(ctx)`). Leaked transactions will cause subsequent `TRUNCATE` operations in other tests to freeze. - [ ] **Use Mock Clients:** If your endpoint calls external services like Square, ensure the test environment uses the in-memory mock client (`square.NewDevClient()`). +### Seeding Working Hours for Specific Days + +Tests involving day-of-week logic (closed days, week summaries) must seed `working_hours` explicitly. Use the weekday mapping (0=Monday, 6=Sunday — already converted from Go's `time.Weekday`): + +```go +todayWeekday := int(time.Now().Weekday()) +if todayWeekday == 0 { + todayWeekday = 6 // Go Sunday → our Sunday +} else { + todayWeekday -= 1 // Go Mon=1→0, Tue=2→1, ..., Sat=6→5 +} + +// Seed today as closed +_, err := db.DB.Exec(ctx, ` + INSERT INTO working_hours (weekday, start_time, end_time, is_open) + VALUES ($1, '00:00', '00:00', false) + ON CONFLICT (weekday) DO UPDATE SET ... +`, todayWeekday) +``` + +### Seeding Exceptional Hours (Holidays, Reduced Hours) + +For tests that need exceptional working hours (overriding default `working_hours`), create a group, hours, and application: + +```go +// 1. Create the group +var groupID int +err := db.DB.QueryRow(ctx, ` + INSERT INTO exceptional_working_hours_groups (name, description) + VALUES ('Test Closure', '...') RETURNING id +`).Scan(&groupID) + +// 2. Define the exceptional day (weekday 0=Monday, 6=Sunday) +_, err = db.DB.Exec(ctx, ` + INSERT INTO exceptional_working_hours (group_id, weekday, start_time, end_time, is_open) + VALUES ($1, $2, '00:00', '00:00', false) +`, groupID, todayWeekday) + +// 3. Apply to the current week (week_start must be the Monday of the week) +_, err = db.DB.Exec(ctx, ` + INSERT INTO exceptional_group_applications (group_id, week_start) + VALUES ($1, $2::date) +`, groupID, mondayStr) +``` + +The `isDayOpen()`, `getClosingTime()`, and `findWeekSummaryRange()` functions in the today handler query `exceptional_working_hours` first and fall back to `working_hours` only if no match is found. The column in `exceptional_group_applications` is `week_start` (not `monday_week_start`). + +### Testing Pagination / per_page Caps + +If you modify pagination parameters (like the `per_page` cap in `parseGetAllBookingsRequest`), test that: +- Values within the cap are accepted and return the expected number of results +- Values exceeding the cap are rejected and fall back to the default (10) + +```go +// per_page=500 should be accepted +w := makeAdminRequest(handler, "GET", "/api/admin/bookings?per_page=500", nil) +var resp bookings.BookingListResponse +parseResponseBody(w, &resp) +assert(resp.PerPage == 500) + +// per_page=600 should be rejected → defaults to 10 +w2 := makeAdminRequest(handler, "GET", "/api/admin/bookings?per_page=600", nil) +parseResponseBody(w2, &resp) +assert(resp.PerPage == 10) +``` + +### Current Test Suite Status + +As of June 2026: +- **Tests run:** `go test -tags "test,dev" -p 1 -count=1 ./...` +- **Results:** 686 passed, 3 skipped, 0 failed (689 total) +- **Skipped tests:** `TestAdminNotifications_List` and `TestAdminNotifications_Acknowledge` — marked WIP, no handler yet + --- ## 5. FAQ & Common Breakage Causes (For Developers & AI Agents)