LunarFront — Small Business Management Platform Implementation Roadmap Version 1.0 | Draft # 1. Purpose This document defines the phased implementation order for LunarFront — a music store management platform built by Lunarfront Tech LLC. Each phase builds on the previous, produces independently testable output, and is scoped for a solo developer working in 2-4 week increments. The goal is a working POS as early as possible, then layer on domain complexity. Tech stack: TypeScript / Bun / Fastify / Drizzle ORM / Zod / BullMQ / PostgreSQL 16 / Valkey 8 / Turborepo monorepo. See `17_Backend_Technical_Architecture.md` for full stack details. ## 1.1 Project Conventions Name | Value App name | LunarFront Package namespace | `@lunarfront/shared`, `@lunarfront/backend`, etc. Database (dev) | `forte` Database (test) | `lunarfront_test` Logging | JSON structured logging via Pino (Fastify built-in) Linting | ESLint + Prettier at monorepo root Auth (Phase 1-2) | Dev bypass via `X-Dev-User` header — JWT planned, wired in Phase 2 Auth (Phase 2+) | Self-issued JWTs + bcrypt, swap to Clerk/Auth0 later Request tracing | Auto-generated request IDs via Fastify (included from Phase 1) Test strategy | Separate `lunarfront_test` database, reset between test runs Dev ports | API: 8000, Postgres: 5432, Valkey: 6379 (all exposed to host for dev tooling) Multi-tenant | `company_id` on all tables for tenant scoping, `location_id` where per-location tracking needed (inventory, transactions, drawer, delivery) # 2. Phase Summary Phase | Name | Dependencies | MVP? 1 | Monorepo Scaffold, Database, Dev Environment | None | Yes 2 | Accounts, Inventory, Auth | Phase 1 | Yes 3 | POS Transactions & Cash Drawer | Phase 2 | Yes 4 | Stripe Integration (Card Payments) | Phase 3 | Yes 5 | Desktop App Shell (Electron) | Phase 4 | Yes 6 | Rentals | Phase 4 | Yes 7 | Lessons | Phase 4 | No 8 | Repairs | Phase 4 | No 9 | Accounting & QuickBooks Export | Phases 6, 7, 8 | No 10 | Licensing & Module Enforcement | Phase 9 | No 11 | Admin Panel (Web) | Phase 10 | No 12 | Customer Portal (Web) | Phase 10 | No 13 | Batch Repairs & Delivery | Phase 8 | No 14 | Advanced Billing | Phases 6, 7 | No 15 | Mobile App (iOS) | Phase 4 | No 16 | Self-Hosted Installer | Phase 10 | No 17 | AIM Migration | Phase 2 | No 18 | Personnel (Time Clock, Scheduling, Time Off) | Phase 2 | No MVP for beta store: Phases 1–6 (scaffold through rentals + desktop app). Phases 5–8 are largely independent after Phase 4 and can be interleaved. Phases 11–13 are also independent. Phase 18 (Personnel) can be built any time after Phase 2 — it only needs employee/user tables. # 3. Phase Details ## 3.1 Phase 1 — Monorepo Scaffold, Database, Dev Environment Reference docs: `01_Overall_Architecture.md`, `17_Backend_Technical_Architecture.md` ### Goal A running Turborepo monorepo with Docker Compose dev environment, Fastify server connected to Postgres and Redis, a health check endpoint, and the shared package exporting its first types. ### Deliverables Area | Files / Artifacts Root config | `turbo.json`, root `package.json` (workspaces), `tsconfig.base.json`, `.env.example`, `CLAUDE.md` Linting | `.eslintrc.cjs`, `.prettierrc`, root lint/format scripts in Turborepo pipeline Docker | `docker-compose.dev.yml` — PostgreSQL 16 (`forte` database) + Valkey 8, ports exposed to host Shared package | `packages/shared/package.json` (`@lunarfront/shared`), `packages/shared/src/types/index.ts`, `packages/shared/src/schemas/index.ts`, `packages/shared/src/utils/currency.ts`, `packages/shared/src/utils/dates.ts` Backend package | `packages/backend/package.json` (`@lunarfront/backend`), `packages/backend/src/main.ts` (Fastify entry with Pino JSON logging + request ID tracing), `packages/backend/src/plugins/database.ts` (Drizzle connection), `packages/backend/src/plugins/redis.ts`, `packages/backend/src/plugins/error-handler.ts`, `packages/backend/src/plugins/cors.ts`, `packages/backend/src/plugins/dev-auth.ts` (dev bypass via `X-Dev-User` header), `packages/backend/src/routes/v1/health.ts` Database | `packages/backend/src/db/index.ts` (client export), `packages/backend/src/db/schema/companies.ts` (company table — the tenant anchor), `packages/backend/src/db/schema/locations.ts` (location table — physical store locations), `drizzle.config.ts` Seed script | `packages/backend/src/db/seed.ts` — creates a test company + location + admin user for local dev Testing | `vitest.config.ts`, health endpoint integration test, `lunarfront_test` database for test isolation ### Architecture Decisions Settled - `company_id` (tenant) + `location_id` (physical store) scoping pattern established on the first domain tables - Inventory and transaction tables use both `company_id` and `location_id`; other tables use `company_id` only - Drizzle migration workflow (generate → migrate) - Shared package import convention (`@lunarfront/shared`) - Standardized error response format (consistent JSON shape for errors) - Request context pattern (`companyId`, `locationId`, and `user` on request) - JSON structured logging via Pino with request ID on every log line - Dev auth bypass (`X-Dev-User` header) — replaced by real JWT auth in Phase 2 ### Testable Outcome `bun run dev` starts Fastify on port 8000. `GET /v1/health` returns `{ status: "ok", db: "connected", redis: "connected" }`. ## 3.2 Phase 2 — Accounts, Inventory, Auth Reference docs: `02_Domain_Accounts_Customers.md`, `03_Domain_Inventory.md` (sale inventory only) ### Goal The foundational domain entities that every other feature depends on — accounts, members, products, inventory units, categories, suppliers. Plus authentication so routes are protected. ### Deliverables Area | Files / Artifacts Auth plugin | `packages/backend/src/plugins/auth.ts` — self-issued JWTs + bcrypt (swap to Clerk/Auth0 later) User/Employee schema | `packages/backend/src/db/schema/users.ts` — `user` table with `company_id`, role enum (admin, manager, staff, technician, instructor), hashed password Account domain | `packages/backend/src/db/schema/accounts.ts` — `account`, `member`, `account_payment_method` tables Account routes | `packages/backend/src/routes/v1/accounts.ts` — CRUD + search (name, phone, email, account_number) Account service | `packages/backend/src/services/account.service.ts` Inventory domain | `packages/backend/src/db/schema/inventory.ts` — `product`, `inventory_unit`, `category` tables Inventory routes | `packages/backend/src/routes/v1/inventory.ts` — CRUD, search by SKU/UPC/name, stock level queries Inventory service | `packages/backend/src/services/inventory.service.ts` Supplier schema | `packages/backend/src/db/schema/suppliers.ts` — `supplier` table Shared types/schemas | `packages/shared/src/types/account.ts`, `packages/shared/src/schemas/account.schema.ts`, `packages/shared/src/types/inventory.ts`, `packages/shared/src/schemas/inventory.schema.ts` ### Business Rules Enforced - Every query scoped by `company_id`; inventory queries also scoped by `location_id` - Account soft-delete only — financial history must be retained - Duplicate account detection on email and phone during creation - Product `is_serialized` flag controls whether `inventory_unit` records are required - Legacy fields (`legacy_id`, `legacy_source`, `migrated_at`) present on all domain tables from day one ### Testable Outcome Full CRUD on accounts, members, products, and inventory via authenticated API calls. Account search by multiple fields works. ## 3.3 Phase 3 — POS Transactions & Cash Drawer Reference docs: `07_Domain_Sales_POS.md` ### Goal A working POS transaction loop — create a transaction, add line items, apply discounts, calculate tax, complete with cash payment. Cash drawer open/close with over/short tracking. This is the minimum viable POS. ### Deliverables Area | Files / Artifacts Transaction domain | `packages/backend/src/db/schema/transactions.ts` — `transaction` (with `company_id` + `location_id`), `transaction_line_item`, `discount`, `discount_audit` tables Transaction routes | `packages/backend/src/routes/v1/transactions.ts` — create, add line items, apply discount, complete, void, refund Transaction service | `packages/backend/src/services/transaction.service.ts` — tax calculation, discount application, inventory decrement on completion Cash drawer | `packages/backend/src/db/schema/drawer.ts` — `drawer_session` table (open, close, denominations, over/short) Drawer routes | `packages/backend/src/routes/v1/drawer.ts` Shared types/schemas | `packages/shared/src/types/payment.ts`, `packages/shared/src/schemas/transaction.schema.ts` ### Business Rules Enforced - Discounts above manager threshold require approval — logged in `discount_audit` - Manual price overrides treated as discounts with reason code - Inventory `qty_on_hand` decremented on sale (non-serialized) or status changed to `sold` (serialized) - Transaction lifecycle: `pending` → `completed` / `voided` - Refund creates a new transaction of type `refund` linked to original - Discount audit records are immutable ### Testable Outcome API-driven POS flow: open drawer, create transaction, add line items from inventory, apply discount, complete with cash, close drawer with denomination count, see over/short. Inventory quantities updated. ## 3.4 Phase 4 — Stripe Integration (Card Payments) Reference docs: `08_Domain_Payments_Billing.md`, `07_Domain_Sales_POS.md` ### Goal Card-present and card-keyed payments via Stripe Terminal and Stripe Elements. Webhook handling for payment confirmation. This turns the cash-only POS into a real POS. ### Deliverables Area | Files / Artifacts Payment provider interface | `packages/backend/src/payment-providers/base.ts` — `PaymentProvider` interface (supports future Global Payments addition) Stripe provider | `packages/backend/src/payment-providers/stripe.ts` — charge, refund, terminal reader discovery, terminal payment collection, setup session Provider factory | `packages/backend/src/payment-providers/factory.ts` Webhook handler | `packages/backend/src/routes/v1/webhooks.ts` — `payment_intent.succeeded`, `payment_intent.payment_failed` Webhook storage | `packages/backend/src/db/schema/webhooks.ts` — `stripe_webhook_event` table (store-before-process pattern) Stripe customer sync | Account creation triggers Stripe Customer creation, `stripe_customer_id` stored on account ### Architecture Notes The `PaymentProvider` interface is defined now even though only Stripe is implemented. Global Payments (`PAY-GP`) can be added later without changing business logic. Critical design difference: Stripe manages recurring billing via its Subscriptions API (provider-managed). Global Payments only provides tokenized charge capability — the platform must own the billing schedule via BullMQ cron jobs (platform-managed). The interface exposes `managedSubscriptions: boolean` so the billing service knows whether to delegate or self-manage. For GP stores, the platform also handles proration calculations internally, whereas Stripe calculates proration automatically. See `08_Domain_Payments_Billing.md` section 6 for full details. ### Testable Outcome Create a transaction, complete with Stripe Terminal (test mode reader) or Stripe Elements. Webhook confirms payment. Transaction marked completed. Refund via Stripe works. ## 3.5 Phase 5 — Desktop App Shell (Electron) Reference docs: `01_Overall_Architecture.md`, `17_Backend_Technical_Architecture.md` ### Goal An Electron app that runs the POS. Not feature-complete — just the shell with navigation, auth login, and the POS transaction screen wired to the API. This is the first time a human can use the system. ### Deliverables Area | Files / Artifacts Desktop package | `packages/desktop/package.json` — Electron + React + Vite App shell | Main window, menu bar, navigation sidebar Auth screen | Login form, JWT storage, auto-refresh POS screen | Product search/scan, cart, discount application, payment method selection (cash/card), complete transaction Customer lookup | Search by name/phone/email, attach account to transaction Inventory browse | Product list, stock levels, basic filters API client | Shared API client layer (`packages/shared/src/api/client.ts` or generated via `tools/codegen`) — reused by all React apps ### Testable Outcome A human can log in, search inventory, ring up a sale, take cash or card payment, and see the transaction complete. ## 3.6 Phase 6 — Rentals Reference docs: `04_Domain_Rentals.md`, `08_Domain_Payments_Billing.md`, `11_Domain_Billing_Date_Management.md` ### Goal Full rental lifecycle — create contracts (all four types), track rental fleet, handle deposits, process returns. Recurring billing via Stripe Subscriptions. Rent-to-own equity tracking and buyout. ### Deliverables Area | Files / Artifacts Rental domain | `packages/backend/src/db/schema/rentals.ts` — `rental`, `rental_payment` tables Rental routes | `packages/backend/src/routes/v1/rentals.ts` — create, return, buyout, list by account Rental service | `packages/backend/src/services/rental.service.ts` Billing service | `packages/backend/src/services/billing.service.ts` — Stripe subscription create, add/remove line items, billing group consolidation RTO business logic | `packages/shared/src/business-logic/rto.ts` — equity calculation, buyout amount Webhook additions | `invoice.paid`, `invoice.payment_failed`, `customer.subscription.deleted` handlers Billing anchor | `billing_anchor_day` column on rental, capped at 28, change audit log Desktop UI | Rental intake form, rental list, return workflow, RTO buyout screen ### Business Rules Enforced - Billing group consolidation vs split billing - RTO equity accumulation per payment - Deposit collected at start (tracked as liability) - Return updates `inventory_unit` status to `available` or `in_repair` - Mid-cycle rental add prorates via Stripe - Billing anchor day requests for 29th/30th/31st capped to 28th ### Testable Outcome Create a rental, see Stripe subscription created (test mode). Simulate payment via webhook. See equity accumulate on RTO. Process return. Process buyout. ## 3.7 Phase 7 — Lessons Reference docs: `05_Domain_Lessons.md` ### Goal Instructor management, scheduling, enrollments, attendance tracking, makeup credits. Recurring billing via Stripe Subscriptions. Session notes, homework, grading scales, and lesson plans. ### Deliverables Area | Files / Artifacts Lesson domain | `packages/backend/src/db/schema/lessons.ts` — `instructor`, `lesson_type`, `schedule_slot`, `enrollment`, `lesson_session` tables Grading | `grading_scale`, `grading_scale_level` tables + default seed data (Standard Letter, Numeric 1-10, ABRSM Style, Progress, Concert Readiness, Simple) Lesson plans | `member_lesson_plan`, `lesson_plan_section`, `lesson_plan_item`, `lesson_plan_item_grade_history`, `lesson_session_plan_item` tables Templates | `lesson_plan_template`, `lesson_plan_template_section`, `lesson_plan_template_item` tables Lesson routes | `packages/backend/src/routes/v1/lessons.ts` — enrollment CRUD, session CRUD, attendance, lesson plan CRUD, grading Lesson service | `packages/backend/src/services/lesson.service.ts` Billing | Enrollment creates Stripe subscription (or adds line item to existing) — reuses billing service from Phase 6 Desktop UI | Schedule view, enrollment form, attendance marking, post-lesson notes form, lesson plan editor ### Business Rules Enforced - No double-booking (student or instructor at same day/time) - Group lesson `max_students` cap on `schedule_slot` - Makeup credits linked to original missed session via `makeup_for_session_id` - Only one active lesson plan per enrollment at a time - `instructor_notes` never exposed to student/parent API routes - Scale levels used in grade history cannot be deleted — only deactivated - Skipped items excluded from progress percentage ### Testable Outcome Create instructor, define schedule, enroll student, mark attendance, record session notes with grades, see lesson plan progress percentage. ## 3.8 Phase 8 — Repairs Reference docs: `06_Domain_Repairs.md`, `03_Domain_Inventory.md` (repair parts sections) ### Goal Full individual repair ticket lifecycle from intake through pickup/payment. Parts logging, labor tracking, flat-rate services. Repair parts inventory separate from sale inventory. ### Deliverables Area | Files / Artifacts Repair domain | `packages/backend/src/db/schema/repairs.ts` — `repair_ticket`, `repair_line_item` tables Repair parts | `packages/backend/src/db/schema/repair-parts.ts` — `repair_part`, `repair_part_usage_template`, `repair_part_usage` tables Repair routes | `packages/backend/src/routes/v1/repairs.ts` — full lifecycle CRUD, parts logging, estimate generation Repair service | `packages/backend/src/services/repair.service.ts` Repair payment | Payment at pickup integrates with transaction system (Phase 3) Seed data | Default bow hair usage templates (full size, cello, bass, fractional sizes) Desktop UI | Repair intake form, technician work screen with parts logging, repair queue/list, pickup/payment flow ### Business Rules Enforced - Status lifecycle with required transitions (intake → diagnosing → pending_approval → approved → in_progress → ready → picked_up) - Estimate approval required before work begins (waivable with manager override) - Parts cost recorded at time of use — historical accuracy preserved - Technician cannot log more parts than `qty_on_hand` - Dual-use parts decrement sale inventory `qty_on_hand` - Shop supply parts tracked for cost but never appear on customer invoice - Flat-rate services bundle labor + material into single invoice line item - Actual cost variance from estimate requires reason code in audit trail ### Testable Outcome Create repair ticket, log labor and parts, generate estimate, approve, mark complete, collect payment at pickup. Repair parts inventory decremented. ## 3.9 Phase 9 — Accounting & QuickBooks Export Reference docs: `12_Domain_Accounting_Journal_Entries.md` ### Goal Automatic double-entry journal entry generation for every financial event. Store-configurable chart of accounts matching QuickBooks. CSV export for QB import. In-platform financial reports. ### Deliverables Area | Files / Artifacts Accounting domain | `packages/backend/src/db/schema/accounting.ts` — `account_code`, `journal_entry`, `journal_entry_line`, `export_batch` tables JE generation | `packages/backend/src/services/accounting.service.ts` — functions for each event type (cash sale, card sale, rental payment, RTO payment/buyout, lesson payment, repair payment, deposits, refunds, discounts, drawer variance, proration, inventory purchase, bad debt write-off) JE rules | `packages/shared/src/business-logic/accounting.ts` — journal entry mapping rules (which accounts debit/credit per event) Export | `packages/backend/src/routes/v1/accounting.ts` — export by date range (CSV), mark exported, reconciliation confirmation Seed data | Default chart of accounts (assets 1000–1320, liabilities 2000–2400, revenue 4000–4500, contra 4900–4920, COGS 5000–5100, expenses 6000–6300) Reports | Revenue summary, gross vs net, AR aging, deferred revenue, cash flow, Stripe clearing reconciliation, cash over/short history, COGS vs revenue ### Architecture Note This phase retrofits accounting hooks into all previously built financial flows. `AccountingService.record*()` calls are added to transaction, rental, lesson, and repair services. Deferred to this phase intentionally — operational flows must be correct before accounting hooks are layered on. ### Business Rules Enforced - `total_debits` must equal `total_credits` — entries that don't balance are rejected - Entry date cannot be future, and past > 30 days requires manager override - Voided entries generate reversing entries (equal and opposite) — never deleted - Exported entries excluded from subsequent exports unless manager override - Chart of accounts `quickbooks_account_name` must match QB exactly ### Testable Outcome Complete a sale, see journal entry generated automatically. Export date range as CSV. Verify debits equal credits on every entry. AR aging report shows outstanding balances. ## 3.10 Phase 10 — Licensing & Module Enforcement Reference docs: `15_Licensing_Modules_Pricing.md` ### Goal Ed25519 signed license files control which modules are active. API route guards block unlicensed routes. UI shows upgrade prompts for locked modules. Trial licenses enable 30-day evaluation. ### Deliverables - `packages/backend/src/license/verify.ts` — Ed25519 signature verification, public key embedded at build time - `packages/backend/src/license/guard.ts` — `requireModule()` Fastify preHandler hook - `packages/backend/src/plugins/license.ts` — loads license on startup, caches in memory - `packages/shared/src/types/license.ts` — `License`, `LicenseModule` types - `packages/shared/src/constants/modules.ts` — `MODULE_IDS` enum (CORE, MOD-RENTALS, MOD-LESSONS, MOD-REPAIRS, etc.) - `packages/shared/src/business-logic/license.ts` — `hasModule()` function for frontend checks - Trial license API endpoint — generates 30-day trial for any unlicensed module - Shared React upgrade prompt component for unlicensed module screens ### Testable Outcome API route returns 403 with upgrade prompt when module not licensed. UI shows upgrade prompt instead of module content. Trial license enables module for 30 days. ## 3.11 Phase 11 — Admin Panel (Web) Reference docs: `01_Overall_Architecture.md` ### Deliverables - `packages/admin/` — React + Vite web app - User management — CRUD for employee accounts, role assignment - Store settings — store name, address, tax configuration, timezone, billing preferences - License management — upload license file, view active modules, start trials - Audit log viewer — searchable log of discount audits, billing changes, refunds, write-offs - Reporting dashboards — sales summary, rental/lesson enrollment counts, repair queue metrics - `packages/backend/src/routes/v1/admin.ts` — admin-only endpoints ## 3.12 Phase 12 — Customer Portal (Web) Reference docs: `05_Domain_Lessons.md` (parent portal sections) ### Deliverables - `packages/web-portal/` — React + Vite web app - Separate customer auth flow (email/password or magic link) — scoped to account data only - Lesson view — enrollment summary, upcoming lessons, session notes (student-facing only), homework, lesson plan progress, grade history - Repair status — ticket status tracking, estimated completion date - Billing — invoice history, payment history, online payment via Stripe Elements - `packages/backend/src/routes/v1/portal.ts` — customer-scoped endpoints (never expose instructor private notes) ## 3.13 Phase 13 — Batch Repairs & Delivery Reference docs: `09_Domain_Batch_Repairs.md`, `10_Domain_Delivery_Chain_of_Custody.md` ### Deliverables - `repair_batch` table — batch lifecycle (scheduled → picked_up → intake_complete → pending_approval → approved → in_progress → ready → delivered → invoiced → paid → closed) - Batch approval cascading to all child `repair_ticket` records - Batch invoicing — itemized per instrument with work, parts, labor - `delivery_event`, `delivery_event_item` tables — condition tracking at every handoff, signature capture (base64), photo URLs (S3) - School PO number on batch and invoice - Desktop UI for batch intake, delivery scheduling, batch invoice generation - Delivery completion auto-updates linked repair ticket and batch status ### Business Rules - Batch status auto-advances to `ready` when all child tickets reach `ready` or `cancelled` - Instrument count discrepancy flagged at pickup — requires staff acknowledgement - Missing instrument (`was_transferred = false`) triggers manager alert - Signature required for `store_pickup` and `store_delivery` events ## 3.14 Phase 14 — Advanced Billing Reference docs: `11_Domain_Billing_Date_Management.md` ### Deliverables - `billing_anchor_change_log` table — append-only audit - Billing anchor change routes — preview proration, execute change, bulk preview, bulk execute, history - Proration logic — earlier day (credit), later day (charge), consolidation, splitting - Edge cases — 48hr pending invoice window warning, failed payment blocks anchor change, paused subscription (no proration), RTO equity unaffected - Bulk change — grouped under `bulk_change_id`, sequential Stripe API calls, full rollback on partial failure - Bulk change preview screen showing per-subscription proration breakdown ## 3.15 Phase 15 — Mobile App (iOS) Reference docs: `01_Overall_Architecture.md` ### Deliverables - `packages/mobile/` — React Native iOS app - Convention POS — product search, cart, payment via Stripe Terminal Bluetooth reader - Customer lookup - Delivery workflow — pull up delivery event, check off instruments, capture condition/photos/signature - Offline queue — local SQLite for transactions when offline, sync on reconnect - Convention cash drawer (account 1010) ## 3.16 Phase 16 — Self-Hosted Installer Reference docs: `14_Self_Hosted_Installer_Platform_Compatibility.md`, `16_Windows_Installer_PowerShell.md` ### Deliverables - Production `docker-compose.yml` — api, postgres, valkey, admin (nginx), portal (nginx), nginx (reverse proxy), updater, backup services - `Dockerfile` — multi-stage build: compile backend with `bun build --compile`, copy binary to clean Ubuntu 24.04 image - `tools/installer/install.ps1` — phased PowerShell installer with Hyper-V/WSL2 detection, Docker install, resume-after-reboot via registry Run key, NSSM service registration - `tools/installer/tray-app/` — Go system tray app with health monitoring (green/yellow/orange/red), update checker, manual backup trigger, log viewer - Automated backup service — daily pg_dump, 30-day retention, integrity verification - Update registry API — `GET /check?version=X&license=Y` returns latest version info - macOS `.dmg` with menu bar app - Linux one-line install script with systemd service ## 3.17 Phase 17 — AIM Migration Reference docs: `01_Overall_Architecture.md` (migration sections), `08_Domain_Payments_Billing.md` (payment transition) ### Deliverables - `tools/migration/` — TypeScript ETL scripts connecting directly to AIM MSSQL database - Per-domain extraction scripts — accounts/customers, inventory, open rentals, active lessons, repair history - Data validation — dry-run mode, exception logging, manual review of edge cases - Duplicate detection — run before import, staff reviews conflicts - Legacy tagging — all migrated records tagged with `legacy_id`, `legacy_source = 'aim'`, `migrated_at` - Payment transition — `requires_payment_update = true` on migrated payment methods, staff prompted to collect new card at each renewal - Old processor wind-down — 120-day window after last transaction before closing old processor account ## 3.18 Phase 18 — Personnel (Time Clock, Scheduling, Time Off) Reference docs: `19_Domain_Personnel.md` ### Goal Employee records, time clock with location tracking, work schedules, time-off requests with approval workflow, overtime calculation, and payroll export. This is a licensed module (MOD-PERSONNEL). ### Deliverables - `employee`, `time_clock_entry`, `schedule`, `schedule_recurring_template`, `time_off_request`, `time_off_balance` tables - Employee CRUD with multi-role support (staff, technician, instructor, manager, admin) - Time clock routes — clock in/out, manager edit with audit trail - Schedule management — recurring templates, concrete entries, conflict detection - Time-off workflow — request, approve/deny, balance tracking, accrual - Overtime calculation based on configurable rules (weekly/daily thresholds) - Payroll CSV export for external payroll services - Integration hooks: instructor_id and technician_id reference employee table ### Business Rules - Employee can work at any location (not locked to one) - Clock entries cannot overlap — no simultaneous clock-ins - Auto clock-out at midnight if forgotten, flagged for manager review - Per-lesson instructors tracked per session, not via time clock - Terminated employees retain all historical records ### Testable Outcome Create employee, clock in/out at a location, create schedule, submit and approve time-off request, generate payroll export CSV. # 4. Dependency Map ``` P1 Scaffold → P2 Accounts/Inventory → P3 POS → P4 Stripe | | P18 +-------+-------+-------+ Personnel | | | | P5 P6 P7 P8 Desktop Rentals Lessons Repairs | | | | +-------+---+---+-------+ | P9 Accounting | P10 Licensing | +-------+---+---+-------+ | | | P11 P12 P13 Admin Portal Batch/Delivery | | | +-------+-------+-------+ | P14 Adv Billing | +-----+-----+ | | P15 P16 Mobile Installer | P17 Migration ``` # 5. MVP Definition The minimum viable product for a beta store deployment is Phases 1–6: - Monorepo + dev environment (Phase 1) - Accounts + inventory + auth (Phase 2) - POS with cash payments (Phase 3) - Card payments via Stripe (Phase 4) - Desktop Electron app (Phase 5) - Instrument rentals with recurring billing (Phase 6) This covers the most common daily operations — a store can use it alongside AIM for new transactions while legacy data remains in AIM. # 6. Verification Strategy After each phase: - Run `bun test` — all tests pass - `docker compose -f docker-compose.dev.yml up -d` — all services healthy - Hit API endpoints via curl or httpie to verify CRUD operations work - After Phase 5+: manual testing through desktop app - After Phase 9: verify every journal entry has `total_debits = total_credits` - After Phase 16: clean install on a fresh Windows machine, platform running within 30 minutes # 7. Cross-Cutting Concerns Not Yet Addressed The following items are referenced across multiple planning documents but do not have dedicated domain docs. They should be designed before or during their dependent phase: Concern | Needed By | Notes Tax configuration | Phase 3 | Sales tax collected at POS but no tax rate config, jurisdiction handling, or TaxJar/Avalara integration designed Notification system | Phase 6 | Repairs, billing, and lessons reference "notify customer" but no channel definitions, preferences schema, or template system exists Offline sync strategy | Phase 15 | iOS app and desktop both reference offline queuing but conflict resolution is not designed Search/indexing | Phase 2 | Account/customer lookup described but no full-text search, trigram indexes, or search infrastructure specified Cash drawer schema | Phase 3 | Mentioned in POS and accounting docs but no `drawer_session` table defined in any planning doc Frontend themes | Phase 5 | All frontend apps should support themes with per-user preference storage ### Resolved - Employee/user table schema → now covered by `19_Domain_Personnel.md` (Phase 18) - `store` entity ambiguity → resolved as `company` (tenant) + `location` (physical store) - `student` entity naming → renamed to `member` to support multiple adults per account - Redis → replaced with Valkey 8 (Redis-compatible fork) - Payment provider billing ownership → documented in `08_Domain_Payments_Billing.md` section 6 (Stripe = provider-managed, GP = platform-managed)