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:
Ryan Moon
2026-03-27 14:51:23 -05:00
commit 5f8726ee4e
36 changed files with 9558 additions and 0 deletions

Binary file not shown.

View 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.

Binary file not shown.

View 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

Binary file not shown.

View 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', 'Ferrees Tools'
contact_name
varchar
Primary contact
email
varchar
phone
varchar
website
varchar
account_number
varchar
Stores 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

Binary file not shown.

View 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

Binary file not shown.

View 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

Binary file not shown.

View 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

Binary file not shown.

View 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

Binary file not shown.

View 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.

Binary file not shown.

View 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

Binary file not shown.

View 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

Binary file not shown.

View 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 (128) 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

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

View 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)

View 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.

Binary file not shown.

View 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

Binary file not shown.

View 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

Binary file not shown.

View 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

View 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 16 (scaffold through rentals + desktop app).
Phases 58 are largely independent after Phase 4 and can be interleaved. Phases 1113 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 10001320, liabilities 20002400, revenue 40004500, contra 49004920, COGS 50005100, expenses 60006300)
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 16:
- 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)

View 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)