Add planning documents for Forte music store platform
17 domain design docs covering architecture, accounts, inventory, rentals, lessons, repairs, POS, payments, batch repairs, delivery, billing, accounting, deployment, licensing, installer, and backend tech architecture. Plus implementation roadmap (doc 18) and personnel management (doc 19). Key design decisions documented: - company/location model (multi-tenant + multi-location) - member entity (renamed from student to support multiple adults) - Stripe vs Global Payments billing ownership differences - User/location/terminal licensing model - Valkey 8 instead of Redis
This commit is contained in:
BIN
planning/01_Overall_Architecture.docx
Normal file
BIN
planning/01_Overall_Architecture.docx
Normal file
Binary file not shown.
267
planning/01_Overall_Architecture.md
Normal file
267
planning/01_Overall_Architecture.md
Normal file
@@ -0,0 +1,267 @@
|
||||
Music Store Management Platform
|
||||
|
||||
Overall System Architecture
|
||||
|
||||
Version 1.0 | Draft
|
||||
|
||||
|
||||
|
||||
# 1. Executive Summary
|
||||
|
||||
This document describes the overall architecture of the Music Store Management Platform — a purpose-built system to replace AIM (Tri-Tech) for independent music retailers. The platform handles point-of-sale, inventory, rentals, lessons, repairs, customer management, financial reporting, and payment processing through a modern cloud-native stack.
|
||||
|
||||
|
||||
|
||||
# 2. System Overview
|
||||
|
||||
## 2.1 Goals
|
||||
|
||||
- Replace legacy AIM/MSSQL system with a modern, maintainable platform
|
||||
|
||||
- Support in-store desktop operations and mobile field sales (conventions)
|
||||
|
||||
- Provide customer-facing web portal for billing and repair status
|
||||
|
||||
- Enable clean migration of historical AIM data
|
||||
|
||||
- Support future multi-tenant expansion without full rewrite
|
||||
|
||||
|
||||
|
||||
## 2.2 Application Inventory
|
||||
|
||||
App
|
||||
|
||||
Technology
|
||||
|
||||
Purpose
|
||||
|
||||
Desktop
|
||||
|
||||
Electron + React
|
||||
|
||||
Full store management — POS, inventory, repairs, rentals, lessons, reporting
|
||||
|
||||
Mobile (iOS)
|
||||
|
||||
React Native
|
||||
|
||||
Field sales at conventions — POS, customer lookup, Stripe Terminal BT
|
||||
|
||||
Customer Portal
|
||||
|
||||
React (web)
|
||||
|
||||
Self-service — lesson billing, repair status, invoice history
|
||||
|
||||
Admin Panel
|
||||
|
||||
React (web)
|
||||
|
||||
Internal ops — users, settings, audit logs, migration tools, no payments
|
||||
|
||||
Backend API
|
||||
|
||||
Node.js / Python
|
||||
|
||||
REST API on AWS EKS — all business logic, Stripe integration, PDF generation
|
||||
|
||||
|
||||
|
||||
## 2.3 Monorepo Structure
|
||||
|
||||
/packages /shared ← TypeScript types, API clients, business logic, pricing engine /desktop ← Electron app (Windows/Mac/Linux) /mobile ← React Native iOS app /web-portal ← Customer-facing React web app /admin ← Internal admin React web app /backend ← Node.js/Python API server /migration ← AIM MSSQL → Postgres ETL scripts
|
||||
|
||||
|
||||
|
||||
# 3. Infrastructure
|
||||
|
||||
## 3.1 AWS Services
|
||||
|
||||
Service
|
||||
|
||||
Usage
|
||||
|
||||
EKS
|
||||
|
||||
Kubernetes cluster hosting API, admin panel, customer portal
|
||||
|
||||
Aurora PostgreSQL
|
||||
|
||||
Primary database — all domain data, append-only event sourcing pattern
|
||||
|
||||
ElastiCache (Redis)
|
||||
|
||||
Session cache, job queues, rate limiting
|
||||
|
||||
S3
|
||||
|
||||
PDF storage (invoices, reports), database backup archival, static assets
|
||||
|
||||
Secrets Manager
|
||||
|
||||
Stripe API keys, DB credentials, JWT secrets
|
||||
|
||||
CloudWatch
|
||||
|
||||
Logging, metrics, alerting
|
||||
|
||||
|
||||
|
||||
## 3.2 Database Backup Strategy
|
||||
|
||||
- Aurora automated continuous backup — point-in-time recovery up to 35 days
|
||||
|
||||
- Scheduled daily snapshots exported to S3 (separate bucket, cross-region)
|
||||
|
||||
- Long-term archival snapshots retained 1 year for financial compliance
|
||||
|
||||
- Recovery runbook documented and tested quarterly
|
||||
|
||||
- RTO target: 2 hours | RPO target: 1 hour
|
||||
|
||||
|
||||
|
||||
# 4. Payment Architecture
|
||||
|
||||
## 4.1 Stripe Integration
|
||||
|
||||
Stripe is the sole payment processor. All payment flows are handled through Stripe. Card data never touches application servers.
|
||||
|
||||
|
||||
|
||||
Feature
|
||||
|
||||
Usage
|
||||
|
||||
Stripe Terminal
|
||||
|
||||
In-person payments — WiFi reader (desktop), Bluetooth reader (iOS/mobile)
|
||||
|
||||
Stripe Elements
|
||||
|
||||
Keyed card entry on desktop — PCI-safe, card data goes direct to Stripe
|
||||
|
||||
Subscriptions
|
||||
|
||||
Recurring billing for lessons and rentals — monthly, with line items per enrollment
|
||||
|
||||
Webhooks
|
||||
|
||||
Async payment events — invoice.paid, payment_failed, subscription updates
|
||||
|
||||
Card Updater
|
||||
|
||||
Automatic card number updates when banks reissue — reduces billing failures
|
||||
|
||||
Stripe Connect
|
||||
|
||||
Architecture-ready for future multi-tenant expansion (not built initially)
|
||||
|
||||
|
||||
|
||||
## 4.2 Billing Model
|
||||
|
||||
- Accounts are the billing entity — one Stripe customer per account
|
||||
|
||||
- Students and rentals link to an account — family billing supported
|
||||
|
||||
- Subscriptions support multiple line items — consolidated billing option
|
||||
|
||||
- Split billing supported — separate subscriptions per enrollment if preferred
|
||||
|
||||
- Billing groups control consolidation — enrollments with same group_id share a subscription
|
||||
|
||||
|
||||
|
||||
# 5. Authentication & Authorization
|
||||
|
||||
- Auth provider: Clerk or Auth0 — handles user management, MFA, SSO
|
||||
|
||||
- Role-based access control: Admin, Manager, Technician, Instructor, Staff
|
||||
|
||||
- Customer portal uses separate auth flow (email/password or magic link)
|
||||
|
||||
- All API routes require JWT — validated on every request
|
||||
|
||||
- Sensitive operations (large discounts, refunds, write-offs) require manager role
|
||||
|
||||
|
||||
|
||||
# 6. Cross-Cutting Concerns
|
||||
|
||||
## 6.1 Audit Trail
|
||||
|
||||
Every discount, adjustment, price override, refund, and write-off is logged with: who, when, original value, new value, reason code, and whether approval was required. Audit records are append-only and cannot be deleted.
|
||||
|
||||
|
||||
|
||||
## 6.2 Multi-Tenant Readiness
|
||||
|
||||
All domain tables include a company_id column from day one — this is the tenant-scoping key. Tables dealing with physical inventory, transactions, cash drawers, and delivery also include a location_id column to identify the specific physical location. Business logic enforces company_id scoping on all queries. Stripe Connect architecture is documented but not implemented initially. Adding a second tenant is a configuration and billing exercise, not a rewrite.
|
||||
|
||||
|
||||
|
||||
## 6.3 Offline Support
|
||||
|
||||
The iOS mobile app supports offline mode for convention use. Transactions are queued locally and synced when connectivity is restored. The desktop app assumes reliable store network connectivity.
|
||||
|
||||
|
||||
|
||||
# 7. AIM Migration Strategy
|
||||
|
||||
## 7.1 Source System
|
||||
|
||||
AIM by Tri-Tech — Windows desktop POS running against a MSSQL (SQL Server Express) database on the store's local network. The application runs from a network share. Migration connects directly to MSSQL — AIM application is not involved.
|
||||
|
||||
|
||||
|
||||
## 7.2 Migration Phases
|
||||
|
||||
Phase
|
||||
|
||||
Scope
|
||||
|
||||
Notes
|
||||
|
||||
1
|
||||
|
||||
Schema exploration
|
||||
|
||||
Connect to AIM MSSQL, map tables to new domain model
|
||||
|
||||
2
|
||||
|
||||
ETL development
|
||||
|
||||
Python scripts: read AIM → transform → load Postgres staging
|
||||
|
||||
3
|
||||
|
||||
Data validation
|
||||
|
||||
Dry runs, exception logging, manual review of edge cases
|
||||
|
||||
4
|
||||
|
||||
Parallel operation
|
||||
|
||||
New system live for new customers, AIM continues for existing
|
||||
|
||||
5
|
||||
|
||||
Customer migration
|
||||
|
||||
Re-enroll active rentals/lessons in Stripe at renewal points
|
||||
|
||||
6
|
||||
|
||||
AIM wind-down
|
||||
|
||||
All recurring billing migrated, AIM read-only, close after chargeback window
|
||||
|
||||
|
||||
|
||||
## 7.3 Legacy Data Tagging
|
||||
|
||||
All migrated records include: legacy_id (AIM's original ID), legacy_source ('aim'), migrated_at timestamp, and requires_payment_update flag for records still on old processor.
|
||||
BIN
planning/02_Domain_Accounts_Customers.docx
Normal file
BIN
planning/02_Domain_Accounts_Customers.docx
Normal file
Binary file not shown.
359
planning/02_Domain_Accounts_Customers.md
Normal file
359
planning/02_Domain_Accounts_Customers.md
Normal file
@@ -0,0 +1,359 @@
|
||||
Music Store Management Platform
|
||||
|
||||
Domain Design: Accounts & Customers
|
||||
|
||||
|
||||
|
||||
# 1. Overview
|
||||
|
||||
The Accounts & Customers domain is the foundational entity model for the platform. All billing, lessons, rentals, repairs, and sales link back to an Account. This domain handles the distinction between the billing entity (Account) and the individual people (Students/Contacts) associated with it.
|
||||
|
||||
|
||||
|
||||
# 2. Core Concepts
|
||||
|
||||
## 2.1 Account
|
||||
|
||||
An Account is the billing entity. It holds the Stripe customer reference and payment preferences. An account may represent a single adult customer, a parent/guardian paying for children, or a business customer.
|
||||
|
||||
- One Stripe customer ID per account
|
||||
|
||||
- Holds billing preferences (consolidated vs split)
|
||||
|
||||
- Has one or more associated members
|
||||
|
||||
- All invoices and statements roll up to the account level
|
||||
|
||||
|
||||
|
||||
## 2.2 Member
|
||||
|
||||
A Member is an individual person associated with an account — adults, children, or anyone who takes lessons, rents instruments, or needs to be tracked under the account. Multiple members per account supports family billing (e.g. two parents and three children all on one account). For a single adult managing their own billing, the account holder and member are the same person.
|
||||
|
||||
- Linked to account for billing
|
||||
|
||||
- Has their own lesson enrollments, rental history, and repair history
|
||||
|
||||
- Multiple members per account (family billing — any mix of adults and minors)
|
||||
|
||||
- `is_minor` flag derived from `date_of_birth` — controls whether parent/guardian consent and portal access rules apply
|
||||
|
||||
- Each member can have their own email and phone — useful for adult members receiving their own notifications
|
||||
|
||||
|
||||
|
||||
# 3. Database Schema
|
||||
|
||||
## 3.1 account
|
||||
|
||||
Column
|
||||
|
||||
Type
|
||||
|
||||
Notes
|
||||
|
||||
id
|
||||
|
||||
uuid PK
|
||||
|
||||
Primary key
|
||||
|
||||
company_id
|
||||
|
||||
uuid FK
|
||||
|
||||
Tenant scoping — identifies which company owns this record
|
||||
|
||||
account_number
|
||||
|
||||
varchar
|
||||
|
||||
Human-readable account ID
|
||||
|
||||
name
|
||||
|
||||
varchar
|
||||
|
||||
Account/business name
|
||||
|
||||
email
|
||||
|
||||
varchar
|
||||
|
||||
Primary contact email
|
||||
|
||||
phone
|
||||
|
||||
varchar
|
||||
|
||||
Primary phone
|
||||
|
||||
address
|
||||
|
||||
jsonb
|
||||
|
||||
Street, city, state, zip
|
||||
|
||||
stripe_customer_id
|
||||
|
||||
varchar
|
||||
|
||||
Stripe customer reference
|
||||
|
||||
billing_mode
|
||||
|
||||
enum
|
||||
|
||||
consolidated | split
|
||||
|
||||
notes
|
||||
|
||||
text
|
||||
|
||||
Internal staff notes
|
||||
|
||||
legacy_id
|
||||
|
||||
varchar
|
||||
|
||||
AIM original customer ID
|
||||
|
||||
legacy_source
|
||||
|
||||
varchar
|
||||
|
||||
'aim' for migrated records
|
||||
|
||||
migrated_at
|
||||
|
||||
timestamptz
|
||||
|
||||
When record was imported
|
||||
|
||||
created_at
|
||||
|
||||
timestamptz
|
||||
|
||||
Record creation timestamp
|
||||
|
||||
updated_at
|
||||
|
||||
timestamptz
|
||||
|
||||
Last update timestamp
|
||||
|
||||
|
||||
|
||||
## 3.2 member
|
||||
|
||||
Column
|
||||
|
||||
Type
|
||||
|
||||
Notes
|
||||
|
||||
id
|
||||
|
||||
uuid PK
|
||||
|
||||
Primary key
|
||||
|
||||
account_id
|
||||
|
||||
uuid FK
|
||||
|
||||
Billing account
|
||||
|
||||
company_id
|
||||
|
||||
uuid FK
|
||||
|
||||
Tenant scoping — identifies which company owns this record
|
||||
|
||||
first_name
|
||||
|
||||
varchar
|
||||
|
||||
|
||||
|
||||
last_name
|
||||
|
||||
varchar
|
||||
|
||||
|
||||
|
||||
date_of_birth
|
||||
|
||||
date
|
||||
|
||||
Used to derive is_minor status
|
||||
|
||||
is_minor
|
||||
|
||||
boolean
|
||||
|
||||
True if under 18 — derived from date_of_birth, controls consent and portal access rules
|
||||
|
||||
email
|
||||
|
||||
varchar
|
||||
|
||||
Member's own email — each adult can have their own
|
||||
|
||||
phone
|
||||
|
||||
varchar
|
||||
|
||||
Member's contact number
|
||||
|
||||
notes
|
||||
|
||||
text
|
||||
|
||||
Staff notes
|
||||
|
||||
legacy_id
|
||||
|
||||
varchar
|
||||
|
||||
AIM original ID
|
||||
|
||||
created_at
|
||||
|
||||
timestamptz
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 3.3 account_payment_method
|
||||
|
||||
Tracks payment methods on file. The actual card data lives in Stripe — this table only stores references.
|
||||
|
||||
Column
|
||||
|
||||
Type
|
||||
|
||||
Notes
|
||||
|
||||
id
|
||||
|
||||
uuid PK
|
||||
|
||||
|
||||
|
||||
account_id
|
||||
|
||||
uuid FK
|
||||
|
||||
|
||||
|
||||
processor
|
||||
|
||||
enum
|
||||
|
||||
stripe | legacy
|
||||
|
||||
stripe_payment_method_id
|
||||
|
||||
varchar
|
||||
|
||||
pm_xxx reference
|
||||
|
||||
card_brand
|
||||
|
||||
varchar
|
||||
|
||||
visa, mastercard, etc.
|
||||
|
||||
last_four
|
||||
|
||||
char(4)
|
||||
|
||||
Display only
|
||||
|
||||
exp_month
|
||||
|
||||
integer
|
||||
|
||||
|
||||
|
||||
exp_year
|
||||
|
||||
integer
|
||||
|
||||
|
||||
|
||||
is_default
|
||||
|
||||
boolean
|
||||
|
||||
Default payment method for account
|
||||
|
||||
requires_update
|
||||
|
||||
boolean
|
||||
|
||||
True if still on legacy processor
|
||||
|
||||
created_at
|
||||
|
||||
timestamptz
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# 4. Business Rules
|
||||
|
||||
- Every member must belong to exactly one account
|
||||
|
||||
- An account must have at least one member (can be the account holder themselves)
|
||||
|
||||
- Billing always targets the account, never the member directly
|
||||
|
||||
- An account can have multiple payment methods but only one default
|
||||
|
||||
- Consolidated billing: all active subscriptions on one account roll to one Stripe customer
|
||||
|
||||
- Split billing: each enrollment/rental can have its own billing date and subscription
|
||||
|
||||
- Account deletion is soft-delete only — financial history must be retained
|
||||
|
||||
- Duplicate account detection on email and phone during creation
|
||||
|
||||
|
||||
|
||||
# 5. Key Workflows
|
||||
|
||||
## 5.1 New Account Creation (Walk-in)
|
||||
|
||||
- Staff searches by name/phone/email to check for existing account
|
||||
|
||||
- If none found, creates new account with contact details
|
||||
|
||||
- Adds member record(s) — could be same person, spouse, or children
|
||||
|
||||
- Optionally captures payment method via Stripe Elements
|
||||
|
||||
- Stripe customer created in background, ID stored on account
|
||||
|
||||
|
||||
|
||||
## 5.2 Account Lookup at POS
|
||||
|
||||
- Search by account number, name, phone, or email
|
||||
|
||||
- Returns account summary: members, active rentals, active lessons, balance
|
||||
|
||||
- Staff selects account to attach transaction
|
||||
|
||||
|
||||
|
||||
## 5.3 AIM Migration
|
||||
|
||||
- AIM customer records imported with legacy_id and legacy_source='aim'
|
||||
|
||||
- Duplicate detection run before import — staff reviews conflicts
|
||||
|
||||
- Payment methods flagged requires_update=true until customer re-enters card in Stripe
|
||||
BIN
planning/03_Domain_Inventory.docx
Normal file
BIN
planning/03_Domain_Inventory.docx
Normal file
Binary file not shown.
927
planning/03_Domain_Inventory.md
Normal file
927
planning/03_Domain_Inventory.md
Normal file
@@ -0,0 +1,927 @@
|
||||
Music Store Management Platform
|
||||
|
||||
Domain Design: Inventory
|
||||
|
||||
Version 1.2 | Updated: Repair Parts Inventory, Bulk Materials, Suppliers
|
||||
|
||||
|
||||
|
||||
# 1. Overview
|
||||
|
||||
The Inventory domain manages all physical items in the store across three distinct inventories: sale inventory (items sold at POS), rental fleet (instruments held for rental), and repair parts inventory (materials consumed in repairs). Each inventory type has different tracking requirements, pricing models, and accounting treatment.
|
||||
|
||||
|
||||
|
||||
# 2. Inventory Types
|
||||
|
||||
Type
|
||||
|
||||
Tracking
|
||||
|
||||
Appears at POS
|
||||
|
||||
Examples
|
||||
|
||||
Sale — serialized
|
||||
|
||||
Per unit (serial #)
|
||||
|
||||
Yes — retail price
|
||||
|
||||
Guitars, trumpets, keyboards
|
||||
|
||||
Sale — non-serialized
|
||||
|
||||
Quantity on hand
|
||||
|
||||
Yes — retail price
|
||||
|
||||
Strings, reeds, books, picks
|
||||
|
||||
Rental fleet
|
||||
|
||||
Per unit (serial #)
|
||||
|
||||
No — rental only
|
||||
|
||||
Student rental instruments
|
||||
|
||||
Repair parts — discrete
|
||||
|
||||
Qty on hand (integer)
|
||||
|
||||
No — repair use only
|
||||
|
||||
Valve guides, springs, pads
|
||||
|
||||
Repair parts — bulk
|
||||
|
||||
Qty on hand (decimal)
|
||||
|
||||
No — repair use only
|
||||
|
||||
Bow hair, cork sheet, solder
|
||||
|
||||
Dual-use
|
||||
|
||||
Qty on hand (integer)
|
||||
|
||||
Yes — also used in repairs
|
||||
|
||||
Strings used in setups, valve oil
|
||||
|
||||
Shop supplies
|
||||
|
||||
Qty on hand (decimal)
|
||||
|
||||
No — overhead cost only
|
||||
|
||||
Cleaning patches, lubricant, sandpaper
|
||||
|
||||
|
||||
|
||||
# 3. Sale Inventory Schema
|
||||
|
||||
## 3.1 product
|
||||
|
||||
A product is the catalog definition — what an item is, not a specific physical unit. Applies to sale and rental fleet items.
|
||||
|
||||
Column
|
||||
|
||||
Type
|
||||
|
||||
Notes
|
||||
|
||||
id
|
||||
|
||||
uuid PK
|
||||
|
||||
|
||||
|
||||
company_id
|
||||
|
||||
uuid FK
|
||||
|
||||
Tenant scoping
|
||||
|
||||
location_id
|
||||
|
||||
uuid FK
|
||||
|
||||
Physical location — inventory is tracked per-location
|
||||
|
||||
sku
|
||||
|
||||
varchar
|
||||
|
||||
Store SKU
|
||||
|
||||
upc
|
||||
|
||||
varchar
|
||||
|
||||
Manufacturer barcode
|
||||
|
||||
name
|
||||
|
||||
varchar
|
||||
|
||||
|
||||
|
||||
description
|
||||
|
||||
text
|
||||
|
||||
|
||||
|
||||
brand
|
||||
|
||||
varchar
|
||||
|
||||
|
||||
|
||||
model
|
||||
|
||||
varchar
|
||||
|
||||
|
||||
|
||||
category_id
|
||||
|
||||
uuid FK
|
||||
|
||||
Product category
|
||||
|
||||
is_serialized
|
||||
|
||||
boolean
|
||||
|
||||
True = tracked per unit
|
||||
|
||||
is_rental
|
||||
|
||||
boolean
|
||||
|
||||
True = rental fleet item
|
||||
|
||||
is_dual_use_repair
|
||||
|
||||
boolean
|
||||
|
||||
True = also usable in repairs
|
||||
|
||||
cost
|
||||
|
||||
numeric(10,2)
|
||||
|
||||
Supplier cost
|
||||
|
||||
price
|
||||
|
||||
numeric(10,2)
|
||||
|
||||
Default retail price
|
||||
|
||||
min_price
|
||||
|
||||
numeric(10,2)
|
||||
|
||||
Minimum allowed sell price
|
||||
|
||||
rental_rate_monthly
|
||||
|
||||
numeric(10,2)
|
||||
|
||||
Default monthly rental rate
|
||||
|
||||
qty_on_hand
|
||||
|
||||
integer
|
||||
|
||||
Non-serialized items only
|
||||
|
||||
qty_reorder_point
|
||||
|
||||
integer
|
||||
|
||||
Low stock alert threshold
|
||||
|
||||
legacy_id
|
||||
|
||||
varchar
|
||||
|
||||
AIM inventory ID
|
||||
|
||||
created_at
|
||||
|
||||
timestamptz
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 3.2 inventory_unit
|
||||
|
||||
Individual physical units for serialized items and rental fleet instruments.
|
||||
|
||||
id, product_id, company_id, location_id, serial_number,condition (new|excellent|good|fair|poor),status (available|sold|rented|in_repair|retired),purchase_date, purchase_cost, notes,legacy_id, created_at
|
||||
|
||||
|
||||
|
||||
# 4. Repair Parts Inventory
|
||||
|
||||
Repair parts are a completely separate inventory from sale items. They are never sold at POS, never appear in the sales catalog, and are tracked specifically for repair cost accounting. The repair parts inventory is accessible only through the repairs module (MOD-REPAIRS).
|
||||
|
||||
|
||||
|
||||
## 4.1 Part Types
|
||||
|
||||
Part Type
|
||||
|
||||
Description
|
||||
|
||||
Invoice Treatment
|
||||
|
||||
billable
|
||||
|
||||
Parts charged to customer. Tracked at cost and bill rate.
|
||||
|
||||
Appears as line item on invoice at bill_rate
|
||||
|
||||
shop_supply
|
||||
|
||||
Consumed in repairs but not billed individually. Overhead cost.
|
||||
|
||||
Does not appear on invoice
|
||||
|
||||
dual_use
|
||||
|
||||
Also exists in sale inventory. Repair use decrements sale stock.
|
||||
|
||||
Billed at repair rate or retail
|
||||
|
||||
flat_rate_material
|
||||
|
||||
Material bundled into a flat-rate service charge. E.g. bow hair in a rehair service.
|
||||
|
||||
Included in flat rate line item — not itemized
|
||||
|
||||
|
||||
|
||||
## 4.2 Discrete vs Bulk Parts
|
||||
|
||||
Material Type
|
||||
|
||||
Tracked As
|
||||
|
||||
Examples
|
||||
|
||||
Discrete
|
||||
|
||||
Integer quantity — whole units only
|
||||
|
||||
Valve guides, pad sets, bridge blanks, spring sets
|
||||
|
||||
Bulk
|
||||
|
||||
Decimal quantity — fractional units allowed
|
||||
|
||||
Bow hair (hank), cork sheet, solder (spool), varnish (ml)
|
||||
|
||||
Bulk consumable
|
||||
|
||||
Decimal — consumed by drops/grams/ml
|
||||
|
||||
Lubricant, pad cement, shellac, jewelers rouge
|
||||
|
||||
|
||||
|
||||
## 4.3 repair_part
|
||||
|
||||
Column
|
||||
|
||||
Type
|
||||
|
||||
Notes
|
||||
|
||||
id
|
||||
|
||||
uuid PK
|
||||
|
||||
|
||||
|
||||
company_id
|
||||
|
||||
uuid FK
|
||||
|
||||
Tenant scoping
|
||||
|
||||
location_id
|
||||
|
||||
uuid FK
|
||||
|
||||
Physical location — repair parts stock is per-location
|
||||
|
||||
name
|
||||
|
||||
varchar
|
||||
|
||||
e.g. 'Bow Hair — Natural White (Standard)'
|
||||
|
||||
description
|
||||
|
||||
text
|
||||
|
||||
|
||||
|
||||
part_number
|
||||
|
||||
varchar
|
||||
|
||||
Manufacturer or supplier part number
|
||||
|
||||
part_type
|
||||
|
||||
enum
|
||||
|
||||
billable | shop_supply | dual_use | flat_rate_material
|
||||
|
||||
is_bulk
|
||||
|
||||
boolean
|
||||
|
||||
True = fractional qty tracking
|
||||
|
||||
unit_of_measure
|
||||
|
||||
varchar
|
||||
|
||||
each | hank | sheet | roll | spool | ml | gram | drop
|
||||
|
||||
qty_on_hand
|
||||
|
||||
numeric(10,3)
|
||||
|
||||
Decimal supports fractional bulk quantities
|
||||
|
||||
qty_reorder_point
|
||||
|
||||
numeric(10,3)
|
||||
|
||||
Low stock alert threshold
|
||||
|
||||
cost_per_unit
|
||||
|
||||
numeric(10,4)
|
||||
|
||||
What store pays per unit — 4 decimal places for small fractions
|
||||
|
||||
bill_rate_per_unit
|
||||
|
||||
numeric(10,2)
|
||||
|
||||
Nullable — what customer is charged per unit if billed per unit
|
||||
|
||||
billing_type
|
||||
|
||||
enum
|
||||
|
||||
per_unit | flat_rate | shop_supply
|
||||
|
||||
product_id
|
||||
|
||||
uuid FK
|
||||
|
||||
Nullable — links to sale inventory for dual_use parts
|
||||
|
||||
supplier_id
|
||||
|
||||
uuid FK
|
||||
|
||||
Primary supplier for reorders
|
||||
|
||||
instrument_categories
|
||||
|
||||
text[]
|
||||
|
||||
Which instrument types use this part
|
||||
|
||||
notes
|
||||
|
||||
text
|
||||
|
||||
|
||||
|
||||
is_active
|
||||
|
||||
boolean
|
||||
|
||||
|
||||
|
||||
created_at
|
||||
|
||||
timestamptz
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 4.4 repair_part_usage_template
|
||||
|
||||
Predefined templates define how much of a bulk material a specific job consumes. Technicians select a template rather than entering quantities manually. Particularly important for bow hair where qty varies by instrument size.
|
||||
|
||||
|
||||
|
||||
Column
|
||||
|
||||
Type
|
||||
|
||||
Notes
|
||||
|
||||
id
|
||||
|
||||
uuid PK
|
||||
|
||||
|
||||
|
||||
company_id
|
||||
|
||||
uuid FK
|
||||
|
||||
Tenant scoping
|
||||
|
||||
repair_part_id
|
||||
|
||||
uuid FK
|
||||
|
||||
Which part this template applies to
|
||||
|
||||
template_name
|
||||
|
||||
varchar
|
||||
|
||||
e.g. 'Full size violin bow rehair'
|
||||
|
||||
instrument_type
|
||||
|
||||
varchar
|
||||
|
||||
violin | viola | cello | bass | fractional | other
|
||||
|
||||
instrument_size
|
||||
|
||||
varchar
|
||||
|
||||
4/4 | 3/4 | 1/2 | 1/4 | 1/8 | full
|
||||
|
||||
qty_used
|
||||
|
||||
numeric(10,3)
|
||||
|
||||
Amount consumed e.g. 1.0 hank, 0.67 hank
|
||||
|
||||
billing_type
|
||||
|
||||
enum
|
||||
|
||||
per_unit | flat_rate | shop_supply
|
||||
|
||||
flat_rate_desc
|
||||
|
||||
varchar
|
||||
|
||||
Customer-facing description if flat rate
|
||||
|
||||
flat_rate_amount
|
||||
|
||||
numeric(10,2)
|
||||
|
||||
Nullable — flat rate bill amount
|
||||
|
||||
sort_order
|
||||
|
||||
integer
|
||||
|
||||
Display order in picker UI
|
||||
|
||||
created_at
|
||||
|
||||
timestamptz
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### Default Bow Hair Templates (System Seeded)
|
||||
|
||||
Template Name
|
||||
|
||||
Instrument
|
||||
|
||||
Qty Used
|
||||
|
||||
Typical Flat Rate
|
||||
|
||||
Full size violin/viola rehair
|
||||
|
||||
violin/viola
|
||||
|
||||
1.0 hank
|
||||
|
||||
$45-55 (store sets)
|
||||
|
||||
Cello bow rehair
|
||||
|
||||
cello
|
||||
|
||||
0.67 hank
|
||||
|
||||
$65-80
|
||||
|
||||
Bass bow rehair
|
||||
|
||||
bass
|
||||
|
||||
0.75 hank
|
||||
|
||||
$85-100
|
||||
|
||||
3/4 violin rehair
|
||||
|
||||
violin
|
||||
|
||||
0.75 hank
|
||||
|
||||
$35-45
|
||||
|
||||
1/2 violin rehair
|
||||
|
||||
violin
|
||||
|
||||
0.60 hank
|
||||
|
||||
$30-40
|
||||
|
||||
1/4 violin rehair
|
||||
|
||||
violin
|
||||
|
||||
0.50 hank
|
||||
|
||||
$25-35
|
||||
|
||||
1/8 and smaller
|
||||
|
||||
violin
|
||||
|
||||
0.40 hank
|
||||
|
||||
$20-30
|
||||
|
||||
|
||||
|
||||
## 4.5 repair_part_usage
|
||||
|
||||
Records each part consumed in a repair ticket. One record per part type per ticket. Referenced by repair_ticket for invoice generation and cost accounting.
|
||||
|
||||
Column
|
||||
|
||||
Type
|
||||
|
||||
Notes
|
||||
|
||||
id
|
||||
|
||||
uuid PK
|
||||
|
||||
|
||||
|
||||
repair_ticket_id
|
||||
|
||||
uuid FK
|
||||
|
||||
|
||||
|
||||
repair_part_id
|
||||
|
||||
uuid FK
|
||||
|
||||
Nullable for custom flat-rate entries
|
||||
|
||||
template_id
|
||||
|
||||
uuid FK
|
||||
|
||||
Nullable — set if selected from template
|
||||
|
||||
qty_used
|
||||
|
||||
numeric(10,3)
|
||||
|
||||
Actual quantity consumed
|
||||
|
||||
unit_of_measure
|
||||
|
||||
varchar
|
||||
|
||||
Recorded at time of use
|
||||
|
||||
unit_cost
|
||||
|
||||
numeric(10,4)
|
||||
|
||||
Cost at time of use — historical accuracy
|
||||
|
||||
total_cost
|
||||
|
||||
numeric(10,2)
|
||||
|
||||
qty_used * unit_cost
|
||||
|
||||
billing_type
|
||||
|
||||
enum
|
||||
|
||||
per_unit | flat_rate | shop_supply
|
||||
|
||||
billed_amount
|
||||
|
||||
numeric(10,2)
|
||||
|
||||
Amount on invoice — 0 for shop_supply
|
||||
|
||||
invoice_description
|
||||
|
||||
varchar
|
||||
|
||||
Customer-facing line item description
|
||||
|
||||
created_at
|
||||
|
||||
timestamptz
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 4.6 Bulk Material Examples
|
||||
|
||||
Material
|
||||
|
||||
Unit
|
||||
|
||||
Type
|
||||
|
||||
Notes
|
||||
|
||||
Bow hair — natural white standard
|
||||
|
||||
hank
|
||||
|
||||
flat_rate_material
|
||||
|
||||
Used via templates by bow size
|
||||
|
||||
Bow hair — natural white premium
|
||||
|
||||
hank
|
||||
|
||||
flat_rate_material
|
||||
|
||||
Higher grade — higher flat rate
|
||||
|
||||
Bow hair — black
|
||||
|
||||
hank
|
||||
|
||||
flat_rate_material
|
||||
|
||||
Bass/cello preference
|
||||
|
||||
Cork sheet — 1mm
|
||||
|
||||
sheet
|
||||
|
||||
billable
|
||||
|
||||
Cut to size for neck corks etc
|
||||
|
||||
Cork sheet — 2mm
|
||||
|
||||
sheet
|
||||
|
||||
billable
|
||||
|
||||
Thicker applications
|
||||
|
||||
Solder — silver bearing
|
||||
|
||||
spool
|
||||
|
||||
shop_supply
|
||||
|
||||
Overhead — not billed
|
||||
|
||||
Pad leather — natural
|
||||
|
||||
sheet
|
||||
|
||||
billable
|
||||
|
||||
Cut to size per pad
|
||||
|
||||
Shellac flakes
|
||||
|
||||
gram
|
||||
|
||||
shop_supply
|
||||
|
||||
Overhead
|
||||
|
||||
Pad cement
|
||||
|
||||
ml
|
||||
|
||||
shop_supply
|
||||
|
||||
Overhead
|
||||
|
||||
Valve oil (bulk)
|
||||
|
||||
bottle
|
||||
|
||||
shop_supply
|
||||
|
||||
Overhead — retail valve oil is separate sale item
|
||||
|
||||
Cleaning patches
|
||||
|
||||
each
|
||||
|
||||
shop_supply
|
||||
|
||||
Discrete but overhead
|
||||
|
||||
Abrasive paper — 220 grit
|
||||
|
||||
sheet
|
||||
|
||||
shop_supply
|
||||
|
||||
Overhead
|
||||
|
||||
Resonators — mylar
|
||||
|
||||
sheet
|
||||
|
||||
billable
|
||||
|
||||
Tone holes — billed per replacement
|
||||
|
||||
Fingerboard ebony blank
|
||||
|
||||
each
|
||||
|
||||
billable
|
||||
|
||||
Discrete — billed per blank used
|
||||
|
||||
Jewelers rouge
|
||||
|
||||
gram
|
||||
|
||||
shop_supply
|
||||
|
||||
Overhead
|
||||
|
||||
|
||||
|
||||
# 5. Supplier Management
|
||||
|
||||
Suppliers are the vendors from whom the store purchases repair parts and sale inventory. Common music repair suppliers include RS Musical, Ferree's Tools, and Allied Supply. Supplier records enable reorder tracking and purchase order generation.
|
||||
|
||||
|
||||
|
||||
## 5.1 supplier
|
||||
|
||||
Column
|
||||
|
||||
Type
|
||||
|
||||
Notes
|
||||
|
||||
id
|
||||
|
||||
uuid PK
|
||||
|
||||
|
||||
|
||||
company_id
|
||||
|
||||
uuid FK
|
||||
|
||||
Tenant scoping
|
||||
|
||||
name
|
||||
|
||||
varchar
|
||||
|
||||
e.g. 'RS Musical', 'Ferree’s Tools'
|
||||
|
||||
contact_name
|
||||
|
||||
varchar
|
||||
|
||||
Primary contact
|
||||
|
||||
email
|
||||
|
||||
varchar
|
||||
|
||||
|
||||
|
||||
phone
|
||||
|
||||
varchar
|
||||
|
||||
|
||||
|
||||
website
|
||||
|
||||
varchar
|
||||
|
||||
|
||||
|
||||
account_number
|
||||
|
||||
varchar
|
||||
|
||||
Store’s account number with this supplier
|
||||
|
||||
payment_terms
|
||||
|
||||
varchar
|
||||
|
||||
e.g. Net 30, COD, prepaid
|
||||
|
||||
notes
|
||||
|
||||
text
|
||||
|
||||
|
||||
|
||||
is_active
|
||||
|
||||
boolean
|
||||
|
||||
|
||||
|
||||
created_at
|
||||
|
||||
timestamptz
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# 6. Repair Parts Reporting
|
||||
|
||||
- Parts on hand — current stock levels across all repair parts
|
||||
|
||||
- Low stock alerts — parts at or below reorder point
|
||||
|
||||
- Parts usage by period — which parts consumed most frequently
|
||||
|
||||
- Parts cost per repair ticket — material cost breakdown
|
||||
|
||||
- Technician material usage — parts consumed per technician
|
||||
|
||||
- Shop supply expense by period — overhead cost tracking
|
||||
|
||||
- Gross margin per repair — revenue vs labor cost vs parts cost
|
||||
|
||||
- Bow hair usage analysis — hanks used per month, yield per hank by bow type
|
||||
|
||||
- Reorder suggestions — parts below reorder point with preferred supplier
|
||||
|
||||
|
||||
|
||||
# 7. Business Rules
|
||||
|
||||
- Repair parts never appear in sale inventory search or POS catalog
|
||||
|
||||
- Dual-use parts decrement sale inventory qty_on_hand when used in repair
|
||||
|
||||
- Bulk qty_on_hand uses numeric(10,3) — supports fractional units down to 0.001
|
||||
|
||||
- cost_per_unit uses numeric(10,4) — supports very small per-unit costs for bulk materials
|
||||
|
||||
- Usage templates are store-configurable — seeded defaults provided, store can modify
|
||||
|
||||
- Shop supply parts reduce qty_on_hand but generate no invoice line item
|
||||
|
||||
- Flat-rate billing combines material + labor into one customer-facing line item
|
||||
|
||||
- Parts cost always recorded at time of use — price changes do not affect historical records
|
||||
|
||||
- Negative qty_on_hand not permitted — system warns technician when stock is insufficient
|
||||
BIN
planning/04_Domain_Rentals.docx
Normal file
BIN
planning/04_Domain_Rentals.docx
Normal file
Binary file not shown.
235
planning/04_Domain_Rentals.md
Normal file
235
planning/04_Domain_Rentals.md
Normal file
@@ -0,0 +1,235 @@
|
||||
Music Store Management Platform
|
||||
|
||||
Domain Design: Rentals
|
||||
|
||||
|
||||
|
||||
# 1. Overview
|
||||
|
||||
The Rentals domain manages instrument rental contracts, recurring billing, and rent-to-own tracking. Rentals are one of the most financially complex areas of the platform due to recurring Stripe subscriptions, multi-rental billing consolidation, and rent-to-own equity calculations.
|
||||
|
||||
|
||||
|
||||
# 2. Rental Types
|
||||
|
||||
Type
|
||||
|
||||
Description
|
||||
|
||||
Month-to-month
|
||||
|
||||
Standard rental — continues until returned or cancelled. No purchase obligation.
|
||||
|
||||
Rent-to-own
|
||||
|
||||
A percentage of each payment applies toward purchase price. Customer can buy out at any time.
|
||||
|
||||
Short-term
|
||||
|
||||
Hourly, daily, or weekly rental. Flat fee, no subscription. Common for events.
|
||||
|
||||
Lease purchase
|
||||
|
||||
Fixed-term contract with defined end purchase price.
|
||||
|
||||
|
||||
|
||||
# 3. Database Schema
|
||||
|
||||
## 3.1 rental
|
||||
|
||||
Column
|
||||
|
||||
Type
|
||||
|
||||
Notes
|
||||
|
||||
id
|
||||
|
||||
uuid PK
|
||||
|
||||
|
||||
|
||||
company_id
|
||||
|
||||
uuid FK
|
||||
|
||||
|
||||
|
||||
account_id
|
||||
|
||||
uuid FK
|
||||
|
||||
Billing account
|
||||
|
||||
member_id
|
||||
|
||||
uuid FK
|
||||
|
||||
Member who has the instrument
|
||||
|
||||
inventory_unit_id
|
||||
|
||||
uuid FK
|
||||
|
||||
Specific instrument rented
|
||||
|
||||
rental_type
|
||||
|
||||
enum
|
||||
|
||||
month_to_month | rent_to_own | short_term | lease_purchase
|
||||
|
||||
status
|
||||
|
||||
enum
|
||||
|
||||
active | returned | cancelled | completed
|
||||
|
||||
start_date
|
||||
|
||||
date
|
||||
|
||||
|
||||
|
||||
end_date
|
||||
|
||||
date
|
||||
|
||||
Null for open-ended rentals
|
||||
|
||||
monthly_rate
|
||||
|
||||
numeric(10,2)
|
||||
|
||||
Monthly charge amount
|
||||
|
||||
deposit_amount
|
||||
|
||||
numeric(10,2)
|
||||
|
||||
Security deposit collected
|
||||
|
||||
deposit_returned
|
||||
|
||||
boolean
|
||||
|
||||
|
||||
|
||||
billing_group
|
||||
|
||||
varchar
|
||||
|
||||
Groups rentals for consolidated billing
|
||||
|
||||
stripe_subscription_id
|
||||
|
||||
varchar
|
||||
|
||||
Stripe subscription reference
|
||||
|
||||
stripe_subscription_item_id
|
||||
|
||||
varchar
|
||||
|
||||
Line item if consolidated
|
||||
|
||||
rto_purchase_price
|
||||
|
||||
numeric(10,2)
|
||||
|
||||
Rent-to-own: full purchase price
|
||||
|
||||
rto_equity_percent
|
||||
|
||||
numeric(5,2)
|
||||
|
||||
% of payment applied to equity
|
||||
|
||||
rto_equity_accumulated
|
||||
|
||||
numeric(10,2)
|
||||
|
||||
Total equity built toward purchase
|
||||
|
||||
notes
|
||||
|
||||
text
|
||||
|
||||
|
||||
|
||||
legacy_id
|
||||
|
||||
varchar
|
||||
|
||||
AIM rental contract ID
|
||||
|
||||
created_at
|
||||
|
||||
timestamptz
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 3.2 rental_payment
|
||||
|
||||
Records each individual payment made against a rental — populated via Stripe webhook events.
|
||||
|
||||
id, rental_id, account_id, stripe_invoice_id, stripe_payment_intent_id,amount, rto_equity_applied, payment_date, status (paid|failed|refunded), created_at
|
||||
|
||||
|
||||
|
||||
# 4. Rent-to-Own Logic
|
||||
|
||||
When a rental is of type rent_to_own, each payment applies a percentage toward the purchase price.
|
||||
|
||||
- rto_equity_accumulated increases by (monthly_rate × rto_equity_percent) each payment
|
||||
|
||||
- Buyout amount = rto_purchase_price − rto_equity_accumulated
|
||||
|
||||
- Staff can offer buyout at any time — triggers inventory_unit status change to 'sold'
|
||||
|
||||
- On buyout, Stripe subscription is cancelled and a one-time charge is created for remaining balance
|
||||
|
||||
- Invoice shows accumulated equity and remaining buyout balance
|
||||
|
||||
|
||||
|
||||
# 5. Billing Consolidation
|
||||
|
||||
## 5.1 Consolidated Billing
|
||||
|
||||
When billing_group is set to the same value on multiple rentals for the same account, they share one Stripe subscription with multiple line items. One charge per month covers all grouped rentals.
|
||||
|
||||
|
||||
|
||||
## 5.2 Split Billing
|
||||
|
||||
When billing_group is null, the rental has its own independent Stripe subscription. Useful when a customer wants rentals charged on different dates.
|
||||
|
||||
|
||||
|
||||
## 5.3 Adding a Rental Mid-Cycle
|
||||
|
||||
- If consolidating: add new line item to existing subscription — Stripe prorates automatically
|
||||
|
||||
- If split: create new standalone subscription with requested billing date
|
||||
|
||||
- Invoice shows prorated amount for partial first month
|
||||
|
||||
|
||||
|
||||
# 6. Return Workflow
|
||||
|
||||
- Staff records return — captures condition assessment
|
||||
|
||||
- inventory_unit status updated to 'available' (or 'in_repair' if damage found)
|
||||
|
||||
- Stripe subscription cancelled or line item removed
|
||||
|
||||
- Deposit refund processed if applicable — logged in audit trail
|
||||
|
||||
- Final invoice generated for any partial month charges
|
||||
|
||||
- If damage found, repair ticket created automatically and linked to rental record
|
||||
BIN
planning/05_Domain_Lessons.docx
Normal file
BIN
planning/05_Domain_Lessons.docx
Normal file
Binary file not shown.
827
planning/05_Domain_Lessons.md
Normal file
827
planning/05_Domain_Lessons.md
Normal file
@@ -0,0 +1,827 @@
|
||||
Music Store Management Platform
|
||||
|
||||
Domain Design: Lessons
|
||||
|
||||
Version 1.2 | Updated: Session notes, grading scales, lesson plans, parent portal
|
||||
|
||||
|
||||
|
||||
# 1. Overview
|
||||
|
||||
The Lessons domain manages instructor scheduling, student enrollments, attendance tracking, and recurring lesson billing. Version 1.2 adds a complete teaching toolkit: per-session notes, custom grading scales, structured lesson plans with curriculum tracking, and homework assignment. These features turn the lessons module from a billing tool into a genuine teaching platform that instructors and parents will actively use.
|
||||
|
||||
|
||||
|
||||
# 2. Feature Summary
|
||||
|
||||
Feature
|
||||
|
||||
Description
|
||||
|
||||
Visible To
|
||||
|
||||
Session notes — internal
|
||||
|
||||
Instructor private notes per lesson. Not shared with student or parent.
|
||||
|
||||
Instructor, Admin
|
||||
|
||||
Session notes — student
|
||||
|
||||
Shareable lesson summary — what was covered, progress comments.
|
||||
|
||||
Instructor, Student, Parent
|
||||
|
||||
Homework assignment
|
||||
|
||||
Practice instructions assigned after each lesson.
|
||||
|
||||
Instructor, Student, Parent
|
||||
|
||||
Topics covered
|
||||
|
||||
Tags for what was worked on — links to lesson plan items.
|
||||
|
||||
Instructor, Admin
|
||||
|
||||
Custom grading scales
|
||||
|
||||
Store or instructor-defined scales. Letter, numeric, descriptive, or music-specific.
|
||||
|
||||
Instructor, Admin
|
||||
|
||||
Lesson plans
|
||||
|
||||
Structured curriculum per student. Sections, items, status, grades.
|
||||
|
||||
Instructor, Student, Parent
|
||||
|
||||
Progress tracking
|
||||
|
||||
Mastered / in progress / not started per curriculum item. History of grades.
|
||||
|
||||
Instructor, Student, Parent
|
||||
|
||||
Parent portal
|
||||
|
||||
Parents see plan progress, session summaries, homework, and grade history.
|
||||
|
||||
Parent via portal
|
||||
|
||||
|
||||
|
||||
# 3. Core Entities
|
||||
|
||||
## 3.1 instructor
|
||||
|
||||
id, company_id, employee_id (FK), display_name, bio,instruments (text[]), is_active, created_at
|
||||
|
||||
|
||||
|
||||
## 3.2 lesson_type
|
||||
|
||||
id, company_id, name, instrument, duration_minutes,lesson_format (private|group), base_rate_monthly, created_at
|
||||
|
||||
|
||||
|
||||
## 3.3 schedule_slot
|
||||
|
||||
id, company_id, instructor_id, lesson_type_id,day_of_week, start_time, room, max_students,is_active, created_at
|
||||
|
||||
|
||||
|
||||
## 3.4 enrollment
|
||||
|
||||
Column
|
||||
|
||||
Type
|
||||
|
||||
Notes
|
||||
|
||||
id
|
||||
|
||||
uuid PK
|
||||
|
||||
|
||||
|
||||
company_id
|
||||
|
||||
uuid FK
|
||||
|
||||
|
||||
|
||||
member_id
|
||||
|
||||
uuid FK
|
||||
|
||||
The member taking lessons
|
||||
|
||||
account_id
|
||||
|
||||
uuid FK
|
||||
|
||||
The billing account
|
||||
|
||||
schedule_slot_id
|
||||
|
||||
uuid FK
|
||||
|
||||
|
||||
|
||||
instructor_id
|
||||
|
||||
uuid FK
|
||||
|
||||
Denormalized for query convenience
|
||||
|
||||
status
|
||||
|
||||
enum
|
||||
|
||||
active | paused | cancelled | completed
|
||||
|
||||
start_date
|
||||
|
||||
date
|
||||
|
||||
|
||||
|
||||
end_date
|
||||
|
||||
date
|
||||
|
||||
Null for open-ended
|
||||
|
||||
monthly_rate
|
||||
|
||||
numeric(10,2)
|
||||
|
||||
Actual rate
|
||||
|
||||
billing_group
|
||||
|
||||
varchar
|
||||
|
||||
Consolidation group
|
||||
|
||||
stripe_subscription_id
|
||||
|
||||
varchar
|
||||
|
||||
|
||||
|
||||
stripe_subscription_item_id
|
||||
|
||||
varchar
|
||||
|
||||
Line item if consolidated
|
||||
|
||||
makeup_credits
|
||||
|
||||
integer
|
||||
|
||||
Available makeup credits
|
||||
|
||||
active_lesson_plan_id
|
||||
|
||||
uuid FK
|
||||
|
||||
Current lesson plan for this enrollment
|
||||
|
||||
notes
|
||||
|
||||
text
|
||||
|
||||
Enrollment-level notes
|
||||
|
||||
legacy_id
|
||||
|
||||
varchar
|
||||
|
||||
AIM enrollment ID
|
||||
|
||||
created_at
|
||||
|
||||
timestamptz
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 3.5 lesson_session
|
||||
|
||||
Column
|
||||
|
||||
Type
|
||||
|
||||
Notes
|
||||
|
||||
id
|
||||
|
||||
uuid PK
|
||||
|
||||
|
||||
|
||||
enrollment_id
|
||||
|
||||
uuid FK
|
||||
|
||||
|
||||
|
||||
company_id
|
||||
|
||||
uuid FK
|
||||
|
||||
|
||||
|
||||
scheduled_date
|
||||
|
||||
date
|
||||
|
||||
|
||||
|
||||
scheduled_time
|
||||
|
||||
time
|
||||
|
||||
|
||||
|
||||
actual_start_time
|
||||
|
||||
time
|
||||
|
||||
Nullable — if late start noted
|
||||
|
||||
actual_end_time
|
||||
|
||||
time
|
||||
|
||||
Nullable — if early end noted
|
||||
|
||||
status
|
||||
|
||||
enum
|
||||
|
||||
scheduled | attended | missed | makeup | cancelled
|
||||
|
||||
instructor_notes
|
||||
|
||||
text
|
||||
|
||||
Internal only — not visible to student or parent
|
||||
|
||||
member_notes
|
||||
|
||||
text
|
||||
|
||||
Shareable lesson summary
|
||||
|
||||
homework_assigned
|
||||
|
||||
text
|
||||
|
||||
Practice instructions for next session
|
||||
|
||||
next_lesson_goals
|
||||
|
||||
text
|
||||
|
||||
What to focus on next lesson
|
||||
|
||||
topics_covered
|
||||
|
||||
text[]
|
||||
|
||||
Free tags: scales, sight-reading, repertoire, theory
|
||||
|
||||
makeup_for_session_id
|
||||
|
||||
uuid FK
|
||||
|
||||
Self-ref — if this is a makeup session
|
||||
|
||||
notes_completed_at
|
||||
|
||||
timestamptz
|
||||
|
||||
When instructor finished post-lesson notes
|
||||
|
||||
created_at
|
||||
|
||||
timestamptz
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# 4. Custom Grading Scales
|
||||
|
||||
Every store can define their own grading scales. Scales can be assigned at the store level (default for all instructors) or at the instructor level (personal preference). Individual lesson plan items can reference any available scale.
|
||||
|
||||
|
||||
|
||||
## 4.1 grading_scale
|
||||
|
||||
Column
|
||||
|
||||
Type
|
||||
|
||||
Notes
|
||||
|
||||
id
|
||||
|
||||
uuid PK
|
||||
|
||||
|
||||
|
||||
company_id
|
||||
|
||||
uuid FK
|
||||
|
||||
|
||||
|
||||
name
|
||||
|
||||
varchar
|
||||
|
||||
e.g. 'Standard Letter', 'ABRSM Style', 'Music Progress'
|
||||
|
||||
description
|
||||
|
||||
text
|
||||
|
||||
Optional explanation of the scale
|
||||
|
||||
is_default
|
||||
|
||||
boolean
|
||||
|
||||
Default scale used when none specified on plan item
|
||||
|
||||
created_by
|
||||
|
||||
uuid FK
|
||||
|
||||
Instructor or admin who created
|
||||
|
||||
is_active
|
||||
|
||||
boolean
|
||||
|
||||
|
||||
|
||||
created_at
|
||||
|
||||
timestamptz
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 4.2 grading_scale_level
|
||||
|
||||
Column
|
||||
|
||||
Type
|
||||
|
||||
Notes
|
||||
|
||||
id
|
||||
|
||||
uuid PK
|
||||
|
||||
|
||||
|
||||
grading_scale_id
|
||||
|
||||
uuid FK
|
||||
|
||||
|
||||
|
||||
value
|
||||
|
||||
varchar
|
||||
|
||||
The grade value: A, 4, Distinction, Mastered
|
||||
|
||||
label
|
||||
|
||||
varchar
|
||||
|
||||
Longer description shown in UI
|
||||
|
||||
numeric_value
|
||||
|
||||
integer
|
||||
|
||||
1-100 for averaging and reporting
|
||||
|
||||
color_hex
|
||||
|
||||
char(7)
|
||||
|
||||
UI display color e.g. #4CAF50 for green
|
||||
|
||||
sort_order
|
||||
|
||||
integer
|
||||
|
||||
Display order — highest grade first
|
||||
|
||||
|
||||
|
||||
## 4.3 Seeded Default Scales
|
||||
|
||||
Scale Name
|
||||
|
||||
Levels
|
||||
|
||||
Standard Letter
|
||||
|
||||
A+ A A- B+ B B- C+ C D F
|
||||
|
||||
Numeric 1-10
|
||||
|
||||
10 9 8 7 6 5 4 3 2 1
|
||||
|
||||
ABRSM Style
|
||||
|
||||
Distinction Merit Pass Below Pass
|
||||
|
||||
Progress
|
||||
|
||||
Mastered Proficient Developing Beginning
|
||||
|
||||
Concert Readiness
|
||||
|
||||
Concert Ready Performance Ready Practice Ready Learning
|
||||
|
||||
Simple
|
||||
|
||||
Excellent Good Needs Work
|
||||
|
||||
|
||||
|
||||
All default scales are seeded at store creation and can be modified or deactivated. Custom scales can be added at any time. Scale levels cannot be deleted if they have been used in a graded item — only deactivated.
|
||||
|
||||
|
||||
|
||||
# 5. Lesson Plans
|
||||
|
||||
A lesson plan is a structured curriculum for a specific student enrollment. It is organized into sections (e.g. Scales, Repertoire, Theory) with individual items in each section. Each item tracks status, grade, and progress history. Multiple plans can exist per student — one active at a time per enrollment.
|
||||
|
||||
|
||||
|
||||
## 5.1 member_lesson_plan
|
||||
|
||||
Column
|
||||
|
||||
Type
|
||||
|
||||
Notes
|
||||
|
||||
id
|
||||
|
||||
uuid PK
|
||||
|
||||
|
||||
|
||||
company_id
|
||||
|
||||
uuid FK
|
||||
|
||||
|
||||
|
||||
member_id
|
||||
|
||||
uuid FK
|
||||
|
||||
|
||||
|
||||
enrollment_id
|
||||
|
||||
uuid FK
|
||||
|
||||
|
||||
|
||||
created_by
|
||||
|
||||
uuid FK
|
||||
|
||||
Instructor who created the plan
|
||||
|
||||
title
|
||||
|
||||
varchar
|
||||
|
||||
e.g. 'Year 2 Piano', 'Grade 3 Violin'
|
||||
|
||||
description
|
||||
|
||||
text
|
||||
|
||||
Goals and context for this plan
|
||||
|
||||
template_id
|
||||
|
||||
uuid FK
|
||||
|
||||
Nullable — if created from a plan template
|
||||
|
||||
is_active
|
||||
|
||||
boolean
|
||||
|
||||
Only one plan active per enrollment at a time
|
||||
|
||||
started_date
|
||||
|
||||
date
|
||||
|
||||
|
||||
|
||||
completed_date
|
||||
|
||||
date
|
||||
|
||||
Nullable — set when all items mastered
|
||||
|
||||
created_at
|
||||
|
||||
timestamptz
|
||||
|
||||
|
||||
|
||||
updated_at
|
||||
|
||||
timestamptz
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 5.2 lesson_plan_section
|
||||
|
||||
id, lesson_plan_id (FK), title, description,sort_order, created_atExample sections: Scales & Arpeggios Repertoire Theory Technique Sight Reading Ear Training
|
||||
|
||||
|
||||
|
||||
## 5.3 lesson_plan_item
|
||||
|
||||
Column
|
||||
|
||||
Type
|
||||
|
||||
Notes
|
||||
|
||||
id
|
||||
|
||||
uuid PK
|
||||
|
||||
|
||||
|
||||
section_id
|
||||
|
||||
uuid FK
|
||||
|
||||
|
||||
|
||||
title
|
||||
|
||||
varchar
|
||||
|
||||
e.g. 'C Major Scale 2 octaves', 'Fur Elise'
|
||||
|
||||
description
|
||||
|
||||
text
|
||||
|
||||
Additional detail or context
|
||||
|
||||
status
|
||||
|
||||
enum
|
||||
|
||||
not_started | in_progress | mastered | skipped
|
||||
|
||||
grading_scale_id
|
||||
|
||||
uuid FK
|
||||
|
||||
Nullable — which scale to use for this item
|
||||
|
||||
current_grade_value
|
||||
|
||||
varchar
|
||||
|
||||
Most recent grade assigned
|
||||
|
||||
target_grade_value
|
||||
|
||||
varchar
|
||||
|
||||
Grade required to mark as mastered
|
||||
|
||||
started_date
|
||||
|
||||
date
|
||||
|
||||
When first worked on
|
||||
|
||||
mastered_date
|
||||
|
||||
date
|
||||
|
||||
When marked mastered
|
||||
|
||||
notes
|
||||
|
||||
text
|
||||
|
||||
Instructor notes specific to this item
|
||||
|
||||
sort_order
|
||||
|
||||
integer
|
||||
|
||||
|
||||
|
||||
created_at
|
||||
|
||||
timestamptz
|
||||
|
||||
|
||||
|
||||
updated_at
|
||||
|
||||
timestamptz
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 5.4 lesson_plan_item_grade_history
|
||||
|
||||
Every grade assigned to a plan item is preserved. This builds a complete history of how a student progressed through each piece or skill over time.
|
||||
|
||||
id, lesson_plan_item_id (FK),lesson_session_id (FK nullable), -- which session this grade was givengrade_value, -- the gradegrading_scale_id (FK),graded_by (uuid FK), -- instructornotes, -- context for this gradecreated_at
|
||||
|
||||
|
||||
|
||||
## 5.5 lesson_session_plan_item
|
||||
|
||||
Links lesson sessions to the plan items worked on during that session. Enables the full history of when each curriculum item was practiced.
|
||||
|
||||
id, lesson_session_id (FK), lesson_plan_item_id (FK),was_worked_on (boolean),grade_value (varchar nullable), -- grade if assessed this sessiongrading_scale_id (FK nullable),notes (text nullable), -- session-specific notes on this itemcreated_at
|
||||
|
||||
|
||||
|
||||
# 6. Lesson Plan Templates
|
||||
|
||||
Instructors can save lesson plan structures as templates to quickly create plans for new students. A template defines the sections and items without any student-specific data. Templates can be shared across the store or kept private to an instructor.
|
||||
|
||||
|
||||
|
||||
lesson_plan_template id, company_id, created_by (FK), title, instrument, skill_level (beginner|intermediate|advanced), description, is_shared (boolean), created_atlesson_plan_template_section id, template_id (FK), title, sort_orderlesson_plan_template_item id, section_id (FK), title, description, suggested_grading_scale_id (FK nullable), sort_order
|
||||
|
||||
|
||||
|
||||
When creating a new lesson plan from a template, all sections and items are copied to the new plan. Changes to the plan after creation do not affect the template.
|
||||
|
||||
|
||||
|
||||
# 7. Example — Year 2 Piano Plan
|
||||
|
||||
Student: Emma Chen Instrument: PianoPlan: Year 2 Piano Status: Active Progress: 40%SCALES & ARPEGGIOS [done] C Major 2 octaves Mastered Oct 2024 [done] G Major 2 octaves Mastered Nov 2024 [>> ] D Major 2 octaves In Progress Grade: B [ ] A Major 2 octaves Not Started [ ] C Major arpeggio Not StartedREPERTOIRE [done] Minuet in G (Bach) Mastered Sep 2024 [>> ] Fur Elise (Beethoven) In Progress Grade: B+ [ ] Moonlight Sonata Mvt 1 Not Started [ ] Sonatina Op.36 No.1 Not StartedTHEORY [done] Key signatures to 2 sharps Mastered [>> ] Intervals - 3rds and 6ths In Progress [ ] Chord inversions Not StartedTECHNIQUE [>> ] Hanon exercises 1-10 In Progress [ ] Scales in thirds Not StartedLast lesson (Nov 15): Topics: Fur Elise bars 1-16, D Major scale Homework: Practice Fur Elise bars 1-8 hands together daily Notes for parent: Great focus today. Right hand is clean. Next goals: Clean up arpeggios in bars 5-6
|
||||
|
||||
|
||||
|
||||
# 8. Instructor Post-Lesson Workflow
|
||||
|
||||
After each lesson the instructor completes a brief post-lesson record. The UI is optimized for speed — a mobile-friendly form the instructor fills in immediately after the student leaves.
|
||||
|
||||
|
||||
|
||||
Post-Lesson Form — Emma Chen, Nov 15Topics covered today: [+ Add from plan] [x] Fur Elise (Beethoven) Grade: [B+ v] [x] D Major 2 octaves Grade: [B v] [+ Add custom topic]Homework assigned: Practice Fur Elise bars 1-8 hands together dailyNotes for parent/student: Great focus today. Right hand melody is clean. Left hand needs more practice on the arpeggios.Private instructor notes: Emma seems distracted lately - check in with parentNext lesson goals: Clean up bars 5-6 arpeggios. Start bars 17-24.[Save Notes]
|
||||
|
||||
|
||||
|
||||
- Topics linked to plan items auto-update item status to in_progress if not_started
|
||||
|
||||
- Grade entry creates a lesson_plan_item_grade_history record
|
||||
|
||||
- Student notes and homework immediately visible in parent portal after save
|
||||
|
||||
- Private instructor notes never visible outside staff views
|
||||
|
||||
- Notes completed timestamp recorded — store can see if instructors are filling in notes
|
||||
|
||||
|
||||
|
||||
# 9. Parent Portal — Lessons View
|
||||
|
||||
Parents access the customer portal to see their child's lesson progress. The view is read-only and shows only student-facing content — never instructor private notes.
|
||||
|
||||
|
||||
|
||||
Portal — Emma Chen's LessonsCurrent Enrollment: Piano with Ms. Sarah M. Tuesdays 4:00pm Next lesson: Nov 22Last Lesson Summary (Nov 15): What we worked on: Fur Elise, D Major Scale Homework: Practice Fur Elise bars 1-8 hands together daily Notes: Great focus today. Right hand melody is clean. Left hand arpeggios need more work.Lesson Plan Progress: 40% complete Scales: 2 of 5 mastered Repertoire: 1 of 4 mastered [In progress: Fur Elise] Theory: 1 of 3 mastered Technique: 0 of 2 mastered [View Full Plan]Recent Grades: Fur Elise B+ Nov 15 D Major Scale B Nov 15 Minuet in G A Sep 28 [Mastered]Upcoming Lessons: Nov 22 Tuesday 4:00pm Nov 29 Tuesday 4:00pmAttendance: 12 attended, 1 missed, 0 makeups available
|
||||
|
||||
|
||||
|
||||
# 10. Business Rules
|
||||
|
||||
## 10.1 Notes
|
||||
|
||||
- instructor_notes are never exposed via any API route accessible to students or parents
|
||||
|
||||
- member_notes and homework are visible in parent portal immediately after save
|
||||
|
||||
- notes_completed_at tracked per session — admin can identify instructors not completing notes
|
||||
|
||||
- Notes are optional — sessions can be marked attended without post-lesson notes
|
||||
|
||||
- Notes can be edited after save — edit history not required but updated_at tracked
|
||||
|
||||
|
||||
|
||||
## 10.2 Grading Scales
|
||||
|
||||
- Each store gets all default scales seeded at creation
|
||||
|
||||
- Default scale is used when a plan item has no explicit scale assigned
|
||||
|
||||
- Scale levels used in grade history cannot be deleted — only deactivated
|
||||
|
||||
- Numeric_value enables cross-scale average calculations in reports
|
||||
|
||||
- Instructors can create personal scales visible only to them
|
||||
|
||||
|
||||
|
||||
## 10.3 Lesson Plans
|
||||
|
||||
- Only one plan can be active per enrollment at a time
|
||||
|
||||
- Completing a plan (all items mastered) does not auto-create the next plan
|
||||
|
||||
- Plan items marked mastered record mastered_date automatically
|
||||
|
||||
- Skipped items excluded from progress percentage calculation
|
||||
|
||||
- Plan history preserved when enrollment ends — never deleted
|
||||
|
||||
- Templates are copied on use — template changes do not affect existing plans
|
||||
|
||||
- Plan visible in parent portal — instructor controls what items appear there via is_shared flag on items
|
||||
|
||||
|
||||
|
||||
## 10.4 Scheduling Constraints
|
||||
|
||||
- Student cannot be enrolled in two slots at the same day and time
|
||||
|
||||
- Instructor cannot teach two students simultaneously unless group lesson
|
||||
|
||||
- Group lessons enforce max_students cap on schedule_slot
|
||||
|
||||
- Makeup sessions linked to original missed session via makeup_for_session_id
|
||||
|
||||
|
||||
|
||||
# 11. Reporting
|
||||
|
||||
Report
|
||||
|
||||
Description
|
||||
|
||||
Student progress summary
|
||||
|
||||
Plan completion % per student, items mastered, current grades
|
||||
|
||||
Instructor notes compliance
|
||||
|
||||
Which instructors are completing post-lesson notes and how quickly
|
||||
|
||||
Grade distribution
|
||||
|
||||
Grade spread per instructor, per instrument, per plan item
|
||||
|
||||
Curriculum progress
|
||||
|
||||
How many students are working on each plan item — popular pieces
|
||||
|
||||
Homework completion
|
||||
|
||||
Homework assigned vs next-session progress correlation
|
||||
|
||||
Retention by progress
|
||||
|
||||
Do students who have active lesson plans churn less? Correlation report.
|
||||
|
||||
Plan template usage
|
||||
|
||||
Which templates are used most, average completion time per template
|
||||
|
||||
Attendance summary
|
||||
|
||||
Attended, missed, makeup by student, instructor, and period
|
||||
BIN
planning/06_Domain_Repairs.docx
Normal file
BIN
planning/06_Domain_Repairs.docx
Normal file
Binary file not shown.
435
planning/06_Domain_Repairs.md
Normal file
435
planning/06_Domain_Repairs.md
Normal file
@@ -0,0 +1,435 @@
|
||||
Music Store Management Platform
|
||||
|
||||
Domain Design: Repairs
|
||||
|
||||
Version 1.2 | Updated: Parts inventory, bulk materials, flat-rate billing, tech metrics
|
||||
|
||||
|
||||
|
||||
# 1. Overview
|
||||
|
||||
The Repairs domain manages instrument intake, diagnosis, work tracking, parts, labor billing, and customer pickup. It integrates with the repair parts inventory for materials tracking and supports three billing models: per-unit parts billing, flat-rate service billing (e.g. bow rehairs), and shop supply tracking. Repair tickets support both individual customer repairs and bulk school repairs via batch grouping.
|
||||
|
||||
|
||||
|
||||
# 2. Repair Ticket Lifecycle
|
||||
|
||||
Status
|
||||
|
||||
Description
|
||||
|
||||
intake
|
||||
|
||||
Instrument received, condition documented, intake ticket printed for customer
|
||||
|
||||
diagnosing
|
||||
|
||||
Technician evaluating — estimate not yet provided
|
||||
|
||||
pending_approval
|
||||
|
||||
Estimate provided, awaiting customer authorization
|
||||
|
||||
approved
|
||||
|
||||
Customer approved — work authorized
|
||||
|
||||
in_progress
|
||||
|
||||
Actively being repaired — parts being logged
|
||||
|
||||
pending_parts
|
||||
|
||||
Waiting on parts order from supplier
|
||||
|
||||
ready
|
||||
|
||||
Repair complete — awaiting pickup or delivery
|
||||
|
||||
picked_up
|
||||
|
||||
Customer collected instrument and paid
|
||||
|
||||
delivered
|
||||
|
||||
Instrument returned via store delivery event
|
||||
|
||||
cancelled
|
||||
|
||||
Repair cancelled — instrument returned uncompleted
|
||||
|
||||
|
||||
|
||||
# 3. Database Schema
|
||||
|
||||
## 3.1 repair_ticket
|
||||
|
||||
Column
|
||||
|
||||
Type
|
||||
|
||||
Notes
|
||||
|
||||
id
|
||||
|
||||
uuid PK
|
||||
|
||||
|
||||
|
||||
company_id
|
||||
|
||||
uuid FK
|
||||
|
||||
|
||||
|
||||
repair_batch_id
|
||||
|
||||
uuid FK
|
||||
|
||||
Nullable — null = individual repair
|
||||
|
||||
ticket_number
|
||||
|
||||
varchar
|
||||
|
||||
Human-readable ID
|
||||
|
||||
account_id
|
||||
|
||||
uuid FK
|
||||
|
||||
Nullable — walk-in without account
|
||||
|
||||
customer_name
|
||||
|
||||
varchar
|
||||
|
||||
Denormalized for walk-ins
|
||||
|
||||
customer_phone
|
||||
|
||||
varchar
|
||||
|
||||
Contact for ready notification
|
||||
|
||||
inventory_unit_id
|
||||
|
||||
uuid FK
|
||||
|
||||
Nullable — known instrument
|
||||
|
||||
instrument_description
|
||||
|
||||
varchar
|
||||
|
||||
Free text for unknown instruments
|
||||
|
||||
serial_number
|
||||
|
||||
varchar
|
||||
|
||||
If known
|
||||
|
||||
condition_in
|
||||
|
||||
enum
|
||||
|
||||
excellent|good|fair|poor — at intake
|
||||
|
||||
condition_in_notes
|
||||
|
||||
text
|
||||
|
||||
Detailed condition at intake
|
||||
|
||||
problem_description
|
||||
|
||||
text
|
||||
|
||||
Customer description of issue
|
||||
|
||||
technician_notes
|
||||
|
||||
text
|
||||
|
||||
Internal diagnosis and work notes
|
||||
|
||||
status
|
||||
|
||||
enum
|
||||
|
||||
See lifecycle above
|
||||
|
||||
assigned_technician_id
|
||||
|
||||
uuid FK
|
||||
|
||||
Employee assigned
|
||||
|
||||
estimated_cost
|
||||
|
||||
numeric(10,2)
|
||||
|
||||
Estimate given to customer
|
||||
|
||||
actual_cost
|
||||
|
||||
numeric(10,2)
|
||||
|
||||
Final billed amount
|
||||
|
||||
intake_date
|
||||
|
||||
date
|
||||
|
||||
|
||||
|
||||
promised_date
|
||||
|
||||
date
|
||||
|
||||
Estimated completion date
|
||||
|
||||
completed_date
|
||||
|
||||
date
|
||||
|
||||
When repair work finished
|
||||
|
||||
linked_rental_id
|
||||
|
||||
uuid FK
|
||||
|
||||
If repair from rental return
|
||||
|
||||
legacy_id
|
||||
|
||||
varchar
|
||||
|
||||
AIM service ticket ID
|
||||
|
||||
created_at
|
||||
|
||||
timestamptz
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 3.2 repair_line_item
|
||||
|
||||
Line items on a repair ticket cover three billing types — labor, per-unit parts, and flat-rate services. All three appear on the customer invoice.
|
||||
|
||||
Column
|
||||
|
||||
Type
|
||||
|
||||
Notes
|
||||
|
||||
id
|
||||
|
||||
uuid PK
|
||||
|
||||
|
||||
|
||||
repair_ticket_id
|
||||
|
||||
uuid FK
|
||||
|
||||
|
||||
|
||||
item_type
|
||||
|
||||
enum
|
||||
|
||||
labor | part | flat_rate | misc
|
||||
|
||||
description
|
||||
|
||||
varchar
|
||||
|
||||
Customer-facing description
|
||||
|
||||
repair_part_usage_id
|
||||
|
||||
uuid FK
|
||||
|
||||
Nullable — links to parts usage record
|
||||
|
||||
qty
|
||||
|
||||
numeric(10,3)
|
||||
|
||||
Supports fractional for bulk materials
|
||||
|
||||
unit_price
|
||||
|
||||
numeric(10,2)
|
||||
|
||||
Rate per unit
|
||||
|
||||
total_price
|
||||
|
||||
numeric(10,2)
|
||||
|
||||
qty * unit_price
|
||||
|
||||
cost
|
||||
|
||||
numeric(10,2)
|
||||
|
||||
Internal cost — for margin calculation
|
||||
|
||||
technician_id
|
||||
|
||||
uuid FK
|
||||
|
||||
For labor lines — which tech
|
||||
|
||||
created_at
|
||||
|
||||
timestamptz
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 3.3 Billing Type Examples
|
||||
|
||||
Type
|
||||
|
||||
Description
|
||||
|
||||
Qty
|
||||
|
||||
Price
|
||||
|
||||
Notes
|
||||
|
||||
labor
|
||||
|
||||
Mechanical overhaul
|
||||
|
||||
2.5 hrs
|
||||
|
||||
$65/hr
|
||||
|
||||
Tech rate x hours
|
||||
|
||||
part
|
||||
|
||||
Trumpet valve guide
|
||||
|
||||
3 each
|
||||
|
||||
$2.50 ea
|
||||
|
||||
Per-unit billable part
|
||||
|
||||
flat_rate
|
||||
|
||||
Bow Rehair — Full Size
|
||||
|
||||
1
|
||||
|
||||
$50.00
|
||||
|
||||
Labor + hair bundled
|
||||
|
||||
flat_rate
|
||||
|
||||
Bow Rehair — Cello
|
||||
|
||||
1
|
||||
|
||||
$70.00
|
||||
|
||||
0.67 hank consumed internally
|
||||
|
||||
misc
|
||||
|
||||
Expedite fee
|
||||
|
||||
1
|
||||
|
||||
$15.00
|
||||
|
||||
Custom charge
|
||||
|
||||
|
||||
|
||||
# 4. Technician Workflow — Parts Logging
|
||||
|
||||
When a technician works a repair ticket they log parts as they use them. The interface is optimized for speed — minimal clicks to record common operations.
|
||||
|
||||
|
||||
|
||||
Repair Ticket #RT-2024-0042 — Bach Stradivarius TrumpetTechnician: Sarah M. Status: in_progress[+ Add Labor] [+ Add Part] [+ Add Flat Rate Service]Current line items: Labor Full mechanical overhaul 2.5 hrs @ $65 $162.50 Part Trumpet valve guide (x3) 3 @ $2.50 $7.50 Part Valve spring set (x1) 1 @ $8.00 $8.00 [shop] Valve oil (shop supply) recorded, not billed [shop] Cleaning patches (x4) recorded, not billed ───────── Subtotal: $178.00 Est. given to customer: $180.00
|
||||
|
||||
|
||||
|
||||
# 5. Technician Productivity Reporting
|
||||
|
||||
Because parts and labor are tracked per technician per ticket, the system can generate detailed productivity and profitability reports that are unavailable in AIM.
|
||||
|
||||
|
||||
|
||||
Report
|
||||
|
||||
Description
|
||||
|
||||
Revenue per technician
|
||||
|
||||
Total billed labor and parts per tech per period
|
||||
|
||||
Gross margin per ticket
|
||||
|
||||
Revenue minus labor cost minus parts cost per ticket
|
||||
|
||||
Parts usage by tech
|
||||
|
||||
Which parts each technician uses — identifies unusual consumption
|
||||
|
||||
Bow rehair analysis
|
||||
|
||||
Rehairs completed, hair consumed per size, yield per hank
|
||||
|
||||
Shop supply expense
|
||||
|
||||
Overhead cost of shop supplies by period and by technician
|
||||
|
||||
Average turnaround time
|
||||
|
||||
Intake to completion per tech and per repair type
|
||||
|
||||
Estimate accuracy
|
||||
|
||||
Actual vs estimated cost variance per tech
|
||||
|
||||
Flat rate profitability
|
||||
|
||||
Margin on flat-rate services — e.g. are rehair rates covering costs
|
||||
|
||||
|
||||
|
||||
# 6. Business Rules
|
||||
|
||||
- Shop supply parts logged for cost tracking but never appear on customer invoice
|
||||
|
||||
- Flat-rate services show as single line item on invoice — hair/material consumption tracked internally
|
||||
|
||||
- Parts costs recorded at time of use — historical accuracy preserved if costs change
|
||||
|
||||
- Technician cannot log more parts than qty_on_hand — system warns and blocks
|
||||
|
||||
- Dual-use parts logged in repair decrease sale inventory qty_on_hand automatically
|
||||
|
||||
- Estimate approval required before work begins — waivable with manager override
|
||||
|
||||
- Actual cost variance from estimate requires reason code logged in audit trail
|
||||
|
||||
- Batch repair approval cascades to all child tickets
|
||||
|
||||
- Repair complete status triggers customer notification via preferred channel
|
||||
|
||||
- Delivered status set by delivery domain completion — not manually
|
||||
BIN
planning/07_Domain_Sales_POS.docx
Normal file
BIN
planning/07_Domain_Sales_POS.docx
Normal file
Binary file not shown.
231
planning/07_Domain_Sales_POS.md
Normal file
231
planning/07_Domain_Sales_POS.md
Normal file
@@ -0,0 +1,231 @@
|
||||
Music Store Management Platform
|
||||
|
||||
Domain Design: Sales & Point of Sale
|
||||
|
||||
|
||||
|
||||
# 1. Overview
|
||||
|
||||
The Sales & POS domain handles all in-person and counter transactions — retail sales, deposits, repair payments, rental fees, and account charges. It integrates with Stripe Terminal for card-present payments and Stripe Elements for keyed entry.
|
||||
|
||||
|
||||
|
||||
# 2. Transaction Types
|
||||
|
||||
Type
|
||||
|
||||
Description
|
||||
|
||||
sale
|
||||
|
||||
Retail sale of product — instruments, accessories, supplies
|
||||
|
||||
repair_payment
|
||||
|
||||
Payment collected at repair ticket pickup
|
||||
|
||||
rental_deposit
|
||||
|
||||
Security deposit collected at rental start
|
||||
|
||||
account_payment
|
||||
|
||||
Payment against outstanding account balance
|
||||
|
||||
refund
|
||||
|
||||
Return of payment — full or partial
|
||||
|
||||
|
||||
|
||||
# 3. Database Schema
|
||||
|
||||
## 3.1 transaction
|
||||
|
||||
Column
|
||||
|
||||
Type
|
||||
|
||||
Notes
|
||||
|
||||
id
|
||||
|
||||
uuid PK
|
||||
|
||||
|
||||
|
||||
company_id
|
||||
|
||||
uuid FK
|
||||
|
||||
Tenant scoping
|
||||
|
||||
location_id
|
||||
|
||||
uuid FK
|
||||
|
||||
Physical location where transaction occurred
|
||||
|
||||
transaction_number
|
||||
|
||||
varchar
|
||||
|
||||
Human-readable ID
|
||||
|
||||
account_id
|
||||
|
||||
uuid FK
|
||||
|
||||
Nullable for anonymous cash sales
|
||||
|
||||
transaction_type
|
||||
|
||||
enum
|
||||
|
||||
sale|repair_payment|rental_deposit|account_payment|refund
|
||||
|
||||
status
|
||||
|
||||
enum
|
||||
|
||||
pending|completed|voided|refunded
|
||||
|
||||
subtotal
|
||||
|
||||
numeric(10,2)
|
||||
|
||||
Before tax and discounts
|
||||
|
||||
discount_total
|
||||
|
||||
numeric(10,2)
|
||||
|
||||
Total discounts applied
|
||||
|
||||
tax_total
|
||||
|
||||
numeric(10,2)
|
||||
|
||||
|
||||
|
||||
total
|
||||
|
||||
numeric(10,2)
|
||||
|
||||
Final amount charged
|
||||
|
||||
payment_method
|
||||
|
||||
enum
|
||||
|
||||
card_present|card_keyed|cash|check|account_charge
|
||||
|
||||
stripe_payment_intent_id
|
||||
|
||||
varchar
|
||||
|
||||
For card transactions
|
||||
|
||||
stripe_terminal_reader_id
|
||||
|
||||
varchar
|
||||
|
||||
Which reader was used
|
||||
|
||||
processed_by
|
||||
|
||||
uuid FK
|
||||
|
||||
Employee who processed
|
||||
|
||||
notes
|
||||
|
||||
text
|
||||
|
||||
|
||||
|
||||
created_at
|
||||
|
||||
timestamptz
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 3.2 transaction_line_item
|
||||
|
||||
id, transaction_id, product_id (nullable), inventory_unit_id (nullable),description, qty, unit_price, discount_amount, discount_reason,tax_rate, line_total, created_at
|
||||
|
||||
|
||||
|
||||
## 3.3 discount
|
||||
|
||||
id, company_id, location_id, name, discount_type (percent|fixed),discount_value, applies_to (order|line_item|category),requires_approval_above (threshold amount), is_active,valid_from, valid_until, created_at
|
||||
|
||||
|
||||
|
||||
## 3.4 discount_audit
|
||||
|
||||
id, transaction_id, transaction_line_item_id, discount_id,applied_by (employee_id), approved_by (employee_id, nullable),original_amount, discounted_amount, reason, created_at
|
||||
|
||||
|
||||
|
||||
# 4. Discount & Adjustment Rules
|
||||
|
||||
- Discounts are applied at line item or order level
|
||||
|
||||
- Discounts exceeding manager threshold require approval — logged in discount_audit
|
||||
|
||||
- Manual price overrides treated as discounts — require reason code
|
||||
|
||||
- Promotional codes validated against discount table before application
|
||||
|
||||
- All discounts appear as line items on invoice — original price shown
|
||||
|
||||
- Discount audit records are immutable — no deletion permitted
|
||||
|
||||
|
||||
|
||||
# 5. Payment Methods
|
||||
|
||||
Method
|
||||
|
||||
Integration
|
||||
|
||||
Notes
|
||||
|
||||
Card present
|
||||
|
||||
Stripe Terminal WiFi
|
||||
|
||||
Desktop — WisePOS E reader
|
||||
|
||||
Card present (BT)
|
||||
|
||||
Stripe Terminal BT
|
||||
|
||||
iOS — Bluetooth reader at conventions
|
||||
|
||||
Card keyed
|
||||
|
||||
Stripe Elements
|
||||
|
||||
Desktop — PCI safe, card data never touches app
|
||||
|
||||
Cash
|
||||
|
||||
Manual entry
|
||||
|
||||
Change calculated, drawer tracked
|
||||
|
||||
Check
|
||||
|
||||
Manual entry
|
||||
|
||||
Check number recorded
|
||||
|
||||
Account charge
|
||||
|
||||
Internal
|
||||
|
||||
Charges to account balance — billed later
|
||||
BIN
planning/08_Domain_Payments_Billing.docx
Normal file
BIN
planning/08_Domain_Payments_Billing.docx
Normal file
Binary file not shown.
199
planning/08_Domain_Payments_Billing.md
Normal file
199
planning/08_Domain_Payments_Billing.md
Normal file
@@ -0,0 +1,199 @@
|
||||
Music Store Management Platform
|
||||
|
||||
Domain Design: Payments & Billing
|
||||
|
||||
|
||||
|
||||
# 1. Overview
|
||||
|
||||
The Payments domain describes how the application integrates with Stripe for all payment flows. It covers one-time payments at POS, recurring subscription billing for lessons and rentals, webhook event handling, and the AIM migration payment transition strategy.
|
||||
|
||||
|
||||
|
||||
# 2. Stripe Entity Model
|
||||
|
||||
Stripe Entity
|
||||
|
||||
Maps To
|
||||
|
||||
Notes
|
||||
|
||||
Customer
|
||||
|
||||
account
|
||||
|
||||
One Stripe customer per billing account
|
||||
|
||||
PaymentMethod
|
||||
|
||||
account_payment_method
|
||||
|
||||
pm_xxx stored as reference only
|
||||
|
||||
Subscription
|
||||
|
||||
enrollment / rental
|
||||
|
||||
One per billing group or standalone
|
||||
|
||||
SubscriptionItem
|
||||
|
||||
enrollment / rental line
|
||||
|
||||
One per enrollment/rental within subscription
|
||||
|
||||
Invoice
|
||||
|
||||
monthly bill
|
||||
|
||||
Auto-generated by Stripe on billing date
|
||||
|
||||
PaymentIntent
|
||||
|
||||
transaction
|
||||
|
||||
One-time charges at POS
|
||||
|
||||
Reader
|
||||
|
||||
terminal device
|
||||
|
||||
Physical Stripe Terminal reader
|
||||
|
||||
|
||||
|
||||
# 3. Recurring Billing Flow
|
||||
|
||||
- Account created in app → Stripe Customer created via API → stripe_customer_id stored
|
||||
|
||||
- Enrollment or rental activated → Stripe Subscription created or line item added
|
||||
|
||||
- Billing date arrives → Stripe automatically charges payment method
|
||||
|
||||
- Stripe sends webhook: invoice.paid → app updates subscription status, records payment
|
||||
|
||||
- Stripe sends webhook: invoice.payment_failed → app flags account, triggers follow-up
|
||||
|
||||
- Stripe card updater silently updates expired/replaced cards → reduces failed payments
|
||||
|
||||
|
||||
|
||||
# 4. Webhook Events
|
||||
|
||||
Event
|
||||
|
||||
Action
|
||||
|
||||
invoice.paid
|
||||
|
||||
Record payment, update subscription status, generate receipt
|
||||
|
||||
invoice.payment_failed
|
||||
|
||||
Flag account, notify store staff, trigger retry logic
|
||||
|
||||
customer.subscription.deleted
|
||||
|
||||
Update enrollment/rental status to cancelled
|
||||
|
||||
payment_intent.succeeded
|
||||
|
||||
Confirm POS transaction, update inventory status
|
||||
|
||||
payment_intent.payment_failed
|
||||
|
||||
Revert cart, notify staff
|
||||
|
||||
charge.dispute.created
|
||||
|
||||
Alert store management, flag transaction
|
||||
|
||||
|
||||
|
||||
# 5. AIM Migration — Payment Transition
|
||||
|
||||
## 5.1 The Problem
|
||||
|
||||
Existing AIM customers on recurring billing (rentals and lessons) are on a legacy payment processor. Stripe tokens are processor-specific — they cannot be transferred. Customers must re-enter their card to move to Stripe.
|
||||
|
||||
|
||||
|
||||
## 5.2 Transition Strategy
|
||||
|
||||
- New customers: go directly to Stripe from day one
|
||||
|
||||
- Migrated customers: flagged requires_payment_update = true on payment method
|
||||
|
||||
- At each monthly renewal point, system prompts staff to collect new card
|
||||
|
||||
- Customer re-enters card via Stripe Elements — new Stripe subscription created
|
||||
|
||||
- Old processor subscription cancelled after successful Stripe charge confirmed
|
||||
|
||||
- Both processors run in parallel until all active recurring billing migrated
|
||||
|
||||
|
||||
|
||||
## 5.3 Old Processor Wind-Down
|
||||
|
||||
- Do not close old processor account until 120 days after last transaction
|
||||
|
||||
- Chargebacks can arrive up to 120 days after transaction — account must remain open
|
||||
|
||||
- Keep old processor credentials in Secrets Manager until confirmed closed
|
||||
|
||||
- All historical transaction IDs preserved in database with processor field
|
||||
|
||||
|
||||
|
||||
# 6. Payment Provider Billing Ownership
|
||||
|
||||
The two supported payment processors have fundamentally different recurring billing models. This difference is the most important architectural consideration in the payment provider abstraction.
|
||||
|
||||
## 6.1 Stripe — Provider-Managed Billing
|
||||
|
||||
Stripe owns the billing schedule. When a rental or enrollment is activated, the platform creates a Stripe Subscription object. Stripe automatically charges the customer's payment method on each billing cycle. The platform learns about charges via webhooks (`invoice.paid`, `invoice.payment_failed`).
|
||||
|
||||
- Platform creates/cancels subscriptions — Stripe handles the schedule and charging
|
||||
- Stripe Card Updater silently refreshes expired cards — reduces failed payments
|
||||
- Proration calculated by Stripe when billing dates change or subscriptions are modified mid-cycle — platform does not need its own proration logic for Stripe stores
|
||||
- Platform is reactive — it responds to webhook events, not proactive billing
|
||||
|
||||
## 6.2 Global Payments — Platform-Managed Billing
|
||||
|
||||
Global Payments provides tokenized card storage and on-demand charge capability, but does not offer a subscription/recurring billing API. The platform must own the billing schedule entirely.
|
||||
|
||||
- Platform stores GP payment tokens on `account_payment_method`
|
||||
- BullMQ cron job runs daily — queries all rentals/enrollments with `billing_anchor_day = today` for GP stores
|
||||
- Job calls `provider.charge()` for each due item
|
||||
- Failed charges retry with exponential backoff (3 attempts over 7 days)
|
||||
- GP's Decline Minimizer helps reduce false declines but does not auto-update expired cards
|
||||
- Proration and partial-month charges calculated by the platform's billing service, not the processor
|
||||
- Mid-cycle additions, cancellations, and billing date changes all require the platform to compute prorated amounts and charge/credit accordingly
|
||||
|
||||
## 6.3 PaymentProvider Interface Impact
|
||||
|
||||
The `PaymentProvider` interface must account for this split:
|
||||
|
||||
- `managedSubscriptions: boolean` — Stripe: true, GP: false
|
||||
- If true: `createSubscription()`, `addSubscriptionItem()`, `removeSubscriptionItem()`, `cancelSubscription()`, `changeBillingAnchor()` — the platform delegates billing lifecycle to the provider
|
||||
- If false: `charge()` is the only billing method — the platform's BullMQ scheduler triggers charges on anchor days, handles retries, and manages the billing lifecycle internally
|
||||
- Both providers share: `createCustomer()`, `charge()`, `refund()`, `collectPayment()` (terminal), `parseWebhookEvent()`
|
||||
|
||||
## 6.4 Billing Service Routing
|
||||
|
||||
The billing service checks `store.payment_processor` to determine the flow:
|
||||
|
||||
- Stripe store — enrollment/rental activation calls `provider.createSubscription()`. Billing is hands-off after that.
|
||||
- GP store — enrollment/rental activation records the billing anchor day. The daily BullMQ job picks it up on the correct day and calls `provider.charge()`.
|
||||
- Both paths produce the same internal records (`rental_payment`, `lesson_payment`) and trigger the same accounting journal entries.
|
||||
|
||||
|
||||
|
||||
# 7. Database Schema
|
||||
|
||||
## 6.1 stripe_webhook_event
|
||||
|
||||
id, event_id (Stripe's ID), event_type, payload (jsonb),processed_at, status (received|processed|failed), error_message, created_at
|
||||
|
||||
All incoming webhook events are stored before processing. This enables replay if processing fails.
|
||||
BIN
planning/09_Domain_Batch_Repairs.docx
Normal file
BIN
planning/09_Domain_Batch_Repairs.docx
Normal file
Binary file not shown.
301
planning/09_Domain_Batch_Repairs.md
Normal file
301
planning/09_Domain_Batch_Repairs.md
Normal file
@@ -0,0 +1,301 @@
|
||||
Music Store Management Platform
|
||||
|
||||
Domain Design: Batch Repairs
|
||||
|
||||
|
||||
|
||||
# 1. Overview
|
||||
|
||||
The Batch Repairs domain handles bulk instrument repair jobs from schools and institutions. This workflow replaces the current spreadsheet-based tracking system. A batch groups multiple individual repair tickets under one job — one approval, one invoice, one delivery event — while still tracking each instrument individually.
|
||||
|
||||
|
||||
|
||||
# 2. Batch Lifecycle
|
||||
|
||||
Status
|
||||
|
||||
Description
|
||||
|
||||
scheduled
|
||||
|
||||
Pickup from school scheduled — instruments not yet received
|
||||
|
||||
picked_up
|
||||
|
||||
Store has collected all instruments from school
|
||||
|
||||
intake_complete
|
||||
|
||||
All instruments assessed, individual tickets created, estimates compiled
|
||||
|
||||
pending_approval
|
||||
|
||||
Batch estimate sent to school contact — awaiting authorization
|
||||
|
||||
approved
|
||||
|
||||
School approved — all child tickets authorized to proceed
|
||||
|
||||
in_progress
|
||||
|
||||
Work underway — child tickets at various stages
|
||||
|
||||
ready
|
||||
|
||||
All child tickets resolved — batch ready for delivery
|
||||
|
||||
delivered
|
||||
|
||||
All instruments returned to school, invoice sent
|
||||
|
||||
invoiced
|
||||
|
||||
Invoice sent to school — awaiting payment
|
||||
|
||||
paid
|
||||
|
||||
Payment received in full
|
||||
|
||||
closed
|
||||
|
||||
Batch complete and reconciled
|
||||
|
||||
|
||||
|
||||
# 3. Database Schema
|
||||
|
||||
## 3.1 repair_batch
|
||||
|
||||
Column
|
||||
|
||||
Type
|
||||
|
||||
Notes
|
||||
|
||||
id
|
||||
|
||||
uuid PK
|
||||
|
||||
|
||||
|
||||
company_id
|
||||
|
||||
uuid FK
|
||||
|
||||
|
||||
|
||||
batch_number
|
||||
|
||||
varchar
|
||||
|
||||
Human-readable batch ID e.g. BATCH-2024-0042
|
||||
|
||||
account_id
|
||||
|
||||
uuid FK
|
||||
|
||||
School/institution account
|
||||
|
||||
contact_name
|
||||
|
||||
varchar
|
||||
|
||||
School contact person for this batch
|
||||
|
||||
contact_phone
|
||||
|
||||
varchar
|
||||
|
||||
|
||||
|
||||
contact_email
|
||||
|
||||
varchar
|
||||
|
||||
Where to send estimates and invoices
|
||||
|
||||
school_po_number
|
||||
|
||||
varchar
|
||||
|
||||
School's purchase order number if applicable
|
||||
|
||||
status
|
||||
|
||||
enum
|
||||
|
||||
See lifecycle above
|
||||
|
||||
scheduled_pickup_date
|
||||
|
||||
date
|
||||
|
||||
When store plans to collect instruments
|
||||
|
||||
actual_pickup_date
|
||||
|
||||
date
|
||||
|
||||
When store actually collected
|
||||
|
||||
promised_return_date
|
||||
|
||||
date
|
||||
|
||||
Committed return date to school
|
||||
|
||||
actual_return_date
|
||||
|
||||
date
|
||||
|
||||
When instruments actually returned
|
||||
|
||||
instrument_count
|
||||
|
||||
integer
|
||||
|
||||
Expected number of instruments
|
||||
|
||||
received_count
|
||||
|
||||
integer
|
||||
|
||||
Actual count received at pickup
|
||||
|
||||
total_estimated
|
||||
|
||||
numeric(10,2)
|
||||
|
||||
Sum of all child ticket estimates
|
||||
|
||||
total_actual
|
||||
|
||||
numeric(10,2)
|
||||
|
||||
Sum of all child ticket actual costs
|
||||
|
||||
approval_date
|
||||
|
||||
date
|
||||
|
||||
When school approved the estimate
|
||||
|
||||
approved_by
|
||||
|
||||
varchar
|
||||
|
||||
Name/signature of school approver
|
||||
|
||||
notes
|
||||
|
||||
text
|
||||
|
||||
Internal notes
|
||||
|
||||
legacy_id
|
||||
|
||||
varchar
|
||||
|
||||
Reference to source spreadsheet row/ID if migrated
|
||||
|
||||
created_at
|
||||
|
||||
timestamptz
|
||||
|
||||
|
||||
|
||||
updated_at
|
||||
|
||||
timestamptz
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# 4. Batch Business Rules
|
||||
|
||||
- Batch status auto-advances to 'ready' when all child repair_ticket records reach 'ready' or 'cancelled'
|
||||
|
||||
- Batch approval cascades to all child tickets — sets each to 'approved' status
|
||||
|
||||
- Individual tickets within a batch can still be worked and tracked independently
|
||||
|
||||
- Total estimated and total actual are computed from child tickets — not manually entered
|
||||
|
||||
- School contact can differ from account primary contact — stored per batch
|
||||
|
||||
- School PO number is stored for institutional billing requirements
|
||||
|
||||
- Cancelled child tickets excluded from invoice total but noted on batch summary
|
||||
|
||||
- Instrument count vs received count discrepancy flagged at pickup — requires staff acknowledgement
|
||||
|
||||
|
||||
|
||||
# 5. Batch Workflow
|
||||
|
||||
## 5.1 Creating a Batch
|
||||
|
||||
- Staff creates batch record — links to school account, sets contact, expected count, scheduled pickup date
|
||||
|
||||
- Batch number auto-generated in sequence
|
||||
|
||||
- Delivery event created simultaneously for the pickup leg (see Delivery domain)
|
||||
|
||||
|
||||
|
||||
## 5.2 Pickup & Intake
|
||||
|
||||
- Driver collects instruments from school — delivery event records condition of each at pickup
|
||||
|
||||
- Back at store, staff creates individual repair_ticket for each instrument
|
||||
|
||||
- Each ticket linked to repair_batch_id
|
||||
|
||||
- Technician assesses each instrument — sets estimated_cost per ticket
|
||||
|
||||
- Once all tickets have estimates, batch status moves to 'pending_approval'
|
||||
|
||||
- Batch estimate report generated — itemized list of all instruments and estimated costs
|
||||
|
||||
|
||||
|
||||
## 5.3 Approval
|
||||
|
||||
- Estimate sent to school contact via email (PDF attachment)
|
||||
|
||||
- School approves — approval date and approver name recorded on batch
|
||||
|
||||
- All child tickets set to 'approved' — work begins
|
||||
|
||||
- School may reject specific instruments — those tickets set to 'cancelled', excluded from work
|
||||
|
||||
|
||||
|
||||
## 5.4 Invoicing
|
||||
|
||||
- Batch invoice generated when status reaches 'delivered'
|
||||
|
||||
- Invoice shows each instrument, work performed, parts, labor — itemized
|
||||
|
||||
- Total compared to estimate — any variance noted
|
||||
|
||||
- Invoice sent to school contact email and available in customer portal
|
||||
|
||||
- School PO number printed on invoice if provided
|
||||
|
||||
|
||||
|
||||
# 6. Reporting
|
||||
|
||||
- Batch repair revenue by school/account
|
||||
|
||||
- Average turnaround time per batch
|
||||
|
||||
- Estimate vs actual variance report
|
||||
|
||||
- Instruments per batch — count and breakdown by work type
|
||||
|
||||
- Outstanding batch invoices (accounts receivable)
|
||||
|
||||
- Technician productivity across batch and individual repairs
|
||||
BIN
planning/10_Domain_Delivery_Chain_of_Custody.docx
Normal file
BIN
planning/10_Domain_Delivery_Chain_of_Custody.docx
Normal file
Binary file not shown.
327
planning/10_Domain_Delivery_Chain_of_Custody.md
Normal file
327
planning/10_Domain_Delivery_Chain_of_Custody.md
Normal file
@@ -0,0 +1,327 @@
|
||||
Music Store Management Platform
|
||||
|
||||
Domain Design: Delivery & Chain of Custody
|
||||
|
||||
|
||||
|
||||
# 1. Overview
|
||||
|
||||
The Delivery domain tracks the physical movement of instruments between the store and customers or institutions. It covers all four handoff types — store picking up from customer, store delivering to customer, customer dropping off at store, and customer picking up from store. Every instrument movement is recorded with condition, timestamp, responsible party, and recipient signature.
|
||||
|
||||
|
||||
|
||||
This domain is primarily used by the batch repair workflow for school instrument pickups and deliveries, but applies to any scenario where the store transports instruments.
|
||||
|
||||
|
||||
|
||||
# 2. Event Types
|
||||
|
||||
Event Type
|
||||
|
||||
Description
|
||||
|
||||
store_pickup
|
||||
|
||||
Store driver goes to customer/school location and collects instruments
|
||||
|
||||
store_delivery
|
||||
|
||||
Store driver brings repaired instruments to customer/school location
|
||||
|
||||
customer_dropoff
|
||||
|
||||
Customer brings instrument(s) into the store
|
||||
|
||||
customer_pickup
|
||||
|
||||
Customer comes to store to collect repaired instrument(s)
|
||||
|
||||
|
||||
|
||||
# 3. Database Schema
|
||||
|
||||
## 3.1 delivery_event
|
||||
|
||||
Column
|
||||
|
||||
Type
|
||||
|
||||
Notes
|
||||
|
||||
id
|
||||
|
||||
uuid PK
|
||||
|
||||
|
||||
|
||||
company_id
|
||||
|
||||
uuid FK
|
||||
|
||||
Tenant scoping
|
||||
|
||||
location_id
|
||||
|
||||
uuid FK
|
||||
|
||||
Physical location originating or receiving the delivery
|
||||
|
||||
event_number
|
||||
|
||||
varchar
|
||||
|
||||
Human-readable ID e.g. DEL-2024-0018
|
||||
|
||||
event_type
|
||||
|
||||
enum
|
||||
|
||||
store_pickup|store_delivery|customer_dropoff|customer_pickup
|
||||
|
||||
repair_batch_id
|
||||
|
||||
uuid FK
|
||||
|
||||
Nullable — links to batch if applicable
|
||||
|
||||
account_id
|
||||
|
||||
uuid FK
|
||||
|
||||
Customer/school account
|
||||
|
||||
status
|
||||
|
||||
enum
|
||||
|
||||
scheduled|in_transit|completed|cancelled
|
||||
|
||||
scheduled_date
|
||||
|
||||
date
|
||||
|
||||
Planned date
|
||||
|
||||
scheduled_time_window
|
||||
|
||||
varchar
|
||||
|
||||
e.g. '8am-10am' — for school scheduling
|
||||
|
||||
actual_datetime
|
||||
|
||||
timestamptz
|
||||
|
||||
When event actually occurred
|
||||
|
||||
address
|
||||
|
||||
jsonb
|
||||
|
||||
Location — may differ from account address
|
||||
|
||||
driver_employee_id
|
||||
|
||||
uuid FK
|
||||
|
||||
Store employee performing pickup/delivery
|
||||
|
||||
recipient_name
|
||||
|
||||
varchar
|
||||
|
||||
Name of person who received/released instruments
|
||||
|
||||
recipient_signature
|
||||
|
||||
text
|
||||
|
||||
Base64 signature capture from iOS app
|
||||
|
||||
recipient_signed_at
|
||||
|
||||
timestamptz
|
||||
|
||||
When signature was captured
|
||||
|
||||
instrument_count_expected
|
||||
|
||||
integer
|
||||
|
||||
How many instruments expected in this event
|
||||
|
||||
instrument_count_actual
|
||||
|
||||
integer
|
||||
|
||||
How many actually transferred — discrepancy flagged
|
||||
|
||||
notes
|
||||
|
||||
text
|
||||
|
||||
Driver/staff notes
|
||||
|
||||
created_at
|
||||
|
||||
timestamptz
|
||||
|
||||
|
||||
|
||||
updated_at
|
||||
|
||||
timestamptz
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 3.2 delivery_event_item
|
||||
|
||||
Records each individual instrument included in a delivery event with condition at time of transfer.
|
||||
|
||||
Column
|
||||
|
||||
Type
|
||||
|
||||
Notes
|
||||
|
||||
id
|
||||
|
||||
uuid PK
|
||||
|
||||
|
||||
|
||||
delivery_event_id
|
||||
|
||||
uuid FK
|
||||
|
||||
|
||||
|
||||
repair_ticket_id
|
||||
|
||||
uuid FK
|
||||
|
||||
Which repair ticket this instrument belongs to
|
||||
|
||||
inventory_unit_id
|
||||
|
||||
uuid FK
|
||||
|
||||
Nullable — if instrument is in system
|
||||
|
||||
instrument_description
|
||||
|
||||
varchar
|
||||
|
||||
Free text for unrecognized instruments
|
||||
|
||||
serial_number
|
||||
|
||||
varchar
|
||||
|
||||
If known
|
||||
|
||||
condition
|
||||
|
||||
enum
|
||||
|
||||
excellent|good|fair|poor|damaged
|
||||
|
||||
condition_notes
|
||||
|
||||
text
|
||||
|
||||
Detailed condition description at this handoff
|
||||
|
||||
photo_urls
|
||||
|
||||
text[]
|
||||
|
||||
S3 URLs of condition photos taken at handoff
|
||||
|
||||
was_transferred
|
||||
|
||||
boolean
|
||||
|
||||
False if instrument was on manifest but not present
|
||||
|
||||
missing_notes
|
||||
|
||||
text
|
||||
|
||||
Explanation if was_transferred = false
|
||||
|
||||
created_at
|
||||
|
||||
timestamptz
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# 4. Chain of Custody
|
||||
|
||||
The full custody history of any instrument can be reconstructed by querying delivery_event_item records for a given repair_ticket_id or inventory_unit_id, ordered by created_at. This provides a complete timeline:
|
||||
|
||||
|
||||
|
||||
Example custody chain for a school instrument:1. store_pickup → 2024-09-05 08:30 Driver: J. Smith Condition: good Received from: Lincoln Middle School, signed by: M. Johnson2. [repair work at store]3. store_delivery → 2024-09-12 09:15 Driver: J. Smith Condition: excellent Delivered to: Lincoln Middle School, signed by: M. Johnson
|
||||
|
||||
|
||||
|
||||
# 5. Business Rules
|
||||
|
||||
- Every instrument transfer must have a delivery_event_item record — no undocumented transfers
|
||||
|
||||
- Condition documented at every handoff — incoming and outgoing
|
||||
|
||||
- Photo capture recommended at pickup — particularly for school batch repairs
|
||||
|
||||
- Signature capture required for store_pickup and store_delivery events
|
||||
|
||||
- Instrument count discrepancy between expected and actual requires staff notes before completing event
|
||||
|
||||
- Missing instrument (was_transferred = false) triggers alert to store manager
|
||||
|
||||
- Delivery event completion automatically updates linked repair_ticket status to 'delivered'
|
||||
|
||||
- Batch delivery event completion triggers batch status update to 'delivered'
|
||||
|
||||
- Repair ticket cannot be invoiced until delivery_event_item shows was_transferred = true
|
||||
|
||||
|
||||
|
||||
# 6. iOS App Integration
|
||||
|
||||
The delivery workflow is a primary use case for the iOS mobile app. The driver uses the app at the school location to:
|
||||
|
||||
- Pull up the scheduled delivery event
|
||||
|
||||
- Check off each instrument as it is received or handed over
|
||||
|
||||
- Record condition and take photos for each instrument
|
||||
|
||||
- Capture recipient signature on screen
|
||||
|
||||
- Complete the event — syncs to backend immediately or queues for sync if offline
|
||||
|
||||
|
||||
|
||||
Offline support is important here — schools may have poor cellular connectivity. The app queues all event data locally and syncs when the driver returns to a connected area.
|
||||
|
||||
|
||||
|
||||
# 7. Reporting
|
||||
|
||||
- Delivery schedule — upcoming pickups and deliveries by date
|
||||
|
||||
- Driver activity log — events completed per employee
|
||||
|
||||
- Outstanding deliveries — instruments at store awaiting return to customer
|
||||
|
||||
- Instrument location report — where is each instrument right now
|
||||
|
||||
- Condition change report — instruments that arrived in worse condition than sent
|
||||
|
||||
- Signature audit — all signed receipts by account and date
|
||||
BIN
planning/11_Domain_Billing_Date_Management.docx
Normal file
BIN
planning/11_Domain_Billing_Date_Management.docx
Normal file
Binary file not shown.
403
planning/11_Domain_Billing_Date_Management.md
Normal file
403
planning/11_Domain_Billing_Date_Management.md
Normal file
@@ -0,0 +1,403 @@
|
||||
Music Store Management Platform
|
||||
|
||||
Domain Design: Billing Date Management
|
||||
|
||||
Version 1.0 | Draft
|
||||
|
||||
|
||||
|
||||
# 1. Overview
|
||||
|
||||
Billing date management covers how recurring subscription charges are scheduled, how customers request date changes, and how the system handles proration when dates shift. This applies to both lesson enrollments and instrument rentals.
|
||||
|
||||
|
||||
|
||||
Stripe owns the billing schedule via the billing_cycle_anchor on each subscription. The application manages the billing_anchor_day preference in its own database and calls Stripe's API to apply changes. All date changes are fully audited and reversible by creating a new change entry.
|
||||
|
||||
|
||||
|
||||
# 2. Billing Anchor Day
|
||||
|
||||
The billing anchor day is the day of month a subscription renews and the customer is charged. It is capped at day 28 to avoid issues with February and months shorter than 31 days. Customers requesting the 29th, 30th, or 31st are set to the 28th with a staff note explaining why.
|
||||
|
||||
|
||||
|
||||
Scenario
|
||||
|
||||
Behavior
|
||||
|
||||
New enrollment / rental
|
||||
|
||||
Default anchor = day of start date, capped at 28
|
||||
|
||||
Customer requests change
|
||||
|
||||
Staff updates anchor — Stripe prorates, new cycle begins on new day
|
||||
|
||||
Consolidating subscriptions
|
||||
|
||||
One anchor adjusted to match other — proration applied to adjusted subscription
|
||||
|
||||
Splitting consolidated billing
|
||||
|
||||
Line item removed, new standalone subscription created on requested day
|
||||
|
||||
AIM migration
|
||||
|
||||
Anchor set to match existing AIM billing date where known
|
||||
|
||||
Paused subscription
|
||||
|
||||
Change applied immediately, no proration — takes effect when subscription resumes
|
||||
|
||||
|
||||
|
||||
# 3. Schema
|
||||
|
||||
## 3.1 rental and enrollment — additional columns
|
||||
|
||||
These four columns are added to both the rental and enrollment tables:
|
||||
|
||||
Column
|
||||
|
||||
Type
|
||||
|
||||
Notes
|
||||
|
||||
billing_anchor_day
|
||||
|
||||
integer
|
||||
|
||||
Day of month (1–28) charge occurs
|
||||
|
||||
billing_anchor_changed_at
|
||||
|
||||
timestamptz
|
||||
|
||||
When anchor was last changed
|
||||
|
||||
billing_anchor_changed_by
|
||||
|
||||
uuid FK
|
||||
|
||||
Employee who made the change
|
||||
|
||||
billing_anchor_change_reason
|
||||
|
||||
text
|
||||
|
||||
Required justification — mandatory field
|
||||
|
||||
|
||||
|
||||
## 3.2 billing_anchor_change_log
|
||||
|
||||
Append-only audit log of every billing date change. Records are never updated or deleted.
|
||||
|
||||
Column
|
||||
|
||||
Type
|
||||
|
||||
Notes
|
||||
|
||||
id
|
||||
|
||||
uuid PK
|
||||
|
||||
|
||||
|
||||
company_id
|
||||
|
||||
uuid FK
|
||||
|
||||
|
||||
|
||||
entity_type
|
||||
|
||||
enum
|
||||
|
||||
rental | enrollment
|
||||
|
||||
entity_id
|
||||
|
||||
uuid
|
||||
|
||||
rental.id or enrollment.id
|
||||
|
||||
account_id
|
||||
|
||||
uuid FK
|
||||
|
||||
For reporting
|
||||
|
||||
stripe_subscription_id
|
||||
|
||||
varchar
|
||||
|
||||
Stripe subscription affected
|
||||
|
||||
previous_anchor_day
|
||||
|
||||
integer
|
||||
|
||||
Old billing day
|
||||
|
||||
new_anchor_day
|
||||
|
||||
integer
|
||||
|
||||
New billing day
|
||||
|
||||
proration_amount
|
||||
|
||||
numeric(10,2)
|
||||
|
||||
Credit or charge applied — sourced from Stripe response
|
||||
|
||||
proration_direction
|
||||
|
||||
enum
|
||||
|
||||
credit | charge | none
|
||||
|
||||
subscription_was_paused
|
||||
|
||||
boolean
|
||||
|
||||
True if change made while subscription paused
|
||||
|
||||
changed_by
|
||||
|
||||
uuid FK
|
||||
|
||||
Employee who made the change
|
||||
|
||||
reason
|
||||
|
||||
text
|
||||
|
||||
Required justification
|
||||
|
||||
stripe_invoice_id
|
||||
|
||||
varchar
|
||||
|
||||
Proration invoice from Stripe if applicable
|
||||
|
||||
bulk_change_id
|
||||
|
||||
uuid
|
||||
|
||||
Groups entries from a single bulk change operation
|
||||
|
||||
created_at
|
||||
|
||||
timestamptz
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# 4. Proration Logic
|
||||
|
||||
When a billing anchor day changes mid-cycle Stripe calculates the proration automatically. The application captures the result and records it in billing_anchor_change_log. Staff should preview the proration amount before confirming the change.
|
||||
|
||||
|
||||
|
||||
## 4.1 Moving to an Earlier Day
|
||||
|
||||
Example: Currently billed on the 20th, customer requests the 5th.Today is the 12th. Monthly rate: $50.Customer has paid through the 20th.New cycle: 12th → 5th of next month (24 days).Stripe issues a proration credit for unused days already paid.New cycle begins on the 5th of next month.
|
||||
|
||||
|
||||
|
||||
## 4.2 Moving to a Later Day
|
||||
|
||||
Example: Currently billed on the 5th, customer requests the 20th.Today is the 12th. Monthly rate: $50.Customer has paid through the 5th.Extended period: 5th → 20th = 15 extra days.Stripe issues a proration charge for the extended period.New cycle begins on the 20th.
|
||||
|
||||
|
||||
|
||||
## 4.3 Consolidating Two Subscriptions
|
||||
|
||||
Example: Rental A billed on the 5th, Rental B billed on the 20th.Staff consolidates — both will bill on the 5th.Rental B subscription anchor updated to 5th.Proration applied to Rental B for the period shift.Rental B added as line item to Rental A's subscription.Original Rental B standalone subscription cancelled.Both now charge together on the 5th.
|
||||
|
||||
|
||||
|
||||
## 4.4 Splitting a Consolidated Subscription
|
||||
|
||||
Example: Rental A and Rental B both on the 5th (consolidated).Customer wants Rental B moved to the 20th.Rental B line item removed from consolidated subscription.New standalone subscription created for Rental B, anchor = 20th.Proration applied for Rental B's partial period.Rental A continues unchanged on the 5th.
|
||||
|
||||
|
||||
|
||||
# 5. Edge Cases
|
||||
|
||||
## 5.1 Pending Invoice Within 48 Hours of Billing
|
||||
|
||||
If a billing date change is requested within 48 hours of the current anchor day Stripe may already be generating the upcoming invoice. The system must handle this explicitly.
|
||||
|
||||
- System checks for pending Stripe invoices before allowing anchor change
|
||||
|
||||
- If pending invoice exists within 48-hour window, staff is shown a warning
|
||||
|
||||
- Staff can choose to: wait until after invoice clears, or proceed and void/recreate the pending invoice
|
||||
|
||||
- Voiding a pending invoice requires manager approval — logged in audit trail
|
||||
|
||||
- Recommended default: block change until after billing date passes, then apply
|
||||
|
||||
|
||||
|
||||
## 5.2 Failed Payment Then Date Change
|
||||
|
||||
If a payment has failed and the customer calls to request a billing date change, the outstanding balance must be resolved first.
|
||||
|
||||
- System checks for outstanding failed invoices on account before allowing anchor change
|
||||
|
||||
- If failed invoice exists, staff is blocked from changing anchor until resolved
|
||||
|
||||
- Resolution options: collect payment on failed invoice, write off with manager approval, or payment plan
|
||||
|
||||
- Once resolved, anchor change proceeds normally
|
||||
|
||||
- Prevents customers from using date changes to evade failed payment follow-up
|
||||
|
||||
|
||||
|
||||
## 5.3 Paused Subscriptions
|
||||
|
||||
If a lesson or rental subscription is paused when a date change is requested, the change is applied immediately but no proration is generated since no active billing is occurring.
|
||||
|
||||
- Anchor day updated in database immediately
|
||||
|
||||
- Stripe subscription anchor updated via API
|
||||
|
||||
- proration_direction = 'none' recorded in change log
|
||||
|
||||
- subscription_was_paused = true flagged in log for clarity
|
||||
|
||||
- When subscription resumes it bills on the new anchor day
|
||||
|
||||
|
||||
|
||||
## 5.4 Rent-to-Own Equity on Date Change
|
||||
|
||||
Equity in a rent-to-own rental is calculated per payment received, not per calendar month. A billing date change shifts when the next payment posts but does not affect total equity earned or the buyout calculation.
|
||||
|
||||
- Equity per payment = monthly_rate × rto_equity_percent — unchanged by date shift
|
||||
|
||||
- rto_equity_accumulated updated after each successful Stripe payment webhook
|
||||
|
||||
- If customer is within one payment of buyout eligibility, staff should be notified at time of date change
|
||||
|
||||
- Buyout amount remains: rto_purchase_price minus rto_equity_accumulated
|
||||
|
||||
|
||||
|
||||
## 5.5 Bulk Date Change — Multiple Subscriptions
|
||||
|
||||
A parent account with multiple children may have several lesson and rental subscriptions all billing on different dates. The system supports changing all subscriptions on an account to a single anchor day in one operation.
|
||||
|
||||
|
||||
|
||||
### Bulk Change Preview Screen
|
||||
|
||||
Before confirming a bulk change, staff is shown a summary screen displaying:
|
||||
|
||||
- Each subscription being changed — entity type, description, current anchor, new anchor
|
||||
|
||||
- Proration amount per subscription — credit or charge
|
||||
|
||||
- Net total proration across all subscriptions — credit or charge to customer
|
||||
|
||||
- Warning if any subscription has a pending invoice or failed payment
|
||||
|
||||
- Confirmation required before any Stripe API calls are made
|
||||
|
||||
|
||||
|
||||
### Bulk Change Execution
|
||||
|
||||
- All changes grouped under a shared bulk_change_id in billing_anchor_change_log
|
||||
|
||||
- Stripe API calls made sequentially — not in parallel to avoid rate limit issues
|
||||
|
||||
- If any Stripe call fails, completed changes are rolled back via Stripe API
|
||||
|
||||
- Staff shown which subscriptions succeeded and which failed if partial failure occurs
|
||||
|
||||
- Full rollback preferred — partial bulk changes create confusion for customer billing
|
||||
|
||||
|
||||
|
||||
### Bulk Change Schema
|
||||
|
||||
The bulk_change_id UUID is generated at the start of the operation and written to every billing_anchor_change_log entry created during that operation. This allows the full bulk change to be queried and audited as a single unit.
|
||||
|
||||
|
||||
|
||||
# 6. Business Rules
|
||||
|
||||
- Billing anchor day must be between 1 and 28 inclusive — enforced at API and UI level
|
||||
|
||||
- Requests for 29th, 30th, 31st are automatically capped to 28th with staff notification
|
||||
|
||||
- Every anchor change requires a reason — reason field is mandatory
|
||||
|
||||
- Change log record written before Stripe API call — if Stripe fails, log is rolled back
|
||||
|
||||
- Failed payment on account blocks anchor change until resolved
|
||||
|
||||
- Pending invoice within 48 hours triggers warning — staff must acknowledge before proceeding
|
||||
|
||||
- Bulk changes require preview confirmation before execution
|
||||
|
||||
- Bulk changes rolled back fully if any subscription fails
|
||||
|
||||
- Paused subscription date changes produce no proration
|
||||
|
||||
- Rent-to-own equity is unaffected by date changes
|
||||
|
||||
- All change log records are immutable — no updates or deletes permitted
|
||||
|
||||
|
||||
|
||||
# 7. API Operations
|
||||
|
||||
Endpoint
|
||||
|
||||
Description
|
||||
|
||||
GET /billing/anchor/preview
|
||||
|
||||
Preview proration for proposed anchor change — no changes made
|
||||
|
||||
POST /billing/anchor/change
|
||||
|
||||
Execute single anchor change for one rental or enrollment
|
||||
|
||||
GET /billing/anchor/bulk-preview
|
||||
|
||||
Preview proration summary for bulk change across all account subscriptions
|
||||
|
||||
POST /billing/anchor/bulk-change
|
||||
|
||||
Execute bulk anchor change for all selected subscriptions on account
|
||||
|
||||
GET /billing/anchor/history/:entityId
|
||||
|
||||
Return full anchor change history for a rental or enrollment
|
||||
|
||||
|
||||
|
||||
# 8. Reporting
|
||||
|
||||
- Billing date changes by employee — frequency and reason breakdown
|
||||
|
||||
- Proration credits issued by month — financial impact tracking
|
||||
|
||||
- Proration charges issued by month
|
||||
|
||||
- Accounts with frequent date changes — potential indicator of billing issues
|
||||
|
||||
- Bulk changes log — full history of multi-subscription operations
|
||||
|
||||
- Failed payment + date change attempts — flagged for manager review
|
||||
BIN
planning/12_Domain_Accounting_Journal_Entries.docx
Normal file
BIN
planning/12_Domain_Accounting_Journal_Entries.docx
Normal file
Binary file not shown.
1219
planning/12_Domain_Accounting_Journal_Entries.md
Normal file
1219
planning/12_Domain_Accounting_Journal_Entries.md
Normal file
File diff suppressed because it is too large
Load Diff
BIN
planning/13_Deployment_Compliance_Pricing_Support.docx
Normal file
BIN
planning/13_Deployment_Compliance_Pricing_Support.docx
Normal file
Binary file not shown.
879
planning/13_Deployment_Compliance_Pricing_Support.md
Normal file
879
planning/13_Deployment_Compliance_Pricing_Support.md
Normal file
@@ -0,0 +1,879 @@
|
||||
Music Store Management Platform
|
||||
|
||||
Deployment, Compliance, Pricing, Updates & Support
|
||||
|
||||
Version 1.0 | Draft
|
||||
|
||||
|
||||
|
||||
# 1. Deployment Models
|
||||
|
||||
The platform supports two deployment models. Customers choose based on their infrastructure preferences, IT capability, budget, and data sovereignty requirements. The core application codebase is identical — deployment configuration differs.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
SaaS (Cloud Hosted)
|
||||
|
||||
Self-Hosted (On-Premise)
|
||||
|
||||
Infrastructure
|
||||
|
||||
AWS — vendor managed
|
||||
|
||||
Customer's own hardware
|
||||
|
||||
Updates
|
||||
|
||||
Automatic — zero customer action
|
||||
|
||||
One-click via admin panel
|
||||
|
||||
Uptime responsibility
|
||||
|
||||
Vendor (99.9% SLA)
|
||||
|
||||
Customer's IT / hardware
|
||||
|
||||
Backup
|
||||
|
||||
Vendor managed — automatic
|
||||
|
||||
Customer managed + optional cloud backup add-on
|
||||
|
||||
Pricing model
|
||||
|
||||
Monthly subscription
|
||||
|
||||
One-time license + annual maintenance
|
||||
|
||||
Internet required
|
||||
|
||||
Yes — always online
|
||||
|
||||
Local network only — internet optional
|
||||
|
||||
Data location
|
||||
|
||||
AWS us-east-1 (default)
|
||||
|
||||
Customer's premises
|
||||
|
||||
Best for
|
||||
|
||||
Stores without IT staff, smaller operations
|
||||
|
||||
Stores with IT, internet-sensitive, AIM migrants
|
||||
|
||||
|
||||
|
||||
## 1.1 SaaS Architecture
|
||||
|
||||
Multi-tenant deployment on AWS EKS. Each company is a tenant — data is logically separated by company_id on all tables. Physical locations within a company are distinguished by location_id on inventory, transaction, cash drawer, and delivery tables. Infrastructure is shared but data is never commingled in queries. Separate Aurora database clusters available as a premium option for large customers requiring hard data isolation.
|
||||
|
||||
|
||||
|
||||
AWS EKS (Kubernetes) └── API pods (auto-scaling) └── Admin frontend pod └── Customer portal podAurora PostgreSQL (Multi-tenant, company_id + location_id scoped)ElastiCache Redis (session, queues)S3 (PDFs, backups, exports)CloudFront (static assets)Route53 (DNS — customer.platform.com subdomains)
|
||||
|
||||
|
||||
|
||||
## 1.2 Self-Hosted Architecture
|
||||
|
||||
Single-tenant Docker Compose deployment. Designed to run on a modest mini PC or small server on the store's local network. Electron desktop app and iOS app connect to the local API over LAN. Remote access optional via VPN.
|
||||
|
||||
|
||||
|
||||
Recommended hardware: Mini PC (Intel NUC or equivalent) CPU: 4-core, RAM: 16GB, Storage: 500GB SSD OS: Ubuntu 24 LTS Estimated hardware cost: $300-600Docker Compose stack: └── api (Node.js / Python API) └── postgres (PostgreSQL 16) └── redis (Redis 7) └── admin (Admin frontend — Nginx) └── portal (Customer portal — Nginx) └── nginx (Reverse proxy, SSL termination) └── updater (Update checker service) └── backup (Automated local backup service)
|
||||
|
||||
|
||||
|
||||
Installer script handles Docker installation, initial configuration, SSL certificate generation (self-signed or Let's Encrypt), and first-run database setup. Target setup time under 30 minutes for a competent IT person.
|
||||
|
||||
|
||||
|
||||
## 1.3 Offline Resilience (Both Models)
|
||||
|
||||
POS operations are the most critical function. Both deployment models support degraded offline operation to prevent a total outage from stopping the store.
|
||||
|
||||
- Desktop app caches current inventory, customer list, and open tickets locally
|
||||
|
||||
- Cash and card-keyed sales can be queued locally if backend unreachable
|
||||
|
||||
- Stripe Terminal card-present payments require internet — clear staff messaging when offline
|
||||
|
||||
- Sync occurs automatically when connection restored — conflict resolution logged
|
||||
|
||||
- Self-hosted: local network outage only affects cloud backup and update checker — core POS unaffected
|
||||
|
||||
|
||||
|
||||
# 2. Update Mechanism
|
||||
|
||||
## 2.1 SaaS Updates
|
||||
|
||||
SaaS customers receive updates automatically. No customer action required. Updates deployed using rolling deployment on EKS — zero downtime. Database migrations run as part of the deployment pipeline and are always backward compatible.
|
||||
|
||||
|
||||
|
||||
- Minor updates (bug fixes, small features) deployed continuously
|
||||
|
||||
- Major updates announced via in-app notification 7 days in advance
|
||||
|
||||
- Breaking changes (schema changes, workflow changes) communicated with migration guides
|
||||
|
||||
- Rollback capability maintained for 24 hours after any deployment
|
||||
|
||||
- Maintenance windows scheduled 2am-4am local time for database migrations if required
|
||||
|
||||
|
||||
|
||||
## 2.2 Self-Hosted Updates
|
||||
|
||||
Self-hosted customers receive updates via an in-app update checker built into the admin panel. The updater service polls the vendor update registry on startup and daily thereafter.
|
||||
|
||||
|
||||
|
||||
### Update Flow
|
||||
|
||||
- Admin panel shows current version and available version when update exists
|
||||
|
||||
- Changelog displayed before update — store admin can review what changed
|
||||
|
||||
- One-click update: pulls new Docker images, runs database migrations, restarts services
|
||||
|
||||
- Automatic backup taken before every update — rollback available if update fails
|
||||
|
||||
- Update log stored locally — full history of applied updates
|
||||
|
||||
|
||||
|
||||
### Update Registry
|
||||
|
||||
Vendor hosts a simple update registry API: GET https://updates.platform.com/check ?version=1.4.2&license=LICENSE_KEY Returns: { latest: '1.5.0', required: false, changelog_url: '...', download_url: '...' }required: true = security update — admin warned, update strongly advisedrequired: false = optional update — admin chooses timing
|
||||
|
||||
|
||||
|
||||
### Database Migration Safety
|
||||
|
||||
- All migrations are additive — new columns, new tables, new indexes
|
||||
|
||||
- No destructive migrations (DROP COLUMN, DROP TABLE) without explicit data export first
|
||||
|
||||
- Migrations tested against production-size data snapshots before release
|
||||
|
||||
- Each migration includes a rollback script
|
||||
|
||||
- Migration history table tracks applied migrations — idempotent on re-run
|
||||
|
||||
|
||||
|
||||
## 2.3 Version Support Policy
|
||||
|
||||
Version Status
|
||||
|
||||
Policy
|
||||
|
||||
Current
|
||||
|
||||
Full support — all bugs fixed, all features available
|
||||
|
||||
Previous major
|
||||
|
||||
Security fixes only — 12 months after new major release
|
||||
|
||||
End of life
|
||||
|
||||
No support — customers on annual maintenance must update
|
||||
|
||||
|
||||
|
||||
# 3. Compliance & Certification
|
||||
|
||||
## 3.1 PCI DSS Compliance
|
||||
|
||||
Payment Card Industry Data Security Standard (PCI DSS) compliance is required for any business that handles card payments. The platform's architecture is specifically designed to minimize PCI scope.
|
||||
|
||||
|
||||
|
||||
Scenario
|
||||
|
||||
SAQ Level
|
||||
|
||||
Requirements
|
||||
|
||||
SaaS — Stripe Terminal + Elements
|
||||
|
||||
SAQ A
|
||||
|
||||
Self-assessment questionnaire only — no audit. Card data never touches platform servers.
|
||||
|
||||
Self-hosted — Stripe Terminal + Elements
|
||||
|
||||
SAQ A
|
||||
|
||||
Same as SaaS — Stripe handles all card data. Customer must secure their local network.
|
||||
|
||||
Any keyed entry NOT using Stripe Elements
|
||||
|
||||
SAQ D
|
||||
|
||||
Full audit required — avoided by design. Platform never implements raw card input fields.
|
||||
|
||||
|
||||
|
||||
SAQ A requirements for the platform vendor (Lunarfront Tech LLC):
|
||||
|
||||
- Never store, process, or transmit cardholder data on platform servers
|
||||
|
||||
- Maintain secure development practices — no card data in logs
|
||||
|
||||
- Annual SAQ A self-assessment completion
|
||||
|
||||
- Provide PCI compliance documentation to customers on request
|
||||
|
||||
- Self-hosted customers receive a PCI compliance guide covering their local environment requirements
|
||||
|
||||
|
||||
|
||||
## 3.2 SOC 2
|
||||
|
||||
Status
|
||||
|
||||
Priority
|
||||
|
||||
Notes
|
||||
|
||||
Not required at launch
|
||||
|
||||
Defer
|
||||
|
||||
SOC 2 Type II audit costs $20,000-50,000. Required only if large school districts or enterprise customers demand it. Revisit at 50+ customers.
|
||||
|
||||
|
||||
|
||||
## 3.3 Data Privacy — CCPA & GDPR
|
||||
|
||||
The platform stores customer PII including names, addresses, contact details, and payment references. Privacy obligations apply based on customer location.
|
||||
|
||||
|
||||
|
||||
Regulation
|
||||
|
||||
Applies
|
||||
|
||||
Requirements
|
||||
|
||||
CCPA
|
||||
|
||||
Yes
|
||||
|
||||
California Consumer Privacy Act — applies to any store with California customers. Right to deletion, data access requests, privacy policy required.
|
||||
|
||||
GDPR
|
||||
|
||||
Unlikely
|
||||
|
||||
EU General Data Protection Regulation — applies only if EU customers. US music stores unlikely. Monitor if international expansion.
|
||||
|
||||
|
||||
|
||||
### Required Privacy Controls
|
||||
|
||||
- Privacy policy published and linked from all customer-facing surfaces
|
||||
|
||||
- Data retention policy — define how long customer data is kept after account closure
|
||||
|
||||
- Right to deletion — PII nullification on customer records (append-only event sourcing uses nullification not deletion)
|
||||
|
||||
- Data access requests — ability to export all data for a customer on request
|
||||
|
||||
- Breach notification procedure — documented and tested
|
||||
|
||||
- Data processing agreements available for enterprise customers
|
||||
|
||||
|
||||
|
||||
## 3.4 Sales Tax on SaaS Fees
|
||||
|
||||
Selling SaaS subscriptions nationally creates sales tax nexus obligations in states where revenue thresholds are crossed. This applies to Lunarfront Tech LLC's own subscription fees — separate from sales tax the stores collect from their customers.
|
||||
|
||||
- Economic nexus thresholds vary by state — typically $100,000 revenue or 200 transactions
|
||||
|
||||
- Integrate TaxJar or Avalara for automated sales tax calculation on subscription invoices
|
||||
|
||||
- Register for sales tax collection in states where thresholds are crossed
|
||||
|
||||
- Texas (home state) requires registration from day one
|
||||
|
||||
- Revisit quarterly as revenue grows into new states
|
||||
|
||||
|
||||
|
||||
## 3.5 Music Industry — No Specific Certification Required
|
||||
|
||||
There are no industry-specific software certifications required to sell POS software to music retailers. NAMM (National Association of Music Merchants) is the primary trade organization — membership and NAMM show presence adds credibility but is not a certification or requirement.
|
||||
|
||||
|
||||
|
||||
## 3.6 Business Entity
|
||||
|
||||
- Lunarfront Tech LLC registered in Texas — covers software sales nationally
|
||||
|
||||
- Standard commercial software license agreement required for all customers
|
||||
|
||||
- Terms of service and privacy policy required before account activation
|
||||
|
||||
- Self-hosted customers sign a separate perpetual license agreement
|
||||
|
||||
|
||||
|
||||
# 4. Pricing
|
||||
|
||||
## 4.1 SaaS Subscription Pricing
|
||||
|
||||
Plan
|
||||
|
||||
Price
|
||||
|
||||
Includes
|
||||
|
||||
|
||||
|
||||
Starter
|
||||
|
||||
$149/mo
|
||||
|
||||
POS + inventory + customers + Stripe Terminal + basic reporting + cash drawer + email support
|
||||
|
||||
|
||||
|
||||
Standard
|
||||
|
||||
$249/mo
|
||||
|
||||
Everything in Starter + rentals + rent-to-own + lessons + scheduling + repairs + QuickBooks export + invoice printing + iOS convention app
|
||||
|
||||
Most stores
|
||||
|
||||
Professional
|
||||
|
||||
$349/mo
|
||||
|
||||
Everything in Standard + batch repairs + delivery tracking + school management + advanced accounting + multi-location + API access + priority support
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### SaaS Add-Ons
|
||||
|
||||
Add-On
|
||||
|
||||
Price
|
||||
|
||||
Notes
|
||||
|
||||
Additional location
|
||||
|
||||
+$99/mo
|
||||
|
||||
Per additional store location
|
||||
|
||||
Priority support upgrade
|
||||
|
||||
+$49/mo
|
||||
|
||||
4hr response + emergency POS-down line
|
||||
|
||||
AIM data migration
|
||||
|
||||
$500-1,500
|
||||
|
||||
One-time — based on data volume and complexity
|
||||
|
||||
Onboarding & training
|
||||
|
||||
$300
|
||||
|
||||
One-time — 3hr remote session covering all modules
|
||||
|
||||
|
||||
|
||||
## 4.2 Self-Hosted Licensing
|
||||
|
||||
License
|
||||
|
||||
Price
|
||||
|
||||
Includes
|
||||
|
||||
Standard License
|
||||
|
||||
$2,500
|
||||
|
||||
Perpetual license — Standard feature set. Software works indefinitely.
|
||||
|
||||
Professional License
|
||||
|
||||
$4,500
|
||||
|
||||
Perpetual license — Professional feature set including batch repairs, delivery, school management.
|
||||
|
||||
Annual Maintenance
|
||||
|
||||
20% of license
|
||||
|
||||
Updates + standard email support. Without maintenance, software still runs but receives no updates after year 1.
|
||||
|
||||
|
||||
|
||||
### Self-Hosted Add-Ons
|
||||
|
||||
Add-On
|
||||
|
||||
Price
|
||||
|
||||
Notes
|
||||
|
||||
Cloud backup
|
||||
|
||||
$29/mo
|
||||
|
||||
Encrypted backups to vendor S3 — disaster recovery
|
||||
|
||||
Priority support
|
||||
|
||||
$49/mo
|
||||
|
||||
4hr response + emergency POS-down line
|
||||
|
||||
AIM data migration
|
||||
|
||||
$500-1,500
|
||||
|
||||
One-time — remote migration assistance
|
||||
|
||||
Remote installation
|
||||
|
||||
$250
|
||||
|
||||
One-time — vendor installs and configures remotely
|
||||
|
||||
Onboarding & training
|
||||
|
||||
$300
|
||||
|
||||
One-time — 3hr remote training session
|
||||
|
||||
|
||||
|
||||
## 4.3 Unit Economics (SaaS)
|
||||
|
||||
Customers
|
||||
|
||||
Avg Revenue/Mo
|
||||
|
||||
MRR
|
||||
|
||||
AWS Est.
|
||||
|
||||
10
|
||||
|
||||
$249
|
||||
|
||||
$2,490
|
||||
|
||||
~$200
|
||||
|
||||
25
|
||||
|
||||
$280
|
||||
|
||||
$7,000
|
||||
|
||||
~$400
|
||||
|
||||
50
|
||||
|
||||
$290
|
||||
|
||||
$14,500
|
||||
|
||||
~$700
|
||||
|
||||
100
|
||||
|
||||
$300
|
||||
|
||||
$30,000
|
||||
|
||||
~$1,200
|
||||
|
||||
|
||||
|
||||
Average revenue per customer increases as stores upgrade plans and add locations. AWS costs scale sublinearly due to multi-tenant efficiency. Net margin at scale is 90%+ before support labor.
|
||||
|
||||
|
||||
|
||||
## 4.4 Launch Pricing Strategy
|
||||
|
||||
First 10 customers should receive significantly discounted or free access in exchange for being design partners. These stores will surface edge cases, validate workflows, and serve as reference customers for future sales.
|
||||
|
||||
- Beta program: free for 6 months, then standard pricing
|
||||
|
||||
- Beta customers get lifetime 20% discount as reference customer reward
|
||||
|
||||
- In exchange: monthly feedback calls, permission to use as case study, referral introductions
|
||||
|
||||
- Target beta customers: stores currently on AIM with pain around cloud access, modern hardware, or school batch repairs
|
||||
|
||||
|
||||
|
||||
# 5. Support Model
|
||||
|
||||
## 5.1 Support Philosophy
|
||||
|
||||
The best support interaction is one that never needs to happen. Investment in self-service documentation, resilient software design, and proactive monitoring reduces support volume. When support is needed, response time and resolution quality matter most — a POS system is critical business infrastructure.
|
||||
|
||||
|
||||
|
||||
## 5.2 Support Tiers by Plan
|
||||
|
||||
Plan
|
||||
|
||||
Channel
|
||||
|
||||
Response SLA
|
||||
|
||||
Notes
|
||||
|
||||
Starter
|
||||
|
||||
Email only
|
||||
|
||||
48 business hours
|
||||
|
||||
Acknowledged at signup — appropriate for low-volume stores
|
||||
|
||||
Standard
|
||||
|
||||
Email + ticket
|
||||
|
||||
Next business day
|
||||
|
||||
Business hours: Mon-Fri 8am-6pm CT
|
||||
|
||||
Professional
|
||||
|
||||
Priority queue
|
||||
|
||||
4 business hours
|
||||
|
||||
Emergency POS-down line for payment processing failures only
|
||||
|
||||
Self-hosted + maintenance
|
||||
|
||||
Email + ticket
|
||||
|
||||
Next business day
|
||||
|
||||
Hardware issues are customer's responsibility
|
||||
|
||||
Priority support add-on
|
||||
|
||||
Priority queue
|
||||
|
||||
4 business hours
|
||||
|
||||
Available as add-on for any plan
|
||||
|
||||
|
||||
|
||||
## 5.3 Emergency POS-Down Definition
|
||||
|
||||
Emergency support (outside business hours) is reserved for true POS-down situations only — defined as the store being completely unable to process any payments. This is the highest-severity scenario.
|
||||
|
||||
|
||||
|
||||
Scenario
|
||||
|
||||
Emergency?
|
||||
|
||||
Response
|
||||
|
||||
Platform completely unreachable
|
||||
|
||||
YES
|
||||
|
||||
Emergency line — target 1hr response
|
||||
|
||||
Card payments failing, cash works
|
||||
|
||||
NO
|
||||
|
||||
Next business day — workaround exists
|
||||
|
||||
Specific feature not working
|
||||
|
||||
NO
|
||||
|
||||
Standard SLA — not a POS-down situation
|
||||
|
||||
Report not generating
|
||||
|
||||
NO
|
||||
|
||||
Standard SLA
|
||||
|
||||
Self-hosted server down
|
||||
|
||||
NO
|
||||
|
||||
Customer's infrastructure — guide to troubleshooting provided
|
||||
|
||||
|
||||
|
||||
## 5.4 Self-Service Infrastructure
|
||||
|
||||
Self-service resources reduce support volume and empower stores to resolve common issues independently. All self-service content is built and maintained as part of the product.
|
||||
|
||||
|
||||
|
||||
### Knowledge Base
|
||||
|
||||
- Searchable help articles for every feature and workflow
|
||||
|
||||
- Step-by-step guides with screenshots for all major operations
|
||||
|
||||
- Troubleshooting guides for common error conditions
|
||||
|
||||
- AIM migration guide — specific help for stores transitioning from AIM
|
||||
|
||||
- Hosted at help.platform.com — accessible without logging in
|
||||
|
||||
|
||||
|
||||
### In-App Help
|
||||
|
||||
- Contextual help tooltips on complex fields and settings
|
||||
|
||||
- Inline error messages that explain what went wrong and how to fix it — never just error codes
|
||||
|
||||
- First-run guided setup wizard for new accounts
|
||||
|
||||
- Search bar in admin panel searches knowledge base without leaving the app
|
||||
|
||||
|
||||
|
||||
### Video Library
|
||||
|
||||
- Short (3-5 minute) walkthrough videos for each major module
|
||||
|
||||
- Onboarding series: first week, first month
|
||||
|
||||
- Hosted on YouTube (unlisted) — embedded in knowledge base
|
||||
|
||||
|
||||
|
||||
### Status Page
|
||||
|
||||
- Public status page at status.platform.com (statuspage.io or similar)
|
||||
|
||||
- Real-time uptime for all platform components
|
||||
|
||||
- Incident history and postmortems
|
||||
|
||||
- Email/SMS subscribe for outage notifications
|
||||
|
||||
- Stores check status page before submitting support tickets — reduces 'is it down?' tickets
|
||||
|
||||
|
||||
|
||||
## 5.5 Proactive Monitoring
|
||||
|
||||
Catching issues before customers report them is the most effective support strategy. Automated monitoring alerts the vendor immediately when problems occur.
|
||||
|
||||
- PagerDuty or OpsGenie — on-call alerting for platform incidents
|
||||
|
||||
- Synthetic monitoring — automated transaction tests run every 5 minutes against production
|
||||
|
||||
- Error rate alerts — spike in API errors triggers immediate investigation
|
||||
|
||||
- Stripe webhook failure alerts — missed webhooks flagged within minutes
|
||||
|
||||
- Database performance monitoring — slow queries identified before they cause user impact
|
||||
|
||||
- Self-hosted: optional telemetry (opt-in) sends health metrics to vendor for proactive support
|
||||
|
||||
|
||||
|
||||
## 5.6 Scaling Support as Customer Base Grows
|
||||
|
||||
Stage
|
||||
|
||||
Support Approach
|
||||
|
||||
0-20 customers
|
||||
|
||||
Solo — founder handles all support. Invest heavily in self-service documentation to reduce volume. Target < 2 tickets per customer per month.
|
||||
|
||||
20-50 customers
|
||||
|
||||
Hire part-time support contractor with music retail background. Train on platform. Handle tier 1 tickets (how-to questions, basic troubleshooting). Founder handles tier 2 (bugs, integrations).
|
||||
|
||||
50-100 customers
|
||||
|
||||
Full-time support hire or VAR (value-added reseller) partnerships. VARs handle implementation and support for a region in exchange for revenue share (typically 20-30%).
|
||||
|
||||
100+ customers
|
||||
|
||||
Dedicated support team. Community forum where experienced stores help newer ones. Formal partner program for VARs and consultants.
|
||||
|
||||
|
||||
|
||||
## 5.7 VAR / Partner Program
|
||||
|
||||
Value-added resellers are consultants or IT companies that sell, implement, and support the platform on behalf of end customers. This is how many niche B2B software companies scale support without hiring — the VAR takes on the customer relationship in exchange for a cut of recurring revenue.
|
||||
|
||||
- VAR sells the platform to their existing music store clients
|
||||
|
||||
- VAR handles implementation, data migration, training, and tier 1 support
|
||||
|
||||
- VAR receives 20-25% of monthly subscription revenue for life of customer
|
||||
|
||||
- Vendor provides VAR training, certification, and technical escalation support
|
||||
|
||||
- NAMM show is the natural place to recruit VARs — music industry consultants attend
|
||||
|
||||
- Target: 3-5 regional VARs covering US geography within first 2 years
|
||||
|
||||
|
||||
|
||||
# 6. Go-to-Market Notes
|
||||
|
||||
## 6.1 Competitive Positioning
|
||||
|
||||
Feature
|
||||
|
||||
This Platform
|
||||
|
||||
AIM
|
||||
|
||||
Music Shop 360
|
||||
|
||||
Cloud-based
|
||||
|
||||
Yes
|
||||
|
||||
No
|
||||
|
||||
Yes
|
||||
|
||||
Self-hosted option
|
||||
|
||||
Yes
|
||||
|
||||
Yes
|
||||
|
||||
No
|
||||
|
||||
Offline POS mode
|
||||
|
||||
Yes
|
||||
|
||||
Yes
|
||||
|
||||
Limited
|
||||
|
||||
Convention iOS app
|
||||
|
||||
Yes
|
||||
|
||||
No
|
||||
|
||||
No
|
||||
|
||||
Full rent-to-own
|
||||
|
||||
Yes
|
||||
|
||||
Yes
|
||||
|
||||
Partial
|
||||
|
||||
Family billing
|
||||
|
||||
Yes
|
||||
|
||||
Limited
|
||||
|
||||
Limited
|
||||
|
||||
Batch school repairs
|
||||
|
||||
Yes
|
||||
|
||||
No
|
||||
|
||||
Partial
|
||||
|
||||
Delivery tracking
|
||||
|
||||
Yes
|
||||
|
||||
No
|
||||
|
||||
No
|
||||
|
||||
Billing date mgmt
|
||||
|
||||
Yes
|
||||
|
||||
Limited
|
||||
|
||||
Limited
|
||||
|
||||
QB export
|
||||
|
||||
Yes
|
||||
|
||||
Add-on
|
||||
|
||||
Unknown
|
||||
|
||||
AIM migration tool
|
||||
|
||||
Yes
|
||||
|
||||
N/A
|
||||
|
||||
No
|
||||
|
||||
|
||||
|
||||
## 6.2 Launch Sequence
|
||||
|
||||
- Phase 1: Build core platform with one beta store — validate all workflows against real operations
|
||||
|
||||
- Phase 2: Onboard 5-10 beta stores at no cost — gather feedback, fix edge cases, build case studies
|
||||
|
||||
- Phase 3: Launch at NAMM or via NAMM member outreach — music retail community is tight-knit
|
||||
|
||||
- Phase 4: Recruit first VAR partner — ideally someone already consulting for music stores
|
||||
|
||||
- Phase 5: Paid marketing — Google Ads targeting 'AIM POS alternative', 'music store software'
|
||||
|
||||
|
||||
|
||||
## 6.3 AIM Migration as a Sales Tool
|
||||
|
||||
The AIM installed base is the primary target market. Stores that have been on AIM for 10+ years are the most likely to be feeling the pain of outdated infrastructure and are actively looking for alternatives. The AIM data migration tool is a meaningful competitive differentiator — it reduces switching cost and anxiety.
|
||||
|
||||
- 'We migrate your AIM data' is a headline feature in sales materials
|
||||
|
||||
- Migration tool handles customers, inventory, rentals, lessons, repair history
|
||||
|
||||
- Parallel operation period means zero risk of data loss during cutover
|
||||
|
||||
- Target stores that have complained publicly about AIM (review sites, forums, NAMM community)
|
||||
BIN
planning/14_Self_Hosted_Installer_Platform_Compatibility.docx
Normal file
BIN
planning/14_Self_Hosted_Installer_Platform_Compatibility.docx
Normal file
Binary file not shown.
653
planning/14_Self_Hosted_Installer_Platform_Compatibility.md
Normal file
653
planning/14_Self_Hosted_Installer_Platform_Compatibility.md
Normal file
@@ -0,0 +1,653 @@
|
||||
Music Store Management Platform
|
||||
|
||||
Self-Hosted Deployment — Installer Design & Platform Compatibility
|
||||
|
||||
Version 1.0 | Draft
|
||||
|
||||
|
||||
|
||||
# 1. Overview
|
||||
|
||||
The self-hosted deployment must be as simple as installing any consumer application. The target user is a music store owner or manager with no IT background. The experience should match what they expect from installing any Windows or Mac application — download, double-click, next-next-finish, done.
|
||||
|
||||
|
||||
|
||||
Under the hood the platform runs in Docker containers managed by Docker Compose. The store owner never sees Docker, never opens a terminal, and never touches a config file. All of that complexity is hidden inside a native installer and a system tray management app.
|
||||
|
||||
|
||||
|
||||
User sees
|
||||
|
||||
What actually happens
|
||||
|
||||
Download setup.exe
|
||||
|
||||
Downloads a bundled installer containing Docker, your stack, and the tray app
|
||||
|
||||
Double-click installer
|
||||
|
||||
Installer checks Windows edition, enables Hyper-V or WSL2, installs Docker Engine
|
||||
|
||||
Fill in store name + password
|
||||
|
||||
Generates .env config file with store settings and secure random secrets
|
||||
|
||||
Click Install
|
||||
|
||||
Docker Compose pulls images, initializes Postgres, seeds config, registers Windows service
|
||||
|
||||
Browser opens automatically
|
||||
|
||||
Nginx serves the app at localhost:3000 — first-run setup wizard shown
|
||||
|
||||
Tray icon appears
|
||||
|
||||
Management app running — monitors service health, handles updates, backup triggers
|
||||
|
||||
|
||||
|
||||
# 2. Virtualization Layer
|
||||
|
||||
Docker requires a Linux kernel to run containers. On Windows and Mac, a lightweight virtualization layer provides this. The installer detects and configures the appropriate layer automatically based on the OS.
|
||||
|
||||
|
||||
|
||||
## 2.1 Windows Virtualization
|
||||
|
||||
Windows Edition
|
||||
|
||||
Virtualization
|
||||
|
||||
Installer Action
|
||||
|
||||
Windows 10/11 Pro
|
||||
|
||||
Hyper-V
|
||||
|
||||
Enable Hyper-V via PowerShell silently — no user action required
|
||||
|
||||
Windows 10/11 Enterprise
|
||||
|
||||
Hyper-V
|
||||
|
||||
Same as Pro — Hyper-V included and licensable
|
||||
|
||||
Windows 10/11 Home
|
||||
|
||||
WSL2
|
||||
|
||||
Install WSL2 silently. If BIOS virtualization disabled, show friendly guide.
|
||||
|
||||
Windows Server 2019/2022
|
||||
|
||||
Hyper-V
|
||||
|
||||
Docker Engine direct — no Desktop needed. Most performant option.
|
||||
|
||||
|
||||
|
||||
The vast majority of music store computers will be Windows 10/11 Pro — retail businesses typically purchase Pro licenses. Home edition is handled gracefully as a fallback. Windows Server is supported for IT-managed deployments.
|
||||
|
||||
|
||||
|
||||
### Hyper-V Silent Enable (PowerShell)
|
||||
|
||||
# Installer runs this silently — requires admin elevationEnable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -All -NoRestartEnable-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatform -NoRestart# Reboot required after enabling Hyper-V# Installer schedules resume-after-reboot via registry run key
|
||||
|
||||
|
||||
|
||||
## 2.2 macOS Virtualization
|
||||
|
||||
macOS 12+ (Monterey and later) includes Apple's Virtualization Framework built into the OS. Docker Desktop uses this transparently. No additional configuration required from the store owner.
|
||||
|
||||
|
||||
|
||||
macOS Version
|
||||
|
||||
Architecture
|
||||
|
||||
Notes
|
||||
|
||||
macOS 12+ (Monterey+)
|
||||
|
||||
Intel x86-64
|
||||
|
||||
Apple Virtualization Framework — fully supported
|
||||
|
||||
macOS 12+ (Monterey+)
|
||||
|
||||
Apple Silicon ARM64
|
||||
|
||||
Native ARM containers — best performance on M1/M2/M3
|
||||
|
||||
macOS 11 (Big Sur)
|
||||
|
||||
Intel only
|
||||
|
||||
Supported but approaching end of life — recommend upgrade
|
||||
|
||||
macOS 10.x or earlier
|
||||
|
||||
Not supported
|
||||
|
||||
Too old — installer shows friendly upgrade message
|
||||
|
||||
|
||||
|
||||
## 2.3 Linux
|
||||
|
||||
Linux deployments run Docker Engine natively — no virtualization layer required. Containers run directly on the host kernel. This is the most performant deployment option and is appropriate for IT-managed stores or dedicated server hardware.
|
||||
|
||||
|
||||
|
||||
# One-line install for Ubuntu 22/24 LTScurl -fsSL https://install.platform.com/linux | bash# Script handles:# - Docker Engine installation# - Docker Compose installation# - Stack configuration# - Systemd service registration# - First-run database initialization
|
||||
|
||||
|
||||
|
||||
# 3. Platform Compatibility Matrix
|
||||
|
||||
Platform
|
||||
|
||||
Supported
|
||||
|
||||
Virtualization
|
||||
|
||||
Architecture
|
||||
|
||||
Notes
|
||||
|
||||
Windows 11 Pro/Enterprise
|
||||
|
||||
Yes
|
||||
|
||||
Hyper-V
|
||||
|
||||
x86-64
|
||||
|
||||
Recommended for most stores
|
||||
|
||||
Windows 11 Home
|
||||
|
||||
Yes
|
||||
|
||||
WSL2
|
||||
|
||||
x86-64
|
||||
|
||||
BIOS virt must be enabled
|
||||
|
||||
Windows 10 Pro/Enterprise
|
||||
|
||||
Yes
|
||||
|
||||
Hyper-V
|
||||
|
||||
x86-64
|
||||
|
||||
Version 1903+ required
|
||||
|
||||
Windows 10 Home
|
||||
|
||||
Yes
|
||||
|
||||
WSL2
|
||||
|
||||
x86-64
|
||||
|
||||
May need BIOS change
|
||||
|
||||
Windows Server 2022
|
||||
|
||||
Yes
|
||||
|
||||
Hyper-V
|
||||
|
||||
x86-64
|
||||
|
||||
Docker Engine direct
|
||||
|
||||
Windows Server 2019
|
||||
|
||||
Yes
|
||||
|
||||
Hyper-V
|
||||
|
||||
x86-64
|
||||
|
||||
Docker Engine direct
|
||||
|
||||
macOS 14 Sonoma
|
||||
|
||||
Yes
|
||||
|
||||
Apple Virt FW
|
||||
|
||||
ARM64 / x86-64
|
||||
|
||||
Intel and Apple Silicon
|
||||
|
||||
macOS 13 Ventura
|
||||
|
||||
Yes
|
||||
|
||||
Apple Virt FW
|
||||
|
||||
ARM64 / x86-64
|
||||
|
||||
|
||||
|
||||
macOS 12 Monterey
|
||||
|
||||
Yes
|
||||
|
||||
Apple Virt FW
|
||||
|
||||
x86-64
|
||||
|
||||
Intel only
|
||||
|
||||
macOS 11 Big Sur
|
||||
|
||||
Limited
|
||||
|
||||
HyperKit
|
||||
|
||||
x86-64
|
||||
|
||||
Approaching EOL
|
||||
|
||||
Ubuntu 22.04 LTS
|
||||
|
||||
Yes
|
||||
|
||||
Native
|
||||
|
||||
x86-64 / ARM64
|
||||
|
||||
Recommended Linux
|
||||
|
||||
Ubuntu 24.04 LTS
|
||||
|
||||
Yes
|
||||
|
||||
Native
|
||||
|
||||
x86-64 / ARM64
|
||||
|
||||
|
||||
|
||||
Raspberry Pi (4/5)
|
||||
|
||||
Yes
|
||||
|
||||
Native
|
||||
|
||||
ARM64
|
||||
|
||||
Pi 5 recommended — 8GB RAM
|
||||
|
||||
|
||||
|
||||
Multi-architecture Docker images are built for both linux/amd64 and linux/arm64 on every release. Docker automatically pulls the correct image for the host architecture. No platform-specific configuration required from the store.
|
||||
|
||||
|
||||
|
||||
## 3.1 Minimum Hardware Requirements
|
||||
|
||||
Component
|
||||
|
||||
Minimum
|
||||
|
||||
Recommended
|
||||
|
||||
CPU
|
||||
|
||||
4-core x86-64 or ARM64
|
||||
|
||||
6-core or better — handles concurrent POS terminals smoothly
|
||||
|
||||
RAM
|
||||
|
||||
8GB
|
||||
|
||||
16GB — headroom for Postgres query cache and API under load
|
||||
|
||||
Storage
|
||||
|
||||
100GB SSD
|
||||
|
||||
500GB SSD — 5+ years of transaction data with room to spare
|
||||
|
||||
Network
|
||||
|
||||
100Mbps LAN
|
||||
|
||||
Gigabit LAN — fast for multi-terminal stores
|
||||
|
||||
OS
|
||||
|
||||
See compatibility matrix
|
||||
|
||||
Windows 11 Pro or Ubuntu 24 LTS
|
||||
|
||||
|
||||
|
||||
## 3.2 Recommended Hardware
|
||||
|
||||
For most music stores a mini PC is the ideal self-hosted server. Small, quiet, low power, no moving parts, runs 24/7 reliably. Examples:
|
||||
|
||||
|
||||
|
||||
Device
|
||||
|
||||
Est. Cost
|
||||
|
||||
Notes
|
||||
|
||||
Intel NUC 13 Pro (i5)
|
||||
|
||||
$400-500
|
||||
|
||||
Compact, reliable, Windows 11 Pro. Popular choice for small business servers.
|
||||
|
||||
Beelink Mini S12 Pro
|
||||
|
||||
$180-220
|
||||
|
||||
Budget option. N100 processor handles the stack well. 16GB RAM config recommended.
|
||||
|
||||
Existing store PC
|
||||
|
||||
$0
|
||||
|
||||
If meets min specs — install alongside existing OS. Avoids new hardware purchase.
|
||||
|
||||
Raspberry Pi 5 (8GB)
|
||||
|
||||
$80-120
|
||||
|
||||
ARM64, fanless, extremely low power. Requires Ubuntu — Linux-savvy stores only.
|
||||
|
||||
Dell OptiPlex (refurb)
|
||||
|
||||
$150-250
|
||||
|
||||
Refurbished business desktop. Reliable, widely available, Windows Pro pre-installed.
|
||||
|
||||
|
||||
|
||||
# 4. Installer Design
|
||||
|
||||
## 4.1 Windows Installer (.exe)
|
||||
|
||||
Built with Inno Setup or NSIS — industry standard Windows installer frameworks. Produces a single signed .exe that handles the complete installation. Code signing certificate required to avoid Windows SmartScreen warnings.
|
||||
|
||||
|
||||
|
||||
### Installer Contents
|
||||
|
||||
setup.exe (self-extracting archive) contains: ├── docker-install/ Docker Desktop or Engine MSI ├── compose/ │ ├── docker-compose.yml │ ├── nginx.conf │ └── init-db.sql ├── tray-app/ │ └── PlatformManager.exe (Electron or Go tray app) ├── scripts/ │ ├── install.ps1 PowerShell install logic │ ├── enable-hyperv.ps1 Hyper-V activation │ ├── service-register.ps1 Windows service setup │ └── first-run.ps1 Initial DB seed └── resources/ ├── icon.ico └── license.txt
|
||||
|
||||
|
||||
|
||||
### Installation Steps (User Facing)
|
||||
|
||||
Screen
|
||||
|
||||
Title
|
||||
|
||||
What happens
|
||||
|
||||
1
|
||||
|
||||
Welcome
|
||||
|
||||
Intro screen, version number, Next button
|
||||
|
||||
2
|
||||
|
||||
License Agreement
|
||||
|
||||
EULA — must accept to continue
|
||||
|
||||
3
|
||||
|
||||
Install Location
|
||||
|
||||
Default: C:\Program Files\Platform — most stores leave default
|
||||
|
||||
4
|
||||
|
||||
Store Setup
|
||||
|
||||
Store name, admin email, admin password, timezone — generates .env
|
||||
|
||||
5
|
||||
|
||||
Installing
|
||||
|
||||
Progress bar — enables Hyper-V/WSL2, installs Docker, pulls images, seeds DB
|
||||
|
||||
6
|
||||
|
||||
Complete
|
||||
|
||||
Open Platform button — launches browser to localhost:3000
|
||||
|
||||
|
||||
|
||||
### Behind the Scenes During Install Step 5
|
||||
|
||||
1. Check Windows edition → select Hyper-V or WSL22. Enable virtualization layer (may require reboot — handled automatically)3. Install Docker Desktop silently (--quiet flag)4. Wait for Docker daemon to start (poll /var/run/docker.sock)5. Pull Docker images (progress shown per image)6. Generate SSL certificate (self-signed, 10yr validity)7. Write .env file with store config and generated secrets8. docker compose up -d9. Wait for Postgres health check to pass10. Run database migrations11. Seed initial config (store name, admin user, chart of accounts)12. Register PlatformManager.exe as Windows startup item13. Register docker compose as Windows service (auto-start on boot)14. Open browser to localhost:3000
|
||||
|
||||
|
||||
|
||||
## 4.2 macOS Installer (.dmg)
|
||||
|
||||
Standard macOS disk image containing a drag-to-Applications app bundle. The app bundle is a macOS app that includes Docker Desktop and manages the compose stack. Code signed and notarized with Apple Developer certificate to avoid Gatekeeper warnings.
|
||||
|
||||
|
||||
|
||||
Platform.dmg └── Platform.app (drag to /Applications) └── Contents/ ├── MacOS/ │ └── PlatformManager (menu bar app binary) ├── Resources/ │ ├── docker-compose.yml │ ├── Docker.dmg (Docker Desktop bundled) │ └── icon.icns └── Info.plist
|
||||
|
||||
|
||||
|
||||
On first launch the menu bar app detects if Docker Desktop is installed, installs it if not, pulls images, and starts the stack. Subsequent launches just start the menu bar icon — stack starts automatically via launchd.
|
||||
|
||||
|
||||
|
||||
## 4.3 Linux Install Script
|
||||
|
||||
Single bash script handles the complete Linux installation. Appropriate for IT-managed deployments and technically capable store owners on Ubuntu.
|
||||
|
||||
curl -fsSL https://install.platform.com/linux | sudo bash# Script actions:# 1. Detect distro (Ubuntu 22/24 supported)# 2. Install Docker Engine via official apt repo# 3. Install Docker Compose plugin# 4. Create /opt/platform/ directory# 5. Download docker-compose.yml and config files# 6. Prompt for store name, admin email, password# 7. Generate .env and SSL cert# 8. docker compose up -d# 9. Register systemd service for auto-start# 10. Print access URL and admin credentials
|
||||
|
||||
|
||||
|
||||
# 5. System Tray & Menu Bar Management App
|
||||
|
||||
The management app is the store owner's window into the platform's health. It runs silently in the background and provides a simple interface for the most common management tasks without requiring any technical knowledge.
|
||||
|
||||
|
||||
|
||||
## 5.1 Tech Stack
|
||||
|
||||
The tray app is built as a small standalone binary using one of the following approaches — final choice based on development effort vs capability tradeoff:
|
||||
|
||||
|
||||
|
||||
Option
|
||||
|
||||
Size
|
||||
|
||||
Notes
|
||||
|
||||
Go + systray lib
|
||||
|
||||
~10MB
|
||||
|
||||
Recommended — small binary, cross-platform, easy Docker API calls, fast startup
|
||||
|
||||
Rust + tray-icon
|
||||
|
||||
~5MB
|
||||
|
||||
Smallest binary, great performance, steeper development curve
|
||||
|
||||
Electron
|
||||
|
||||
~150MB
|
||||
|
||||
Easiest to build, heaviest — uses same stack as desktop app so reusable code
|
||||
|
||||
Python + pystray
|
||||
|
||||
~50MB
|
||||
|
||||
Quick to develop but large bundled size
|
||||
|
||||
|
||||
|
||||
## 5.2 Tray Icon States
|
||||
|
||||
Icon State
|
||||
|
||||
Color
|
||||
|
||||
Meaning
|
||||
|
||||
Running
|
||||
|
||||
Green
|
||||
|
||||
All services healthy — API, Postgres, Redis all responding
|
||||
|
||||
Starting
|
||||
|
||||
Yellow
|
||||
|
||||
Services starting up — normal after boot, takes 30-60 seconds
|
||||
|
||||
Update available
|
||||
|
||||
Blue badge
|
||||
|
||||
New version available — click to view changelog and update
|
||||
|
||||
Warning
|
||||
|
||||
Orange
|
||||
|
||||
One service degraded but platform partially operational
|
||||
|
||||
Stopped / Error
|
||||
|
||||
Red
|
||||
|
||||
Platform not running or critical service down
|
||||
|
||||
|
||||
|
||||
## 5.3 Tray Menu
|
||||
|
||||
Right-click tray icon: ┌─────────────────────────────┐ │ ● Platform Manager │ │ Version 1.5.2 │ ├─────────────────────────────┤ │ Open Platform → │ Opens localhost:3000 in browser ├─────────────────────────────┤ │ Status │ │ ✓ API Running │ │ ✓ Database Running │ │ ✓ Cache Running │ ├─────────────────────────────┤ │ 🔵 Update Available (1.6.0) │ Shows when update exists │ View Changelog │ │ Install Update │ ├─────────────────────────────┤ │ Backup Now │ Triggers manual backup │ View Last Backup │ Shows last backup time ├─────────────────────────────┤ │ Restart Services │ docker compose restart │ View Logs │ Shows last 100 API log lines ├─────────────────────────────┤ │ Stop Platform │ docker compose down └─────────────────────────────┘
|
||||
|
||||
|
||||
|
||||
## 5.4 Update Flow via Tray App
|
||||
|
||||
1. Tray app polls update registry daily on startup GET https://updates.platform.com/check?version=1.5.2&key=LICENSE2. If update available — tray icon shows blue badge Tooltip: 'Platform 1.6.0 available'3. Store owner right-clicks → View Changelog Simple window shows what's new in plain language 'Fixed rental billing date issue, improved school batch reports'4. Store owner clicks Install Update Progress window appears: [=====> ] Backing up database... [=========> ] Downloading update... [=============>] Applying update... [==============] Done!5. Browser refreshes automatically — new version running If update fails — automatic rollback to previous version Error message: 'Update failed, your previous version has been restored'
|
||||
|
||||
|
||||
|
||||
# 6. Auto-Start & Service Management
|
||||
|
||||
The platform starts automatically when the server boots. Store owners should never need to manually start the software — it should just be running when they arrive in the morning.
|
||||
|
||||
|
||||
|
||||
## 6.1 Windows Service
|
||||
|
||||
Platform Manager registered as Windows Service: Name: PlatformService Startup: Automatic (delayed) Recovery: Restart on failure (3 attempts) Action: Runs 'docker compose up -d' on startDocker Desktop also set to start with Windows.Sequence on boot: 1. Windows starts 2. Docker Desktop starts (Hyper-V/WSL2 VM initializes) 3. PlatformService starts (waits for Docker daemon) 4. docker compose up -d (starts all containers) 5. Tray app launches (shows starting state) 6. ~60 seconds after boot — platform ready
|
||||
|
||||
|
||||
|
||||
## 6.2 macOS launchd
|
||||
|
||||
LaunchAgent plist registered in ~/Library/LaunchAgents/ com.platform.manager.plist RunAtLoad: true KeepAlive: trueSame sequence as Windows — Docker starts, then compose stack.
|
||||
|
||||
|
||||
|
||||
## 6.3 Linux systemd
|
||||
|
||||
# /etc/systemd/system/platform.service[Unit]Description=Platform Music StoreAfter=docker.serviceRequires=docker.service[Service]WorkingDirectory=/opt/platformExecStart=docker compose upExecStop=docker compose downRestart=alwaysRestartSec=10[Install]WantedBy=multi-user.target
|
||||
|
||||
|
||||
|
||||
# 7. Backup — Self-Hosted
|
||||
|
||||
Backup is the store's responsibility on self-hosted deployments. The platform provides automated local backup tooling and an optional cloud backup add-on. Store owners should be clearly informed during installation that they are responsible for their own data.
|
||||
|
||||
|
||||
|
||||
## 7.1 Local Backup (Included)
|
||||
|
||||
- Automated daily Postgres dump to local backup directory
|
||||
|
||||
- Default location: install directory /backups/
|
||||
|
||||
- Retains last 30 daily backups — older automatically pruned
|
||||
|
||||
- Backup integrity verified after each dump — corrupt backup flagged in tray app
|
||||
|
||||
- Tray app shows last backup time and status
|
||||
|
||||
- Manual backup available via tray menu at any time
|
||||
|
||||
- Store strongly recommended to copy backups to external drive or NAS regularly
|
||||
|
||||
|
||||
|
||||
## 7.2 Cloud Backup Add-On ($29/month)
|
||||
|
||||
- Encrypted backup uploaded to vendor S3 after each local backup
|
||||
|
||||
- Encryption key held by customer — vendor cannot read backup data
|
||||
|
||||
- 30-day retention in cloud — point-in-time restore available
|
||||
|
||||
- Restore wizard available in admin panel — no technical knowledge required
|
||||
|
||||
- Ideal for stores without IT staff or dedicated backup infrastructure
|
||||
|
||||
|
||||
|
||||
## 7.3 Backup Responsibility Notice
|
||||
|
||||
During installation the store owner must acknowledge the following before completing setup:
|
||||
|
||||
IMPORTANT — DATA BACKUP RESPONSIBILITYYou are responsible for backing up your data.This software includes automated local backups but theseare stored on the same machine as your data.We strongly recommend: • Enabling cloud backup ($29/month add-on), OR • Regularly copying backups to an external drive or NASHardware failure can result in permanent data lossif backups are not stored separately.[ ] I understand I am responsible for my data backups (checkbox — must check to proceed)
|
||||
|
||||
|
||||
|
||||
# 8. Network Access
|
||||
|
||||
The platform runs on the store's local network. The Electron desktop app, iOS app, and any browser on the local network can access it by IP address or hostname.
|
||||
|
||||
|
||||
|
||||
- Default access: http://[server-ip]:3000 or http://platform.local (mDNS)
|
||||
|
||||
- HTTPS available with self-signed cert — browser warning expected, staff instructed to accept
|
||||
|
||||
- Optional Let's Encrypt cert if store has a domain name pointed at their server
|
||||
|
||||
- Remote access via VPN — store uses existing VPN infrastructure
|
||||
|
||||
- No ports need to be opened to the internet for core operation
|
||||
|
||||
- Cloud backup and update checker are the only outbound internet connections required
|
||||
|
||||
|
||||
|
||||
For stores that want remote access without a VPN, Cloudflare Tunnel is a free option that creates a secure tunnel without exposing ports. The tray app can optionally configure this during setup.
|
||||
BIN
planning/15_Licensing_Modules_Pricing.docx
Normal file
BIN
planning/15_Licensing_Modules_Pricing.docx
Normal file
Binary file not shown.
823
planning/15_Licensing_Modules_Pricing.md
Normal file
823
planning/15_Licensing_Modules_Pricing.md
Normal file
@@ -0,0 +1,823 @@
|
||||
Music Store Management Platform
|
||||
|
||||
Licensing, Module Structure & Pricing
|
||||
|
||||
Version 1.0 | Draft
|
||||
|
||||
|
||||
|
||||
# 1. Licensing Philosophy
|
||||
|
||||
The platform offers two fundamentally different ownership models depending on deployment type. Self-hosted customers can own their software outright — pay once, use forever. SaaS customers subscribe to a hosted service. This distinction maps cleanly to how customers think about each deployment type.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Self-Hosted
|
||||
|
||||
SaaS
|
||||
|
||||
Model
|
||||
|
||||
Perpetual License
|
||||
|
||||
Subscription
|
||||
|
||||
Payment
|
||||
|
||||
One-time upfront per module
|
||||
|
||||
Monthly or annual recurring
|
||||
|
||||
Software access
|
||||
|
||||
Forever — software never stops working
|
||||
|
||||
While subscription is active
|
||||
|
||||
Updates
|
||||
|
||||
While maintenance is active
|
||||
|
||||
Always included
|
||||
|
||||
Support
|
||||
|
||||
While maintenance is active
|
||||
|
||||
Always included per plan tier
|
||||
|
||||
Hosting
|
||||
|
||||
Customer's own hardware
|
||||
|
||||
Vendor managed on AWS
|
||||
|
||||
Best for
|
||||
|
||||
Stores that want ownership, AIM migrants, IT-managed
|
||||
|
||||
Stores that want zero IT burden, cloud-first
|
||||
|
||||
|
||||
|
||||
# 2. Module Structure
|
||||
|
||||
The platform is built around a core that is always included, with optional modules that unlock additional functionality. Each module is licensed independently — stores pay only for what they use. Module licenses are perpetual for self-hosted customers and included in subscription tiers for SaaS customers.
|
||||
|
||||
|
||||
|
||||
## 2.1 Core (Always Included)
|
||||
|
||||
Core is included with every license at no additional cost. It is never disabled, never expires, and requires no ongoing payment. A store can always process sales even if maintenance lapses or a subscription expires.
|
||||
|
||||
|
||||
|
||||
CORE — Always Included
|
||||
|
||||
Point of Sale — cash, card present, card keyed, account charge
|
||||
|
||||
Basic inventory — products, categories, stock levels, low stock alerts
|
||||
|
||||
Customer management — accounts, contacts, basic history
|
||||
|
||||
Cash drawer — sessions, denominations, over/short tracking
|
||||
|
||||
User accounts and role-based access control
|
||||
|
||||
Basic sales reporting — daily, weekly, monthly totals
|
||||
|
||||
Invoice and receipt printing
|
||||
|
||||
One payment processor module required (PAY-STRIPE or PAY-GP)
|
||||
|
||||
|
||||
|
||||
## 2.2 Licensed Modules
|
||||
|
||||
Module ID
|
||||
|
||||
Description
|
||||
|
||||
Typical User
|
||||
|
||||
MOD-RENTALS
|
||||
|
||||
Instrument rentals — month-to-month, rent-to-own, short-term, lease purchase. Rental fleet inventory, deposit tracking, return workflow.
|
||||
|
||||
Most music stores
|
||||
|
||||
MOD-LESSONS
|
||||
|
||||
Lesson scheduling — instructors, enrollments, schedule slots, attendance, makeup credits, lesson billing.
|
||||
|
||||
Stores offering lessons
|
||||
|
||||
MOD-REPAIRS
|
||||
|
||||
Repair ticket management — intake, diagnosis, estimates, approval, labor and parts tracking, customer notification.
|
||||
|
||||
Most music stores
|
||||
|
||||
MOD-BATCH
|
||||
|
||||
Batch repairs for schools and institutions — bulk intake, group approval, batch invoicing. Requires MOD-REPAIRS.
|
||||
|
||||
School music dealers
|
||||
|
||||
MOD-DELIVERY
|
||||
|
||||
Delivery and chain of custody tracking — pickup scheduling, per-instrument condition, signature capture. Requires MOD-BATCH.
|
||||
|
||||
School music dealers
|
||||
|
||||
MOD-ACCOUNTING
|
||||
|
||||
Journal entry generation, chart of accounts, QuickBooks CSV export, AR aging, deferred revenue tracking.
|
||||
|
||||
Stores needing accounting
|
||||
|
||||
MOD-BILLING
|
||||
|
||||
Advanced billing management — billing date changes, proration, consolidated family billing, split billing, bulk date changes.
|
||||
|
||||
Stores with lessons/rentals
|
||||
|
||||
MOD-MOBILE
|
||||
|
||||
iOS convention app — mobile POS, Bluetooth Terminal, customer lookup, inventory check, offline sync.
|
||||
|
||||
Convention sellers
|
||||
|
||||
MOD-PORTAL
|
||||
|
||||
Customer self-service portal — lesson billing management, repair status tracking, invoice history, online payment.
|
||||
|
||||
Customer-facing stores
|
||||
|
||||
MOD-SCHOOL
|
||||
|
||||
School and district management — district lists, instrument categories, school contacts, school-specific rental packages.
|
||||
|
||||
School music dealers
|
||||
|
||||
MOD-MULTI
|
||||
|
||||
Multi-location support — additional store locations, cross-location inventory visibility, consolidated reporting.
|
||||
|
||||
Multi-location stores
|
||||
|
||||
MOD-API
|
||||
|
||||
API access — REST API for third-party integrations, webhooks, data export. Rate limited by tier.
|
||||
|
||||
Technical integrations
|
||||
|
||||
|
||||
|
||||
## 2.3 Payment Processor Modules
|
||||
|
||||
Exactly one payment processor module is required. The store uses their own processor account and credentials — the platform does not handle merchant accounts or transaction fees.
|
||||
|
||||
|
||||
|
||||
Module ID
|
||||
|
||||
Description
|
||||
|
||||
Notes
|
||||
|
||||
PAY-STRIPE
|
||||
|
||||
Stripe integration — Terminal (WiFi + Bluetooth), Elements for keyed entry, Subscriptions, Webhooks, Card Updater.
|
||||
|
||||
Recommended default
|
||||
|
||||
PAY-GP
|
||||
|
||||
Global Payments integration — Terminal hardware, recurring billing, tokenization, Decline Minimizer. Supports token import from AIM.
|
||||
|
||||
For GP stores
|
||||
|
||||
|
||||
|
||||
Additional processor modules may be added in future releases. The payment abstraction layer architecture supports adding new processors without changes to business logic.
|
||||
|
||||
|
||||
|
||||
## 2.4 Module Dependencies
|
||||
|
||||
Module
|
||||
|
||||
Requires
|
||||
|
||||
MOD-BATCH
|
||||
|
||||
MOD-REPAIRS — batch repairs extend individual repair functionality
|
||||
|
||||
MOD-DELIVERY
|
||||
|
||||
MOD-BATCH — delivery tracking is part of the batch repair workflow
|
||||
|
||||
MOD-SCHOOL
|
||||
|
||||
MOD-RENTALS + MOD-BATCH — school module extends both
|
||||
|
||||
MOD-BILLING
|
||||
|
||||
MOD-RENTALS or MOD-LESSONS — billing management requires at least one recurring billing module
|
||||
|
||||
MOD-PORTAL
|
||||
|
||||
One of MOD-RENTALS, MOD-LESSONS, or MOD-REPAIRS — portal surfaces these to customers
|
||||
|
||||
MOD-MULTI
|
||||
|
||||
None — can be added to any configuration
|
||||
|
||||
MOD-API
|
||||
|
||||
None — can be added to any configuration
|
||||
|
||||
|
||||
|
||||
# 3. Self-Hosted — Perpetual Licensing
|
||||
|
||||
## 3.1 Core License
|
||||
|
||||
The core license is free. There is no charge for the base POS, inventory, customer management, cash drawer, and basic reporting functionality. The store pays for modules they need on top of core.
|
||||
|
||||
|
||||
|
||||
## 3.2 Module Pricing
|
||||
|
||||
Module
|
||||
|
||||
License
|
||||
|
||||
Maintenance/yr
|
||||
|
||||
Notes
|
||||
|
||||
MOD-RENTALS
|
||||
|
||||
$600
|
||||
|
||||
$120
|
||||
|
||||
Most purchased module
|
||||
|
||||
MOD-LESSONS
|
||||
|
||||
$600
|
||||
|
||||
$120
|
||||
|
||||
|
||||
|
||||
MOD-REPAIRS
|
||||
|
||||
$400
|
||||
|
||||
$80
|
||||
|
||||
|
||||
|
||||
MOD-BATCH
|
||||
|
||||
$400
|
||||
|
||||
$80
|
||||
|
||||
Requires MOD-REPAIRS
|
||||
|
||||
MOD-DELIVERY
|
||||
|
||||
$300
|
||||
|
||||
$60
|
||||
|
||||
Requires MOD-BATCH
|
||||
|
||||
MOD-ACCOUNTING
|
||||
|
||||
$500
|
||||
|
||||
$100
|
||||
|
||||
|
||||
|
||||
MOD-BILLING
|
||||
|
||||
$400
|
||||
|
||||
$80
|
||||
|
||||
Requires MOD-RENTALS or MOD-LESSONS
|
||||
|
||||
MOD-MOBILE
|
||||
|
||||
$600
|
||||
|
||||
$120
|
||||
|
||||
iOS app for conventions
|
||||
|
||||
MOD-PORTAL
|
||||
|
||||
$400
|
||||
|
||||
$80
|
||||
|
||||
Customer self-service
|
||||
|
||||
MOD-SCHOOL
|
||||
|
||||
$500
|
||||
|
||||
$100
|
||||
|
||||
Requires MOD-RENTALS + MOD-BATCH
|
||||
|
||||
MOD-MULTI
|
||||
|
||||
$500/loc
|
||||
|
||||
$100/loc/yr
|
||||
|
||||
Per additional location — see section 3.6 for user/location/terminal pricing
|
||||
|
||||
MOD-API
|
||||
|
||||
$400
|
||||
|
||||
$80
|
||||
|
||||
REST API access
|
||||
|
||||
PAY-STRIPE
|
||||
|
||||
$300
|
||||
|
||||
$60
|
||||
|
||||
Stripe integration
|
||||
|
||||
PAY-GP
|
||||
|
||||
$300
|
||||
|
||||
$60
|
||||
|
||||
Global Payments integration
|
||||
|
||||
|
||||
|
||||
## 3.3 Common Bundle Examples
|
||||
|
||||
Bundle
|
||||
|
||||
Modules
|
||||
|
||||
License Total
|
||||
|
||||
Maintenance/yr
|
||||
|
||||
Repair Shop
|
||||
|
||||
REPAIRS + PAY
|
||||
|
||||
$700
|
||||
|
||||
$140/yr
|
||||
|
||||
Standard Store
|
||||
|
||||
RENTALS + LESSONS + REPAIRS + ACCOUNTING + BILLING + PAY
|
||||
|
||||
$2,800
|
||||
|
||||
$560/yr
|
||||
|
||||
School Dealer
|
||||
|
||||
Standard + BATCH + DELIVERY + SCHOOL + MOBILE
|
||||
|
||||
$4,600
|
||||
|
||||
$920/yr
|
||||
|
||||
Full Platform
|
||||
|
||||
All modules
|
||||
|
||||
$5,700
|
||||
|
||||
$1,140/yr
|
||||
|
||||
Full Platform Bundle
|
||||
|
||||
All modules (bundled)
|
||||
|
||||
$4,500
|
||||
|
||||
$900/yr — 21% saving
|
||||
|
||||
|
||||
|
||||
## 3.6 User, Location & Terminal Pricing (Self-Hosted)
|
||||
|
||||
In addition to module licenses, self-hosted customers purchase capacity licenses for users, locations, and terminals. The core license includes a starter allocation. Additional capacity is purchased as add-ons.
|
||||
|
||||
### Included with Core License
|
||||
|
||||
- 5 users (staff accounts)
|
||||
- 1 location
|
||||
- 2 terminals (desktop app installs)
|
||||
|
||||
### Additional Capacity Pricing
|
||||
|
||||
Capacity | Price | Maintenance/yr | Notes
|
||||
Additional user (5-pack) | $200 | $40/yr | Staff accounts — customers are always unlimited
|
||||
Additional location | $500 | $100/yr | Own inventory, transactions, cash drawer
|
||||
Additional terminal (5-pack) | $250 | $50/yr | Desktop app installs
|
||||
|
||||
### Web UI Access
|
||||
|
||||
Web UI access (admin panel) is included for all licensed users at no additional cost. The web UI provides management, reporting, scheduling, and configuration capabilities. It does not include POS terminal, card reader hardware integration, or cash drawer operations — those require the desktop app (terminal license).
|
||||
|
||||
### Enforcement
|
||||
|
||||
- User creation blocked when at limit — admin shown upgrade prompt with pricing
|
||||
- Location creation blocked when at limit — same upgrade prompt
|
||||
- Desktop app shows "terminal limit reached" on first launch if no seats available — links to purchase
|
||||
- Web UI never blocked — always available for licensed users
|
||||
- All limits checked at API level — cannot be bypassed by client
|
||||
|
||||
|
||||
## 3.4 Maintenance Policy
|
||||
|
||||
- Maintenance is optional — software works forever without it
|
||||
|
||||
- Maintenance includes: all software updates, bug fixes, new features, support access
|
||||
|
||||
- Without active maintenance: software runs on last version at maintenance lapse date
|
||||
|
||||
- Version cap: lapsed maintenance locks to the major version active at lapse
|
||||
|
||||
- Newer major versions require active maintenance to install
|
||||
|
||||
- Maintenance can be renewed at any time at current pricing — no penalty for lapsing
|
||||
|
||||
- Re-activating lapsed maintenance does not back-bill for the lapsed period
|
||||
|
||||
- Security patches distributed free regardless of maintenance status — safety is non-negotiable
|
||||
|
||||
|
||||
|
||||
## 3.5 Version Cap Behavior
|
||||
|
||||
Example: Store purchases license — maintenance active Platform releases v1.0, v1.5, v2.0, v2.3 Store updates through v2.3 while maintenance active Maintenance lapses — store is on v2.3 Platform releases v3.0 with new features Store experience: v2.3 continues to work — forever v3.0 shows in update checker as available Update blocked: 'Renew maintenance to upgrade to v3.0' [Renew Maintenance] button links to purchase Security patch v2.3.1 released: Applied automatically regardless of maintenance status
|
||||
|
||||
|
||||
|
||||
# 4. SaaS — Subscription Pricing
|
||||
|
||||
SaaS subscriptions bundle modules into tiers for simplicity. The store always runs the latest version and support is always included. No perpetual ownership — access continues while subscription is active. Each tier includes a base allocation of users, locations, and terminals.
|
||||
|
||||
|
||||
|
||||
## 4.1 Subscription Tiers
|
||||
|
||||
Plan
|
||||
|
||||
Monthly
|
||||
|
||||
Annual
|
||||
|
||||
Modules Included
|
||||
|
||||
Included Capacity
|
||||
|
||||
Starter
|
||||
|
||||
$99/mo
|
||||
|
||||
$990/yr
|
||||
|
||||
CORE + one PAY module + basic support
|
||||
|
||||
5 users, 1 location, 2 terminals
|
||||
|
||||
Standard
|
||||
|
||||
$249/mo
|
||||
|
||||
$2,490/yr
|
||||
|
||||
CORE + RENTALS + LESSONS + REPAIRS + ACCOUNTING + BILLING + PORTAL + one PAY
|
||||
|
||||
10 users, 1 location, 5 terminals
|
||||
|
||||
Professional
|
||||
|
||||
$349/mo
|
||||
|
||||
$3,490/yr
|
||||
|
||||
All Standard modules + BATCH + DELIVERY + SCHOOL + MOBILE + API + priority support
|
||||
|
||||
25 users, 3 locations, 10 terminals
|
||||
|
||||
|
||||
|
||||
## 4.2 SaaS Add-Ons
|
||||
|
||||
Add-On
|
||||
|
||||
Price
|
||||
|
||||
Notes
|
||||
|
||||
Additional location
|
||||
|
||||
+$99/mo
|
||||
|
||||
Per additional store — MOD-MULTI included
|
||||
|
||||
Additional users (5-pack)
|
||||
|
||||
+$29/mo
|
||||
|
||||
Staff accounts beyond plan allocation
|
||||
|
||||
Additional terminals (5-pack)
|
||||
|
||||
+$19/mo
|
||||
|
||||
Desktop app installs beyond plan allocation
|
||||
|
||||
Second PAY module
|
||||
|
||||
+$29/mo
|
||||
|
||||
Run two processors simultaneously during migration
|
||||
|
||||
Priority support upgrade
|
||||
|
||||
+$49/mo
|
||||
|
||||
4hr response + emergency POS-down line
|
||||
|
||||
|
||||
|
||||
## 4.3 Subscription to Perpetual Conversion
|
||||
|
||||
SaaS customers who decide they want ownership can convert to a self-hosted perpetual license. Subscription payments made to date are credited toward the perpetual license price.
|
||||
|
||||
|
||||
|
||||
Example: Standard SaaS customer converts after 18 months Paid to date: 18 x $249 = $4,482 Standard bundle perpetual: $4,500 Credit applied: $4,482 Balance due: $18 + first year maintenance ($560) Result: Customer owns software outright 18 months of SaaS fees count toward ownership Effectively: renting to own
|
||||
|
||||
|
||||
|
||||
- Conversion available at any time — no minimum subscription period
|
||||
|
||||
- Credit applied is total subscription fees paid, capped at perpetual license price
|
||||
|
||||
- Customer chooses which modules to own — must match or subset of current SaaS modules
|
||||
|
||||
- Maintenance starts fresh at conversion date
|
||||
|
||||
|
||||
|
||||
# 5. License File Technical Design
|
||||
|
||||
## 5.1 License File Format
|
||||
|
||||
License files are JSON documents cryptographically signed with the vendor's Ed25519 private key. The software verifies signatures using the embedded public key. Tampering with the JSON payload invalidates the signature and the license is rejected.
|
||||
|
||||
|
||||
|
||||
{ "license_id": "LIC-2024-00142", "license_type": "perpetual", "company_id": "uuid-here", "company_name": "Springfield Music Co.", "issued_to": "admin@springfieldmusic.com", "issued_at": "2024-09-01T00:00:00Z", "maintenance_expires": "2025-09-01T00:00:00Z", "software_version_cap": "2.x", "modules": [ "CORE", "MOD-RENTALS", "MOD-LESSONS", "MOD-REPAIRS", "MOD-ACCOUNTING", "MOD-BILLING", "PAY-GP" ], "limits": { "users": 15, // staff accounts (customers unlimited) "locations": 1, // physical store locations "terminals": 5 // desktop app installs }, "signature": "base64-ed25519-signature-here"}
|
||||
|
||||
## 5.1.1 License Limits — Revenue Model
|
||||
|
||||
The `limits` object in the license file controls three independent licensing axes. Each is a separate revenue line.
|
||||
|
||||
Limit | What It Controls | Enforcement
|
||||
`users` | Staff/employee accounts — people who log into the system. A user can access any location within their company (not locked to one location). Customer accounts (the `account` table) are unlimited and never counted. | Block user creation when `count(users where company_id = X) >= limits.users`
|
||||
`locations` | Physical store locations. Each location has its own inventory, transactions, and cash drawers. Adding a new location requires a location license. | Block location creation when `count(locations where company_id = X) >= limits.locations`
|
||||
`terminals` | Installed instances of the desktop app (Electron). Each terminal registers with the license server on first launch and consumes a terminal seat. | Block terminal registration when active terminal count >= `limits.terminals`
|
||||
|
||||
### Web UI vs Desktop App (Terminal)
|
||||
|
||||
The web UI (admin panel) is always available to licensed users and does not consume a terminal license. However, the web UI has restricted capabilities compared to the desktop app:
|
||||
|
||||
Capability | Desktop App (Terminal) | Web UI
|
||||
POS transactions | Yes | No — requires Stripe Terminal hardware
|
||||
Stripe Terminal card reader | Yes | No — hardware integration requires desktop
|
||||
Cash drawer integration | Yes | No — hardware
|
||||
Repair technician work screen | Yes | View only
|
||||
Inventory receiving / adjustments | Yes | Yes
|
||||
Customer / account management | Yes | Yes
|
||||
Reporting and dashboards | Yes | Yes
|
||||
Settings and admin | Yes | Yes
|
||||
Lesson scheduling and notes | Yes | Yes
|
||||
|
||||
The web UI is positioned as a management and reporting tool. The desktop app is the full operational client. This creates natural upsell pressure — a store with 3 registers needs 3 terminal licenses, but the owner checking reports from home uses the web UI at no extra terminal cost.
|
||||
|
||||
|
||||
|
||||
## 5.2 Signature Scheme
|
||||
|
||||
- Algorithm: Ed25519 — fast, small signatures, strong security
|
||||
|
||||
- Private key held by vendor only — never distributed, never in source code
|
||||
|
||||
- Public key embedded in application binary at build time
|
||||
|
||||
- Signature covers entire JSON payload excluding the signature field itself
|
||||
|
||||
- License file delivered as .lic file — plain JSON, human readable
|
||||
|
||||
- Offline verification — no network call required to validate license
|
||||
|
||||
|
||||
|
||||
// License verification pseudocodefunction verifyLicense(licenseFile) { const { signature, ...payload } = JSON.parse(licenseFile) const isValid = ed25519.verify( EMBEDDED_PUBLIC_KEY, JSON.stringify(payload), signature ) if (!isValid) throw new Error('Invalid license') if (payload.modules.includes('MOD-RENTALS')) enableRentals() // ... enable each licensed module}
|
||||
|
||||
|
||||
|
||||
## 5.3 SaaS License Delivery
|
||||
|
||||
- SaaS license generated automatically on subscription creation or change
|
||||
|
||||
- Pushed to store instance on login and refreshed daily
|
||||
|
||||
- Module changes take effect within 24 hours — no manual file handling
|
||||
|
||||
- Subscription cancellation: license updated to CORE only after grace period
|
||||
|
||||
|
||||
|
||||
## 5.4 Self-Hosted License Delivery
|
||||
|
||||
- License file generated by vendor portal when purchase is completed
|
||||
|
||||
- Delivered via email and available for re-download from customer portal
|
||||
|
||||
- Store admin uploads via Settings → License in admin panel
|
||||
|
||||
- Tray app detects new license file and applies without restart
|
||||
|
||||
- License stored in platform database — survives Docker restarts
|
||||
|
||||
|
||||
|
||||
## 5.5 Expiry and Grace Period
|
||||
|
||||
State
|
||||
|
||||
Behavior
|
||||
|
||||
Maintenance active
|
||||
|
||||
All licensed modules available. Updates and support included.
|
||||
|
||||
Maintenance expiring soon
|
||||
|
||||
Warning banner shown to admin 30 days before expiry. Renewal reminder emails sent.
|
||||
|
||||
Maintenance lapsed — grace (14 days)
|
||||
|
||||
All modules still work. Urgent renewal banner shown. No new updates downloaded.
|
||||
|
||||
Maintenance lapsed — post grace
|
||||
|
||||
All licensed modules continue working. Updates blocked. Support not available until renewal.
|
||||
|
||||
CORE — always
|
||||
|
||||
CORE never disabled under any circumstance. Store can always process sales.
|
||||
|
||||
SaaS subscription cancelled
|
||||
|
||||
14-day grace period — all modules active. After grace: CORE only. Data export available for 90 days.
|
||||
|
||||
|
||||
|
||||
# 6. Module Enforcement
|
||||
|
||||
## 6.1 Runtime Enforcement
|
||||
|
||||
Module availability is checked at two points — API routes and UI rendering. Both checks use the same license object loaded at application startup.
|
||||
|
||||
|
||||
|
||||
// API route guard — server siderouter.get('/rentals', requireModule('MOD-RENTALS'), (req, res) => { // handler only runs if MOD-RENTALS licensed})// UI guard — client sidefunction RentalsNav() { if (!license.hasModule('MOD-RENTALS')) { return <UpgradePrompt module='MOD-RENTALS' /> } return <RentalsLink />}
|
||||
|
||||
|
||||
|
||||
## 6.2 Upgrade Prompt
|
||||
|
||||
When a staff member attempts to access an unlicensed module they see a clear, non-alarming upgrade prompt. The goal is to surface value, not block the user with an error.
|
||||
|
||||
|
||||
|
||||
┌─────────────────────────────────────────────┐│ Rentals Module ││ ││ Track instrument rentals, rent-to-own ││ contracts, and rental fleet inventory. ││ ││ This module is not included in your ││ current license. ││ ││ [Start 30-Day Trial] [Purchase Module] ││ ││ Questions? support@platform.com │└─────────────────────────────────────────────┘
|
||||
|
||||
|
||||
|
||||
## 6.3 Trial Licenses
|
||||
|
||||
- Any unlicensed module can be trialed for 30 days at no cost
|
||||
|
||||
- Trial generated automatically — no credit card required
|
||||
|
||||
- One trial per module per store — cannot restart a completed trial
|
||||
|
||||
- Trial license has same format as paid license with trial flag and 30-day expiry
|
||||
|
||||
- Store receives reminder emails at day 20 and day 28 of trial
|
||||
|
||||
- Trial expiry shows purchase prompt — module disabled after grace period
|
||||
|
||||
|
||||
|
||||
# 7. Professional Services
|
||||
|
||||
Service
|
||||
|
||||
Price
|
||||
|
||||
Notes
|
||||
|
||||
AIM data migration — standard
|
||||
|
||||
$750
|
||||
|
||||
Customers, inventory, open rentals, lessons, repair history
|
||||
|
||||
AIM data migration — with GP token import
|
||||
|
||||
$1,200
|
||||
|
||||
Includes validation and import of existing GP payment tokens
|
||||
|
||||
Remote installation (self-hosted)
|
||||
|
||||
$250
|
||||
|
||||
Vendor installs and configures remotely via screen share
|
||||
|
||||
Onboarding training
|
||||
|
||||
$300
|
||||
|
||||
3hr remote session covering all licensed modules
|
||||
|
||||
Custom report development
|
||||
|
||||
$150/hr
|
||||
|
||||
Custom reports beyond standard library
|
||||
|
||||
Chart of accounts setup
|
||||
|
||||
$200
|
||||
|
||||
Configure accounting module to match existing QB setup
|
||||
|
||||
|
||||
|
||||
# 8. Beta Program
|
||||
|
||||
The first 10 stores receive the platform at no charge for 6 months in exchange for active participation as design partners. Beta stores get lifetime pricing advantages as reference customer compensation.
|
||||
|
||||
|
||||
|
||||
Beta Term
|
||||
|
||||
Detail
|
||||
|
||||
Duration
|
||||
|
||||
6 months free access to all modules
|
||||
|
||||
Post-beta discount
|
||||
|
||||
20% lifetime discount on all licenses and maintenance — locked in forever
|
||||
|
||||
Store obligations
|
||||
|
||||
Monthly feedback call (30 min), permission to use as case study, referral introductions to other stores
|
||||
|
||||
Target profile
|
||||
|
||||
Stores currently on AIM with pain around cloud access, school batch repairs, or modern hardware requirements
|
||||
|
||||
GP token benefit
|
||||
|
||||
Beta stores on Global Payments get free token migration validation — highest value migration path
|
||||
BIN
planning/16_Windows_Installer_PowerShell.docx
Normal file
BIN
planning/16_Windows_Installer_PowerShell.docx
Normal file
Binary file not shown.
289
planning/16_Windows_Installer_PowerShell.md
Normal file
289
planning/16_Windows_Installer_PowerShell.md
Normal file
@@ -0,0 +1,289 @@
|
||||
Music Store Management Platform
|
||||
|
||||
Self-Hosted Windows Installer — PowerShell Design
|
||||
|
||||
Version 1.0 | Phase 1: PowerShell | Phase 2: Inno Setup .exe wrapper
|
||||
|
||||
|
||||
|
||||
# 1. Overview
|
||||
|
||||
The Windows self-hosted installer is delivered in two phases. Phase 1 (current) uses a PowerShell script — appropriate for beta customers and IT-managed deployments. Phase 2 wraps the same PowerShell logic in an Inno Setup .exe for a consumer-grade double-click experience. No logic changes are required between phases — only packaging changes.
|
||||
|
||||
|
||||
|
||||
Phase
|
||||
|
||||
Delivery
|
||||
|
||||
Target
|
||||
|
||||
Phase 1
|
||||
|
||||
install.ps1 script
|
||||
|
||||
Beta customers, IT-managed stores, technically capable staff
|
||||
|
||||
Phase 2
|
||||
|
||||
setup.exe (Inno Setup)
|
||||
|
||||
All stores — double-click consumer experience, no technical knowledge required
|
||||
|
||||
|
||||
|
||||
# 2. Installation Phase Overview
|
||||
|
||||
The installer proceeds through numbered phases. Phase state is saved to disk so installation can resume after a required reboot. Each phase is idempotent — safe to re-run if interrupted.
|
||||
|
||||
|
||||
|
||||
Phase
|
||||
|
||||
Name
|
||||
|
||||
Actions
|
||||
|
||||
1
|
||||
|
||||
collect-config
|
||||
|
||||
Prompt for license key, store name, admin email, admin password. Validate license against API. Save state.
|
||||
|
||||
2
|
||||
|
||||
enable-virt
|
||||
|
||||
Check Windows edition. Enable Hyper-V (Pro/Enterprise) or WSL2 (Home). Reboot if required — resume automatically after.
|
||||
|
||||
3
|
||||
|
||||
install-docker
|
||||
|
||||
Check if Docker already installed. Download and install Docker Desktop silently if not. Wait for daemon.
|
||||
|
||||
4
|
||||
|
||||
create-dirs
|
||||
|
||||
Create C:\Platform directory structure. Write .env, docker-compose.yml, nginx.conf, license.lic.
|
||||
|
||||
5
|
||||
|
||||
pull-images
|
||||
|
||||
Authenticate to private registry using license token. Pull platform Docker images.
|
||||
|
||||
6
|
||||
|
||||
start-services
|
||||
|
||||
docker compose up -d. Wait for API health check to pass.
|
||||
|
||||
7
|
||||
|
||||
register-service
|
||||
|
||||
Install NSSM. Register PlatformService Windows service for auto-start on boot.
|
||||
|
||||
8
|
||||
|
||||
complete
|
||||
|
||||
Clean up state file. Open browser to localhost. Show completion message.
|
||||
|
||||
|
||||
|
||||
# 3. Hyper-V Restart Handling
|
||||
|
||||
Enabling Hyper-V requires a kernel-level change that cannot take effect without a reboot. This is unavoidable on machines where Hyper-V is not already enabled. The installer handles this transparently — the store owner reboots once and installation continues automatically after login.
|
||||
|
||||
|
||||
|
||||
## 3.1 Detection Flow
|
||||
|
||||
Check Hyper-V state: Already enabled → no reboot needed, continue to Docker phase Disabled → enable, check if reboot required RestartNeeded = false → continue immediately (rare) RestartNeeded = true → save state, schedule resume, rebootWindows Home — check WSL2 instead: Already enabled → no reboot needed Disabled → enable WSL2 + VirtualMachinePlatform Both features require reboot → same resume pattern
|
||||
|
||||
|
||||
|
||||
## 3.2 Resume-After-Reboot Mechanism
|
||||
|
||||
Windows registry Run key schedules the script to execute automatically after the next login. The script detects the resume flag, skips completed phases, and continues from where it left off.
|
||||
|
||||
|
||||
|
||||
On reboot needed: 1. Save install state to C:\Platform\install-state.json { phase: 'install-docker', licenseKey: '...', storeName: '...' } 2. Write registry run key: HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run PlatformInstallResume = powershell -ExecutionPolicy Bypass -File 'C:\Platform\install.ps1' -Resume 3. Prompt user to restart: 'A restart is required. Press Enter to restart now.'After reboot (auto-runs on login): 1. Script starts with -Resume flag 2. Reads install-state.json 3. Removes registry run key 4. Continues from saved phase 5. User sees: 'Resuming installation after restart...'
|
||||
|
||||
|
||||
|
||||
## 3.3 State File Format
|
||||
|
||||
C:\Platform\install-state.json{ "phase": "install-docker", "licenseKey": "LIC-2024-00142-XXXX", "registryToken": "eyJ...", "storeName": "Springfield Music Co.", "adminEmail": "admin@springfieldmusic.com", "modules": ["CORE", "MOD-RENTALS", "MOD-LESSONS"], "savedAt": "2024-09-01T10:30:00Z"}Note: admin password NOT saved to state file.If resuming after reboot, password is re-prompted.State file deleted on successful completion.
|
||||
|
||||
|
||||
|
||||
## 3.4 User Experience During Restart
|
||||
|
||||
============================================ Platform Music Store Installation============================================Enter your license key: LIC-2024-00142-XXXXValidating license... OKModules: Rentals, Lessons, Repairs, AccountingEnter your store name: Springfield Music Co.Enter admin email: admin@springfieldmusic.comEnter admin password: ********Checking virtualization...Enabling Hyper-V...============================================ A restart is required to enable Hyper-V. Installation will continue automatically after your computer restarts. You do not need to run this script again. Press Enter to restart now, or close this window to restart manually later.============================================[User presses Enter — machine reboots][Script runs automatically after login]============================================ Resuming installation after restart...============================================Checking Docker...Downloading Docker Desktop...Installing Docker Desktop......
|
||||
|
||||
|
||||
|
||||
# 4. Script Structure
|
||||
|
||||
## 4.1 Script Parameters and Entry Point
|
||||
|
||||
#Requires -RunAsAdministrator#Requires -Version 5.1$ErrorActionPreference = 'Stop'param( [switch]$Resume, [switch]$Uninstall)# ── Constants ───────────────────────────────────$INSTALL_DIR = 'C:\Platform'$STATE_FILE = '$INSTALL_DIR\install-state.json'$LICENSE_API = 'https://license.platform.com'$REGISTRY_URL = 'registry.platform.com'$RESUME_KEY = 'PlatformInstallResume'$SERVICE_NAME = 'PlatformService'$NSSM_URL = 'https://nssm.cc/release/nssm-2.24.zip'$DOCKER_URL = 'https://desktop.docker.com/win/main/amd64/...'# ── Entry point ─────────────────────────────────if ($Uninstall) { Invoke-Uninstall; exit }$currentPhase = 'collect-config'if ($Resume -or (Test-Path $STATE_FILE)) { $state = Get-Content $STATE_FILE | ConvertFrom-Json $currentPhase = $state.phase Remove-ResumeKey Write-Banner 'Resuming installation after restart...'}Invoke-InstallPhases -StartFrom $currentPhase
|
||||
|
||||
|
||||
|
||||
## 4.2 Phase Execution Pattern
|
||||
|
||||
function Invoke-InstallPhases($StartFrom) { $phases = @( 'collect-config', 'enable-virt', 'install-docker', 'create-dirs', 'pull-images', 'start-services', 'register-service', 'complete' ) $active = $false foreach ($phase in $phases) { if ($phase -eq $StartFrom) { $active = $true } if (-not $active) { continue } Write-Host '' Write-Host "--- Phase: $phase ---" Save-State $phase # save before running switch ($phase) { 'collect-config' { Invoke-CollectConfig } 'enable-virt' { Invoke-EnableVirtualization } 'install-docker' { Invoke-InstallDocker } 'create-dirs' { Invoke-CreateDirectories } 'pull-images' { Invoke-PullImages } 'start-services' { Invoke-StartServices } 'register-service' { Invoke-RegisterService } 'complete' { Invoke-Complete } } }}
|
||||
|
||||
|
||||
|
||||
## 4.3 Virtualization Detection
|
||||
|
||||
function Invoke-EnableVirtualization { $edition = (Get-WindowsEdition -Online).Edition Write-Host "Windows edition: $edition" if ($edition -match 'Pro|Enterprise|Education|Server') { Invoke-EnableHyperV } else { Invoke-EnableWSL2 }}function Invoke-EnableHyperV { $feature = Get-WindowsOptionalFeature ` -Online -FeatureName Microsoft-Hyper-V if ($feature.State -eq 'Enabled') { Write-Host 'Hyper-V already enabled — no restart needed.' return } Write-Host 'Enabling Hyper-V...' $result = Enable-WindowsOptionalFeature ` -Online -FeatureName Microsoft-Hyper-V ` -All -NoRestart if ($result.RestartNeeded) { Invoke-ScheduleResumeAndReboot }}function Invoke-EnableWSL2 { $wsl = Get-WindowsOptionalFeature ` -Online -FeatureName Microsoft-Windows-Subsystem-Linux $vmp = Get-WindowsOptionalFeature ` -Online -FeatureName VirtualMachinePlatform if ($wsl.State -eq 'Enabled' -and $vmp.State -eq 'Enabled') { Write-Host 'WSL2 already enabled — no restart needed.' return } Write-Host 'Enabling WSL2...' Enable-WindowsOptionalFeature ` -Online -FeatureName Microsoft-Windows-Subsystem-Linux ` -NoRestart | Out-Null Enable-WindowsOptionalFeature ` -Online -FeatureName VirtualMachinePlatform ` -NoRestart | Out-Null Invoke-ScheduleResumeAndReboot}
|
||||
|
||||
|
||||
|
||||
## 4.4 Resume Schedule and Reboot
|
||||
|
||||
function Invoke-ScheduleResumeAndReboot { $scriptPath = $MyInvocation.ScriptName # Schedule resume after reboot $cmd = "powershell -ExecutionPolicy Bypass" + " -WindowStyle Normal" + " -File '$scriptPath' -Resume" New-ItemProperty ` -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run' ` -Name $RESUME_KEY ` -Value $cmd ` -Force | Out-Null Write-Host '' Write-Host '============================================' Write-Host ' A restart is required to enable' Write-Host ' virtualization for Docker.' Write-Host '' Write-Host ' Installation will continue automatically' Write-Host ' after your computer restarts.' Write-Host ' You do not need to run this script again.' Write-Host '============================================' Write-Host '' $confirm = Read-Host 'Press Enter to restart now' Restart-Computer -Force exit # Won't reach here but good practice}function Remove-ResumeKey { Remove-ItemProperty ` -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run' ` -Name $RESUME_KEY ` -ErrorAction SilentlyContinue}
|
||||
|
||||
|
||||
|
||||
## 4.5 Docker Installation
|
||||
|
||||
function Invoke-InstallDocker { if (Get-Command docker -ErrorAction SilentlyContinue) { Write-Host 'Docker already installed.' Invoke-WaitForDockerDaemon return } Write-Host 'Downloading Docker Desktop...' $installer = '$env:TEMP\DockerDesktopInstaller.exe' Invoke-WebRequest -Uri $DOCKER_URL -OutFile $installer Write-Host 'Installing Docker Desktop (2-5 minutes)...' Start-Process $installer ` -ArgumentList 'install --quiet --accept-license' ` -Wait # Refresh PATH $env:Path = [Environment]::GetEnvironmentVariable('Path', 'Machine') Invoke-WaitForDockerDaemon}function Invoke-WaitForDockerDaemon { Write-Host 'Waiting for Docker daemon...' $timeout = 90 $elapsed = 0 while ($elapsed -lt $timeout) { try { docker info 2>&1 | Out-Null Write-Host 'Docker is running.' return } catch { } Start-Sleep -Seconds 3 $elapsed += 3 Write-Host " Still waiting... ($elapsed/$timeout sec)" } throw 'Docker did not start. Please restart and re-run.'}
|
||||
|
||||
|
||||
|
||||
## 4.6 Service Registration (NSSM)
|
||||
|
||||
function Invoke-RegisterService { Write-Host 'Installing service manager (NSSM)...' # Download NSSM if not present $nssm = '$INSTALL_DIR\nssm.exe' if (-not (Test-Path $nssm)) { $zip = '$env:TEMP\nssm.zip' Invoke-WebRequest -Uri $NSSM_URL -OutFile $zip Expand-Archive $zip '$env:TEMP\nssm' -Force Copy-Item '$env:TEMP\nssm\nssm-2.24\win64\nssm.exe' $nssm } # Remove existing service if present & $nssm stop $SERVICE_NAME 2>&1 | Out-Null & $nssm remove $SERVICE_NAME confirm 2>&1 | Out-Null # Docker executable path $docker = 'C:\Program Files\Docker\Docker\resources\bin\docker.exe' # Register service & $nssm install $SERVICE_NAME $docker ` 'compose --project-directory $INSTALL_DIR up' & $nssm set $SERVICE_NAME Start SERVICE_AUTO_START & $nssm set $SERVICE_NAME AppDirectory $INSTALL_DIR & $nssm set $SERVICE_NAME AppStdout '$INSTALL_DIR\logs\service.log' & $nssm set $SERVICE_NAME AppStderr '$INSTALL_DIR\logs\service-error.log' & $nssm set $SERVICE_NAME AppRotateFiles 1 & $nssm set $SERVICE_NAME AppRotateBytes 10485760 # 10MB Start-Service $SERVICE_NAME Write-Host 'Service registered and started.'}
|
||||
|
||||
|
||||
|
||||
# 5. Uninstall Script
|
||||
|
||||
The same script handles uninstallation via the -Uninstall flag. Clean removal for beta testing and troubleshooting.
|
||||
|
||||
|
||||
|
||||
function Invoke-Uninstall { Write-Host 'Uninstalling Platform...' # Stop and remove service $nssm = '$INSTALL_DIR\nssm.exe' if (Test-Path $nssm) { & $nssm stop $SERVICE_NAME 2>&1 | Out-Null & $nssm remove $SERVICE_NAME confirm 2>&1 | Out-Null } # Stop and remove containers if (Get-Command docker -ErrorAction SilentlyContinue) { Set-Location $INSTALL_DIR -ErrorAction SilentlyContinue docker compose down -v 2>&1 | Out-Null docker image rm registry.platform.com/platform:latest 2>&1 | Out-Null } # Remove resume registry key if present Remove-ResumeKey # Ask about data $keepData = Read-Host 'Keep your store data? (y/n)' if ($keepData -eq 'n') { Remove-Item -Recurse -Force $INSTALL_DIR -ErrorAction SilentlyContinue Write-Host 'All data removed.' } else { # Remove everything except data and backups Remove-Item '$INSTALL_DIR\*.yml' -ErrorAction SilentlyContinue Remove-Item '$INSTALL_DIR\*.conf' -ErrorAction SilentlyContinue Remove-Item '$INSTALL_DIR\*.env' -ErrorAction SilentlyContinue Remove-Item '$INSTALL_DIR\*.exe' -ErrorAction SilentlyContinue Write-Host 'Platform removed. Data preserved in $INSTALL_DIR\data' } Write-Host 'Uninstall complete.'}
|
||||
|
||||
|
||||
|
||||
# 6. Error Handling
|
||||
|
||||
All phases run inside a try-catch. Failures show a clear message with context and a support contact. State is preserved so the install can be retried from the failed phase without starting over.
|
||||
|
||||
|
||||
|
||||
try { Invoke-InstallPhases -StartFrom $currentPhase} catch { Write-Host '' Write-Host '============================================' Write-Host ' Installation failed.' Write-Host '' Write-Host " Error: $($_.Exception.Message)" Write-Host " Phase: $currentPhase" Write-Host '' Write-Host ' Your progress has been saved.' Write-Host ' Re-run this script to retry from' Write-Host ' where it failed.' Write-Host '' Write-Host ' Support: support@platform.com' Write-Host ' Include the error above in your email.' Write-Host '============================================' exit 1}
|
||||
|
||||
|
||||
|
||||
# 7. Phase 2 — Inno Setup .exe Wrapper
|
||||
|
||||
Once the platform is stable and beta is complete, the PowerShell script is wrapped in an Inno Setup installer. No logic changes to the PowerShell script are required — Inno Setup is purely a packaging and UI layer on top.
|
||||
|
||||
|
||||
|
||||
## 7.1 What Inno Setup Adds
|
||||
|
||||
- Professional installer UI — welcome screen, license agreement, progress bar
|
||||
|
||||
- Single signed .exe download — no script to right-click and run
|
||||
|
||||
- UAC elevation prompt handled automatically
|
||||
|
||||
- Code signed binary — no SmartScreen warning
|
||||
|
||||
- Windows Add/Remove Programs entry
|
||||
|
||||
- Desktop shortcut creation
|
||||
|
||||
- Built-in restart handling — Inno Setup has native resume-after-reboot support
|
||||
|
||||
- Uninstaller registered in Windows
|
||||
|
||||
|
||||
|
||||
## 7.2 Inno Setup Wraps the PowerShell Script
|
||||
|
||||
; installer.iss (Inno Setup script)[Setup]AppName=Platform Music StoreAppVersion=1.0DefaultDirName={autopf}\PlatformOutputBaseFilename=PlatformSetupSignTool=signtool[Files]; Bundle all dependenciesSource: install.ps1; DestDir: {tmp}Source: DockerDesktopInstaller.exe; DestDir: {tmp}Source: nssm.exe; DestDir: {app}Source: docker-compose.yml; DestDir: {app}Source: nginx.conf; DestDir: {app}[Run]; Execute PowerShell script silentlyFilename: powershell.exe; Parameters: -ExecutionPolicy Bypass -File '{tmp}\install.ps1' -DockerInstaller '{tmp}\DockerDesktopInstaller.exe' -InstallDir '{app}' StatusMsg: Installing Platform... Flags: runhidden waituntilterminated
|
||||
|
||||
|
||||
|
||||
## 7.3 Code Signing Requirements
|
||||
|
||||
Certificate Type
|
||||
|
||||
Cost/yr
|
||||
|
||||
Notes
|
||||
|
||||
Standard OV certificate
|
||||
|
||||
~$200-400
|
||||
|
||||
Removes unknown publisher warning. SmartScreen trust builds over time with installs.
|
||||
|
||||
EV (Extended Validation)
|
||||
|
||||
~$400-700
|
||||
|
||||
Instant SmartScreen trust — no reputation period. Requires hardware token (YubiKey). Recommended for commercial release.
|
||||
|
||||
|
||||
|
||||
# 8. Distribution Security
|
||||
|
||||
## 8.1 Source Code Protection
|
||||
|
||||
Python source code is compiled to a native binary using Nuitka before being packaged into the Docker image. The Docker image is distributed via a private registry — customers receive a container with a compiled binary, not readable Python source.
|
||||
|
||||
|
||||
|
||||
Build pipeline (GitHub Actions): 1. Run tests 2. Nuitka compile: python -m nuitka --standalone --onefile main.py Output: single compiled binary 3. Docker build: FROM ubuntu:24.04 COPY main /app/main # compiled binary only CMD ["/app/main"] # no Python, no .py files 4. Push to AWS ECR: registry.platform.com/platform:1.6.0 registry.platform.com/platform:latest 5. Update version registry API
|
||||
|
||||
|
||||
|
||||
## 8.2 Registry Access Control
|
||||
|
||||
The private Docker registry requires authentication. License validation returns a time-limited AWS ECR pull token scoped to read-only access on the platform image only.
|
||||
|
||||
|
||||
|
||||
License validation response:{ valid: true, modules: ['CORE', 'MOD-RENTALS', ...], registry_token: 'eyJ...', // ECR token, 12hr expiry registry_url: 'registry.platform.com', license_json: '{ signed license file... }'}ECR token properties: - Read-only (pull only, cannot push) - Scoped to platform image only - 12 hour expiry - Refreshed automatically by tray app - Even if extracted from config, limited blast radius
|
||||
|
||||
|
||||
|
||||
## 8.3 License Enforcement in Binary
|
||||
|
||||
The Ed25519 public key is embedded in the compiled binary at build time. License verification is compiled code — there is no license.py file to edit or bypass. The binary verifies the license file signature on every startup and checks module permissions on every licensed API route.
|
||||
|
||||
|
||||
|
||||
# 9. Post-Install Management
|
||||
|
||||
Phase 1 (PowerShell) customers manage the platform via command line. Phase 2 customers get the tray app. Both approaches documented below.
|
||||
|
||||
|
||||
|
||||
## 9.1 Phase 1 — Command Line Management
|
||||
|
||||
# All commands run from C:\Platformcd C:\Platform# Check statusdocker compose ps# View logsdocker compose logs --tail=50docker compose logs api --follow# Restartdocker compose restart# Stopdocker compose down# Startdocker compose up -d# Updatedocker compose pulldocker compose up -d# Backup nowdocker compose exec postgres pg_dump -U platform platform > backups\backup-$(Get-Date -Format 'yyyy-MM-dd').sql
|
||||
|
||||
|
||||
|
||||
## 9.2 Phase 2 — Tray App
|
||||
|
||||
The system tray app (built in Go for minimal footprint) provides a point-and-click interface for all common management tasks. Store owner never needs a terminal.
|
||||
|
||||
|
||||
|
||||
Right-click tray icon: ● Platform Manager v1.6.0 ────────────────────────── Open Platform → browser to localhost ────────────────────────── Status ✓ API Running ✓ Database Running ✓ Cache Running ────────────────────────── 🔵 Update Available (1.7.0) View Changelog Install Update ────────────────────────── Backup Now View Last Backup: Today 3:00am ✓ ────────────────────────── Restart Services View Logs ────────────────────────── Stop Platform
|
||||
BIN
planning/17_Backend_Technical_Architecture.docx
Normal file
BIN
planning/17_Backend_Technical_Architecture.docx
Normal file
Binary file not shown.
297
planning/17_Backend_Technical_Architecture.md
Normal file
297
planning/17_Backend_Technical_Architecture.md
Normal file
@@ -0,0 +1,297 @@
|
||||
Music Store Management Platform
|
||||
|
||||
Backend Technical Architecture
|
||||
|
||||
TypeScript | Fastify | Bun | Drizzle ORM | BullMQ
|
||||
|
||||
Version 1.0 | Draft
|
||||
|
||||
|
||||
|
||||
# 1. Technology Rationale
|
||||
|
||||
The platform is TypeScript end-to-end. Every application — desktop, mobile, web portals, and API — shares one language, one type system, and one validation library. This is the primary driver of the technology choice: a solo developer maintaining the full stack gets maximum benefit from type safety flowing across every boundary.
|
||||
|
||||
|
||||
|
||||
Decision
|
||||
|
||||
Rationale
|
||||
|
||||
TypeScript everywhere
|
||||
|
||||
One language across API, desktop, mobile, and web. Shared types and validation. Refactoring catches errors across all applications simultaneously.
|
||||
|
||||
Fastify over Express
|
||||
|
||||
Better TypeScript support, faster performance, schema-based validation built in, plugin architecture cleaner than Express middleware.
|
||||
|
||||
Bun over Node.js
|
||||
|
||||
Native TypeScript execution without compilation step. Faster startup. Built-in compile-to-binary for self-hosted distribution. Compatible with Node.js ecosystem.
|
||||
|
||||
Drizzle over Prisma
|
||||
|
||||
Schema defined in TypeScript — no separate DSL file. Fully typed queries. SQL-transparent — generated queries are readable and predictable.
|
||||
|
||||
BullMQ over Celery
|
||||
|
||||
TypeScript-native. Redis-based — same Redis already used for caching. No separate Python worker process needed.
|
||||
|
||||
Zod for validation
|
||||
|
||||
Single schema definition runs on both backend (request validation) and frontend (form validation). Eliminates duplicate validation logic.
|
||||
|
||||
|
||||
|
||||
# 2. Monorepo Structure
|
||||
|
||||
The entire platform lives in a single monorepo managed by Turborepo. All packages share the same TypeScript compiler configuration, lint rules, and test runner. Internal packages are referenced by workspace path — no publishing required.
|
||||
|
||||
|
||||
|
||||
/ (monorepo root) turbo.json pipeline config package.json workspace root tsconfig.base.json shared TS config /packages /shared shared types, schemas, business logic /backend Fastify API (Bun runtime) /desktop Electron + React /mobile React Native (iOS) /web-portal React (customer facing) /admin React (internal) /tools /installer PowerShell scripts + Go tray app /migration AIM MSSQL -> Postgres ETL /codegen OpenAPI client generation
|
||||
|
||||
|
||||
|
||||
# 3. Shared Package
|
||||
|
||||
The shared package is the most important piece of the monorepo. It contains everything that is used by more than one application. No business logic lives in individual apps — it lives here.
|
||||
|
||||
|
||||
|
||||
/packages/shared/src /types account.ts Account, Member, PaymentMethod rental.ts Rental, RentalType, RentalStatus lesson.ts Enrollment, LessonSession, LessonPlan repair.ts RepairTicket, RepairBatch, RepairPart inventory.ts Product, InventoryUnit, RepairPart payment.ts Transaction, PaymentMethod, Subscription accounting.ts JournalEntry, AccountCode license.ts License, LicenseModules index.ts re-exports everything /schemas Zod schemas (frontend + backend validation) account.schema.ts rental.schema.ts lesson.schema.ts repair.schema.ts inventory.schema.ts payment.schema.ts /business-logic rto.ts rent-to-own equity calculations pricing.ts discount engine, min price enforcement billing.ts proration calculations, billing groups license.ts license verification, module checks accounting.ts journal entry generation rules /constants modules.ts MODULE_IDS, feature flags tax.ts tax rate helpers /utils currency.ts formatting, rounding helpers dates.ts billing date utilities
|
||||
|
||||
|
||||
|
||||
## 3.1 Zod Schema Example — Shared Validation
|
||||
|
||||
// packages/shared/src/schemas/rental.schema.tsimport { z } from 'zod'export const RentalCreateSchema = z.object({ account_id: z.string().uuid(), member_id: z.string().uuid(), inventory_unit_id: z.string().uuid(), rental_type: z.enum([ 'month_to_month', 'rent_to_own', 'short_term', 'lease_purchase' ]), monthly_rate: z.number().positive().max(9999), deposit_amount: z.number().min(0).optional(), billing_anchor_day: z.number().int().min(1).max(28), billing_group: z.string().max(50).optional(), rto_purchase_price: z.number().positive().optional(), rto_equity_percent: z.number().min(0).max(100).optional(),})export type RentalCreate = z.infer<typeof RentalCreateSchema>// Used in backend: request body validation// Used in desktop: rental intake form validation// Used in mobile: same form, same rules// One definition, zero drift
|
||||
|
||||
|
||||
|
||||
## 3.2 Business Logic Example — RTO Calculation
|
||||
|
||||
// packages/shared/src/business-logic/rto.tsexport function calculateRTOEquity(params: { monthlyRate: number equityPercent: number paymentsReceived: number}): { equityAccumulated: number equityPerPayment: number} { const equityPerPayment = (params.monthlyRate * params.equityPercent) / 100 const equityAccumulated = equityPerPayment * params.paymentsReceived return { equityAccumulated, equityPerPayment }}export function calculateBuyoutAmount(params: { purchasePrice: number equityAccumulated: number}): number { return Math.max(0, params.purchasePrice - params.equityAccumulated )}// Same calculation runs on API, desktop, and portal// Customer sees same number the staff sees
|
||||
|
||||
|
||||
|
||||
# 4. Backend Package
|
||||
|
||||
The backend is a Fastify application running on the Bun runtime. It handles all API requests, payment provider integrations, background job scheduling, and PDF generation. For self-hosted deployments it compiles to a single standalone binary.
|
||||
|
||||
|
||||
|
||||
## 4.1 Directory Structure
|
||||
|
||||
/packages/backend/src main.ts entry point — registers plugins, starts server /plugins auth.ts JWT verification, role extraction license.ts license loading, module guard database.ts Drizzle connection pool redis.ts Redis connection cors.ts error-handler.ts /routes /v1 accounts.ts rentals.ts lessons.ts repairs.ts inventory.ts payments.ts accounting.ts reports.ts admin.ts webhooks.ts payment processor webhooks health.ts /services business logic — thin routes call services account.service.ts rental.service.ts lesson.service.ts repair.service.ts billing.service.ts accounting.service.ts pdf.service.ts notification.service.ts /db schema/ Drizzle table definitions accounts.ts rentals.ts lessons.ts repairs.ts inventory.ts payments.ts accounting.ts migrations/ Drizzle migration files index.ts db client export /payment-providers base.ts PaymentProvider interface stripe.ts Stripe implementation global-payments.ts Global Payments implementation factory.ts returns correct provider per store /jobs BullMQ workers billing.job.ts daily GP billing scheduler reminders.job.ts renewal and payment reminders backup.job.ts self-hosted backup trigger reports.job.ts scheduled report generation scheduler.ts registers all jobs with cron /license verify.ts Ed25519 signature verification guard.ts requireModule() Fastify hook
|
||||
|
||||
|
||||
|
||||
## 4.2 Route Pattern
|
||||
|
||||
Routes are thin — they validate input using shared Zod schemas, call a service, and return the result. All business logic lives in services.
|
||||
|
||||
|
||||
|
||||
// routes/v1/rentals.tsimport { FastifyPluginAsync } from 'fastify'import { RentalCreateSchema } from '@platform/shared'import { RentalService } from '../../services/rental.service'const rentals: FastifyPluginAsync = async (fastify) => { fastify.post('/', { preHandler: [ fastify.authenticate, // JWT check fastify.requireModule('MOD-RENTALS'), // license check fastify.requireRole('staff'), // role check ], schema: { body: RentalCreateSchema, // Zod validation } }, async (request, reply) => { const rental = await RentalService.create( fastify.db, request.companyId, request.body, request.user ) return reply.code(201).send(rental) } ) fastify.get('/:id', { preHandler: [fastify.authenticate], }, async (request, reply) => { const rental = await RentalService.getById( fastify.db, request.companyId, request.params.id ) if (!rental) return reply.code(404).send() return rental } )}export default rentals
|
||||
|
||||
|
||||
|
||||
## 4.3 Drizzle Schema Example
|
||||
|
||||
// db/schema/rentals.tsimport { pgTable, uuid, numeric, integer, timestamp, boolean, text, pgEnum} from 'drizzle-orm/pg-core'import { accounts } from './accounts'import { inventoryUnits } from './inventory'export const rentalTypeEnum = pgEnum('rental_type', [ 'month_to_month', 'rent_to_own', 'short_term', 'lease_purchase'])export const rentalStatusEnum = pgEnum('rental_status', [ 'active', 'returned', 'cancelled', 'completed'])export const rentals = pgTable('rental', { id: uuid('id').primaryKey().defaultRandom(), company_id: uuid('company_id').notNull(), account_id: uuid('account_id') .notNull() .references(() => accounts.id), inventory_unit_id: uuid('inventory_unit_id') .references(() => inventoryUnits.id), rental_type: rentalTypeEnum('rental_type').notNull(), status: rentalStatusEnum('status') .notNull().default('active'), monthly_rate: numeric('monthly_rate', { precision: 10, scale: 2 }).notNull(), billing_anchor_day: integer('billing_anchor_day').notNull(), billing_group: text('billing_group'), stripe_subscription_id: text('stripe_subscription_id'), rto_purchase_price: numeric('rto_purchase_price', { precision: 10, scale: 2 }), rto_equity_percent: numeric('rto_equity_percent', { precision: 5, scale: 2 }), rto_equity_accumulated: numeric('rto_equity_accumulated', { precision: 10, scale: 2 }) .default('0'), legacy_id: text('legacy_id'), created_at: timestamp('created_at').defaultNow(), updated_at: timestamp('updated_at').defaultNow(),})// Fully typed — TypeScript infers the shape from the schemaexport type Rental = typeof rentals.$inferSelectexport type RentalInsert = typeof rentals.$inferInsert
|
||||
|
||||
|
||||
|
||||
## 4.4 Payment Provider Interface
|
||||
|
||||
// payment-providers/base.tsexport interface PaymentProvider { readonly name: string // Customers createCustomer(account: Account): Promise<ProcessorCustomer> deleteCustomer(processorId: string): Promise<void> // Payment methods createSetupSession(): Promise<SetupSession> attachPaymentMethod( customerId: string, token: string ): Promise<PaymentMethod> // One-time charges charge(params: ChargeParams): Promise<ChargeResult> refund(params: RefundParams): Promise<RefundResult> // Subscriptions createSubscription( params: SubscriptionParams ): Promise<Subscription> addSubscriptionItem( subscriptionId: string, item: SubscriptionItem ): Promise<SubscriptionItem> removeSubscriptionItem( subscriptionId: string, itemId: string ): Promise<void> cancelSubscription(id: string): Promise<void> pauseSubscription(id: string): Promise<void> resumeSubscription(id: string): Promise<void> changeBillingAnchor( subscriptionId: string, day: number ): Promise<ProrationResult> // Terminal discoverReaders(): Promise<Reader[]> collectPayment( params: TerminalPaymentParams ): Promise<TerminalPaymentResult> // Webhooks parseWebhookEvent( payload: string, signature: string ): Promise<WebhookEvent>}// Factory returns correct provider per storeexport function getPaymentProvider( store: Store): PaymentProvider { switch (store.payment_processor) { case 'stripe': return new StripeProvider(store) case 'global_payments': return new GlobalPaymentsProvider(store) default: throw new Error('Unknown processor') }}
|
||||
|
||||
|
||||
|
||||
## 4.5 BullMQ Billing Scheduler
|
||||
|
||||
// jobs/billing.job.tsimport { Worker, Queue } from 'bullmq'import { getPaymentProvider } from '../payment-providers/factory'export const billingQueue = new Queue('billing', { connection: redis, defaultJobOptions: { attempts: 3, backoff: { type: 'exponential', delay: 5000 } }})export const billingWorker = new Worker( 'billing', async (job) => { const { companyId, entityType, entityId } = job.data const store = await StoreService.getById(db, companyId) const provider = getPaymentProvider(store) if (entityType === 'rental') { const rental = await RentalService.getById( db, companyId, entityId ) await provider.charge({ customerId: rental.account.processor_customer_id, amount: rental.monthly_rate, description: `Rental - ${rental.product_name}`, metadata: { rental_id: rental.id } }) await AccountingService.recordRentalPayment(db, rental) } }, { connection: redis, concurrency: 5 })// Scheduler registers daily cron// jobs/scheduler.tsawait billingQueue.add('daily-billing', { trigger: 'cron' }, { repeat: { pattern: '0 0 * * *' } } // midnight daily)
|
||||
|
||||
|
||||
|
||||
# 5. Bun Runtime
|
||||
|
||||
Bun is a fast JavaScript/TypeScript runtime that executes TypeScript natively without a compilation step. For development this means running the backend directly from .ts files. For self-hosted deployment it compiles to a single standalone binary.
|
||||
|
||||
|
||||
|
||||
## 5.1 Development
|
||||
|
||||
# Run backend directly — no tsc step neededbun run src/main.ts# Watch mode — restarts on file changesbun --watch run src/main.ts# Run testsbun test
|
||||
|
||||
|
||||
|
||||
## 5.2 Self-Hosted Binary Compilation
|
||||
|
||||
# Compile to standalone binary# No Bun or Node.js runtime required on target machinebun build src/main.ts \ --compile \ --outfile platform-api \ --target bun-linux-x64 # or bun-linux-arm64# Result: single ~50-80MB executable# Ships inside Docker image — no source code included# Multi-arch build in CI:bun build src/main.ts --compile \ --outfile platform-api-amd64 --target bun-linux-x64bun build src/main.ts --compile \ --outfile platform-api-arm64 --target bun-linux-arm64
|
||||
|
||||
|
||||
|
||||
## 5.3 Docker Image
|
||||
|
||||
# DockerfileFROM oven/bun:1 AS builderWORKDIR /buildCOPY packages/shared ./packages/sharedCOPY packages/backend ./packages/backendRUN cd packages/backend && \ bun install --frozen-lockfile && \ bun build src/main.ts \ --compile --outfile /build/platform-api# Runtime image — binary only, no sourceFROM ubuntu:24.04RUN apt-get update && apt-get install -y \ ca-certificates && rm -rf /var/lib/apt/lists/*COPY --from=builder /build/platform-api /app/platform-apiEXPOSE 8000CMD ["/app/platform-api"]# Result:# No TypeScript source in image# No Bun runtime in final image# No node_modules# Just the compiled binary + Ubuntu base
|
||||
|
||||
|
||||
|
||||
# 6. Drizzle ORM
|
||||
|
||||
Drizzle defines the database schema in TypeScript. All queries are fully typed — TypeScript knows the shape of every query result. Migrations are generated automatically from schema changes.
|
||||
|
||||
|
||||
|
||||
## 6.1 Typed Queries
|
||||
|
||||
// Fully typed — TypeScript infers return type from schemaconst rental = await db.query.rentals.findFirst({ where: and( eq(rentals.id, id), eq(rentals.company_id, companyId) // multi-tenant scoping ), with: { account: { with: { students: true } }, inventoryUnit: { with: { product: true } } }})// TypeScript knows: rental.account.students[0].first_name// No any, no casting, no guessing// Update with type safetyawait db.update(rentals) .set({ status: 'returned', // TypeScript enforces enum rto_equity_accumulated: '450.00' }) .where(eq(rentals.id, id))
|
||||
|
||||
|
||||
|
||||
## 6.2 Migrations
|
||||
|
||||
# Generate migration from schema changesbunx drizzle-kit generate# Apply migrationsbunx drizzle-kit migrate# Migrations are plain SQL files — readable and reviewable# /packages/backend/src/db/migrations/# 0001_initial_schema.sql# 0002_add_repair_parts.sql# 0003_add_lesson_plans.sql# Applied automatically on startup in production# Self-hosted: runs during update process
|
||||
|
||||
|
||||
|
||||
# 7. Complete Tech Stack
|
||||
|
||||
Layer
|
||||
|
||||
Technology
|
||||
|
||||
Notes
|
||||
|
||||
Language
|
||||
|
||||
TypeScript 5.x
|
||||
|
||||
All packages — API, desktop, mobile, web
|
||||
|
||||
Runtime
|
||||
|
||||
Bun
|
||||
|
||||
Native TS execution, compile-to-binary for self-hosted
|
||||
|
||||
API Framework
|
||||
|
||||
Fastify
|
||||
|
||||
Fast, TypeScript-first, schema validation built in
|
||||
|
||||
ORM
|
||||
|
||||
Drizzle ORM
|
||||
|
||||
TypeScript schema, fully typed queries, SQL-transparent
|
||||
|
||||
Validation
|
||||
|
||||
Zod
|
||||
|
||||
Shared schemas — frontend and backend use same definitions
|
||||
|
||||
Job Queue
|
||||
|
||||
BullMQ
|
||||
|
||||
Redis-based, TypeScript-native, retry logic built in
|
||||
|
||||
Database
|
||||
|
||||
PostgreSQL 16
|
||||
|
||||
Aurora on SaaS, Docker container on self-hosted
|
||||
|
||||
Cache / Queue broker
|
||||
|
||||
Redis 7
|
||||
|
||||
Session cache, BullMQ broker, rate limiting
|
||||
|
||||
Desktop
|
||||
|
||||
Electron + React
|
||||
|
||||
Windows/Mac/Linux, imports from shared package
|
||||
|
||||
Mobile
|
||||
|
||||
React Native
|
||||
|
||||
iOS, Stripe Terminal Bluetooth, imports from shared
|
||||
|
||||
Web portals
|
||||
|
||||
React + Vite
|
||||
|
||||
Customer portal + admin panel
|
||||
|
||||
PDF generation
|
||||
|
||||
Puppeteer
|
||||
|
||||
HTML templates rendered to PDF for invoices/reports
|
||||
|
||||
Auth
|
||||
|
||||
Clerk or Auth0
|
||||
|
||||
JWT-based, RBAC, handles MFA
|
||||
|
||||
Payments — Stripe
|
||||
|
||||
stripe npm package
|
||||
|
||||
Official TypeScript SDK
|
||||
|
||||
Payments — GP
|
||||
|
||||
globalpayments-sdk
|
||||
|
||||
Node.js SDK
|
||||
|
||||
Monorepo
|
||||
|
||||
Turborepo
|
||||
|
||||
Build pipeline, caching, workspace management
|
||||
|
||||
Testing
|
||||
|
||||
Vitest
|
||||
|
||||
Fast, native ESM, compatible with Bun
|
||||
|
||||
Tray app
|
||||
|
||||
Go
|
||||
|
||||
System tray manager for self-hosted — tiny binary
|
||||
|
||||
Installer
|
||||
|
||||
PowerShell → Inno Setup
|
||||
|
||||
Phase 1: PS1 script. Phase 2: .exe wrapper.
|
||||
|
||||
|
||||
|
||||
# 8. CI/CD Pipeline
|
||||
|
||||
GitHub Actions handles the full build and release pipeline. SaaS deployments push to EKS. Self-hosted releases push compiled binaries to AWS ECR.
|
||||
|
||||
|
||||
|
||||
On push to main: 1. Typecheck all packages (tsc --noEmit) 2. Lint (ESLint) 3. Test (vitest) 4. Build shared package 5. Bun compile backend → binary (amd64 + arm64) 6. Docker build → push to ECR registry.platform.com/platform:sha-abc123 registry.platform.com/platform:latest 7. Update version registry APIOn tag (release): Same as above plus: 8. Tag Docker image with version registry.platform.com/platform:1.6.0 9. Build Electron desktop app (auto-updates via electron-updater) 10. Build React Native iOS app (TestFlight distribution) 11. Deploy SaaS to EKS (rolling update) 12. Update changelog and release notes
|
||||
|
||||
|
||||
|
||||
# 9. Local Development Setup
|
||||
|
||||
# Prerequisites: Bun, Docker Desktop, Node.js (for tools)# Clone and installgit clone https://github.com/lunarfront/platformcd platformbun install# Start dependencies (Postgres + Redis)docker compose -f docker-compose.dev.yml up -d# Run database migrationscd packages/backendbunx drizzle-kit migrate# Start all packages in dev modebun run dev # Turborepo starts: # backend localhost:8000 # admin localhost:3001 # web-portal localhost:3002 # desktop Electron window# Run testsbun testbun test --watch
|
||||
624
planning/18_Implementation_Roadmap.md
Normal file
624
planning/18_Implementation_Roadmap.md
Normal file
@@ -0,0 +1,624 @@
|
||||
Forte — Music Store Management Platform
|
||||
|
||||
Implementation Roadmap
|
||||
|
||||
Version 1.0 | Draft
|
||||
|
||||
|
||||
|
||||
# 1. Purpose
|
||||
|
||||
This document defines the phased implementation order for Forte — 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 | Forte
|
||||
Package namespace | `@forte/shared`, `@forte/backend`, etc.
|
||||
Database (dev) | `forte`
|
||||
Database (test) | `forte_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 `forte_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` (`@forte/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` (`@forte/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, `forte_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 (`@forte/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)
|
||||
263
planning/19_Domain_Personnel.md
Normal file
263
planning/19_Domain_Personnel.md
Normal file
@@ -0,0 +1,263 @@
|
||||
Forte — Music Store Management Platform
|
||||
|
||||
Domain Design: Personnel Management
|
||||
|
||||
Version 1.0 | Draft
|
||||
|
||||
|
||||
|
||||
# 1. Overview
|
||||
|
||||
The Personnel domain manages employee records, time clock, time-off requests, and work schedules. This is foundational infrastructure — every other domain references employees (POS operators, technicians, instructors, drivers). This domain provides the employee entity and workforce management tools.
|
||||
|
||||
Personnel management is a licensed module (MOD-PERSONNEL). Without it, the platform still tracks basic user accounts for login and role-based access. The module adds time clock, scheduling, time-off, and labor reporting.
|
||||
|
||||
|
||||
|
||||
# 2. Core Concepts
|
||||
|
||||
## 2.1 Employee
|
||||
|
||||
An employee is a person who works for the company. They have a user account for system login and an employee record for HR/scheduling purposes. An employee can work at any location within the company — they are not locked to a single location.
|
||||
|
||||
- Linked to a `user` account for authentication
|
||||
- Has a home location (primary work site) but can clock in at any location
|
||||
- Roles: admin, manager, staff, technician, instructor
|
||||
- An employee can hold multiple roles (e.g. a technician who also teaches lessons)
|
||||
- Pay rate tracked for labor cost calculations (repair labor, lesson instructor pay)
|
||||
- Employment status: active, inactive, terminated
|
||||
|
||||
## 2.2 Time Clock
|
||||
|
||||
Employees clock in and out to track hours worked. Clock entries record which location the employee is working at, supporting multi-location operations.
|
||||
|
||||
- Clock in/out via desktop app or web UI
|
||||
- Location recorded at clock-in — employee may work at different locations on different days
|
||||
- Break tracking — paid and unpaid breaks
|
||||
- Overtime calculated based on configurable rules (weekly threshold, daily threshold)
|
||||
- Clock entries editable by managers with audit trail
|
||||
|
||||
## 2.3 Schedules
|
||||
|
||||
Work schedules define when employees are expected to work. Schedules are per-location and per-week.
|
||||
|
||||
- Weekly recurring schedules with override capability for specific dates
|
||||
- Shift-based — start time, end time, location, role
|
||||
- Instructors have dual scheduling: lesson schedule (from Lessons domain) + store shift schedule
|
||||
- Schedule conflicts detected — cannot schedule same employee at two locations simultaneously
|
||||
- Published schedules visible to employees (future: via portal or mobile)
|
||||
|
||||
## 2.4 Time Off
|
||||
|
||||
Employees can request time off. Managers approve or deny. Approved time off blocks scheduling for those dates.
|
||||
|
||||
- Request types: vacation, sick, personal, unpaid
|
||||
- Accrual tracking — configurable accrual rates per type
|
||||
- Manager approval workflow with notification
|
||||
- Approved time off appears on schedule as blocked
|
||||
- Annual carryover rules configurable per type
|
||||
|
||||
|
||||
|
||||
# 3. Database Schema
|
||||
|
||||
## 3.1 employee
|
||||
|
||||
Column | Type | Notes
|
||||
id | uuid PK |
|
||||
company_id | uuid FK | Tenant scoping
|
||||
user_id | uuid FK | Linked login account — nullable for employees not yet set up with system access
|
||||
first_name | varchar |
|
||||
last_name | varchar |
|
||||
email | varchar | Work email
|
||||
phone | varchar |
|
||||
home_location_id | uuid FK | Primary work location
|
||||
roles | text[] | Array of roles: admin, manager, staff, technician, instructor
|
||||
hire_date | date |
|
||||
termination_date | date | Nullable — set when terminated
|
||||
employment_status | enum | active, inactive, terminated
|
||||
pay_type | enum | hourly, salary, commission, per_lesson
|
||||
pay_rate | numeric(10,2) | Hourly rate or salary amount
|
||||
overtime_eligible | boolean |
|
||||
notes | text | Internal HR notes
|
||||
legacy_id | varchar | AIM employee ID
|
||||
created_at | timestamptz |
|
||||
updated_at | timestamptz |
|
||||
|
||||
## 3.2 time_clock_entry
|
||||
|
||||
Column | Type | Notes
|
||||
id | uuid PK |
|
||||
company_id | uuid FK |
|
||||
employee_id | uuid FK |
|
||||
location_id | uuid FK | Where the employee clocked in
|
||||
clock_in | timestamptz |
|
||||
clock_out | timestamptz | Nullable — null while clocked in
|
||||
break_minutes | integer | Total break time in minutes
|
||||
break_type | enum | paid, unpaid, none
|
||||
total_hours | numeric(6,2) | Computed: clock_out - clock_in - unpaid breaks
|
||||
is_overtime | boolean | Flagged based on overtime rules
|
||||
overtime_hours | numeric(6,2) | Hours beyond overtime threshold
|
||||
edited | boolean | True if entry was modified after clock-out
|
||||
edited_by | uuid FK | Manager who edited
|
||||
edit_reason | text | Required if edited
|
||||
created_at | timestamptz |
|
||||
|
||||
## 3.3 schedule
|
||||
|
||||
Column | Type | Notes
|
||||
id | uuid PK |
|
||||
company_id | uuid FK |
|
||||
employee_id | uuid FK |
|
||||
location_id | uuid FK |
|
||||
schedule_date | date | Specific date for this shift
|
||||
start_time | time |
|
||||
end_time | time |
|
||||
role | varchar | What role for this shift (staff, technician, instructor)
|
||||
is_recurring | boolean | True if generated from a recurring template
|
||||
recurring_template_id | uuid FK | Nullable — links to the template that generated this
|
||||
notes | text |
|
||||
created_by | uuid FK | Manager who created
|
||||
created_at | timestamptz |
|
||||
updated_at | timestamptz |
|
||||
|
||||
## 3.4 schedule_recurring_template
|
||||
|
||||
Weekly recurring schedule patterns. The system generates concrete `schedule` entries from these templates.
|
||||
|
||||
Column | Type | Notes
|
||||
id | uuid PK |
|
||||
company_id | uuid FK |
|
||||
employee_id | uuid FK |
|
||||
location_id | uuid FK |
|
||||
day_of_week | integer | 0=Sunday through 6=Saturday
|
||||
start_time | time |
|
||||
end_time | time |
|
||||
role | varchar |
|
||||
effective_from | date | When this pattern starts
|
||||
effective_until | date | Nullable — open-ended if null
|
||||
is_active | boolean |
|
||||
created_at | timestamptz |
|
||||
|
||||
## 3.5 time_off_request
|
||||
|
||||
Column | Type | Notes
|
||||
id | uuid PK |
|
||||
company_id | uuid FK |
|
||||
employee_id | uuid FK |
|
||||
request_type | enum | vacation, sick, personal, unpaid
|
||||
start_date | date |
|
||||
end_date | date |
|
||||
total_days | numeric(4,1) | Supports half days
|
||||
status | enum | pending, approved, denied, cancelled
|
||||
reason | text | Employee's reason
|
||||
manager_notes | text | Manager's response
|
||||
reviewed_by | uuid FK | Manager who reviewed
|
||||
reviewed_at | timestamptz |
|
||||
created_at | timestamptz |
|
||||
|
||||
## 3.6 time_off_balance
|
||||
|
||||
Tracks accrued and used time off per employee per type per year.
|
||||
|
||||
Column | Type | Notes
|
||||
id | uuid PK |
|
||||
company_id | uuid FK |
|
||||
employee_id | uuid FK |
|
||||
year | integer | Calendar year
|
||||
request_type | enum | vacation, sick, personal
|
||||
accrued | numeric(5,1) | Days accrued this year
|
||||
used | numeric(5,1) | Days used this year
|
||||
carried_over | numeric(5,1) | Days carried from previous year
|
||||
available | numeric(5,1) | Computed: accrued + carried_over - used
|
||||
created_at | timestamptz |
|
||||
updated_at | timestamptz |
|
||||
|
||||
|
||||
|
||||
# 4. Overtime Rules
|
||||
|
||||
Overtime configuration is per-company, stored in company settings.
|
||||
|
||||
Setting | Default | Notes
|
||||
weekly_overtime_threshold | 40 | Hours per week before overtime kicks in
|
||||
daily_overtime_threshold | null | Nullable — some states require daily overtime (e.g. California = 8 hrs)
|
||||
overtime_multiplier | 1.5 | Pay multiplier for overtime hours
|
||||
double_time_threshold | null | Nullable — hours per day before double time (e.g. California = 12 hrs)
|
||||
double_time_multiplier | 2.0 | Pay multiplier for double time
|
||||
|
||||
Overtime is calculated at clock-out based on the current week's total hours. If daily thresholds are configured, both daily and weekly are evaluated and the higher overtime amount applies.
|
||||
|
||||
|
||||
|
||||
# 5. Key Workflows
|
||||
|
||||
## 5.1 Clock In / Out
|
||||
|
||||
- Employee opens desktop app or web UI
|
||||
- Selects "Clock In" — system records current time, location, and employee
|
||||
- On clock out, system records end time, calculates total hours and break time
|
||||
- If total weekly hours exceed overtime threshold, overtime flagged
|
||||
- Manager can edit clock entries with required reason — logged in audit trail
|
||||
|
||||
## 5.2 Schedule Creation
|
||||
|
||||
- Manager creates recurring schedule templates for each employee
|
||||
- System generates concrete schedule entries for upcoming weeks
|
||||
- Manager can override specific dates (e.g. swap shifts, add extra coverage)
|
||||
- Instructor lesson schedule slots (from Lessons domain) display alongside store shifts for visibility
|
||||
- Schedule conflicts flagged — same employee at two locations at the same time
|
||||
|
||||
## 5.3 Time Off Request
|
||||
|
||||
- Employee submits request specifying dates, type, and reason
|
||||
- Manager receives notification
|
||||
- Manager approves or denies with optional notes
|
||||
- Approved time off deducts from employee's time_off_balance
|
||||
- Schedule entries for approved dates are flagged or removed
|
||||
- If employee is an instructor, lesson sessions for those dates can be auto-cancelled or flagged for makeup
|
||||
|
||||
## 5.4 Pay Period Reporting
|
||||
|
||||
- Manager selects date range for pay period
|
||||
- System generates report: employee, regular hours, overtime hours, total hours
|
||||
- Export to CSV for payroll processing (platform does not run payroll — exports data for external payroll service)
|
||||
- Includes time-off hours taken by type
|
||||
|
||||
|
||||
|
||||
# 6. Integration with Other Domains
|
||||
|
||||
Domain | Integration
|
||||
Lessons | Instructors are employees. Lesson schedule_slot.instructor_id references employee. Time off auto-flags affected lesson sessions.
|
||||
Repairs | Technicians are employees. repair_ticket.assigned_technician_id references employee. Labor cost calculated from employee pay_rate.
|
||||
Sales/POS | transaction.processed_by references employee. Drawer sessions linked to employee.
|
||||
Delivery | delivery_event.driver_employee_id references employee.
|
||||
Accounting | Labor costs from repair tickets use employee.pay_rate for margin calculation. Payroll export supports journal entry generation for labor expense.
|
||||
|
||||
|
||||
|
||||
# 7. Business Rules
|
||||
|
||||
- Employee must have unique email within company
|
||||
- Clock entries cannot overlap — cannot be clocked in at two locations simultaneously
|
||||
- Clock-out required before new clock-in (system auto-clocks out at midnight if forgotten — flagged for manager review)
|
||||
- Time-off requests for past dates require manager approval
|
||||
- Schedule recurring templates auto-generate entries 4 weeks ahead (configurable)
|
||||
- Terminated employees retain all historical records (time clock, schedules, time off) — never deleted
|
||||
- Employee pay_rate changes are effective immediately — historical clock entries retain the rate at time of clock-out
|
||||
- Per-lesson instructors: pay tracked per lesson session attended, not via time clock
|
||||
|
||||
|
||||
|
||||
# 8. Reporting
|
||||
|
||||
Report | Description
|
||||
Hours summary | Total regular + overtime hours per employee per period
|
||||
Overtime report | Employees who hit overtime threshold — broken out by daily vs weekly
|
||||
Time-off balances | Current accrued, used, and available days per employee per type
|
||||
Schedule coverage | Shifts per location per day — identifies understaffed days
|
||||
Clock edit audit | All manager edits to time clock entries with reasons
|
||||
Labor cost by department | Hours * pay_rate grouped by role (technician, instructor, staff)
|
||||
Attendance | Scheduled vs actual clock-in times — identifies chronic lateness
|
||||
Payroll export | CSV export formatted for common payroll services (QuickBooks Payroll, Gusto, ADP)
|
||||
Reference in New Issue
Block a user