Rebrand from Forte (music-store-specific) to LunarFront (any small business): - Package namespace @forte/* → @lunarfront/* - Database forte/forte_test → lunarfront/lunarfront_test - Docker containers, volumes, connection strings - UI branding, localStorage keys, test emails - All documentation and planning docs Generalize music-specific terminology: - instrumentDescription → itemDescription - instrumentCount → itemCount - instrumentType → itemCategory (on service templates) - New migration 0027_generalize_terminology for column renames - Seed data updated with generic examples - RBAC descriptions updated
625 lines
32 KiB
Markdown
625 lines
32 KiB
Markdown
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)
|