profile_image_file_id on member table, entity_type=member category=profile in file storage. UI shows silhouette placeholder when empty.
539 lines
9.2 KiB
Markdown
539 lines
9.2 KiB
Markdown
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
|
|
|
|
Auto-generated 6-digit random number, unique per company. Human-readable account ID.
|
|
|
|
name
|
|
|
|
varchar
|
|
|
|
Account/business name
|
|
|
|
email
|
|
|
|
varchar
|
|
|
|
Primary contact email
|
|
|
|
phone
|
|
|
|
varchar
|
|
|
|
Primary phone
|
|
|
|
address
|
|
|
|
jsonb
|
|
|
|
Street, city, state, zip
|
|
|
|
|
|
billing_mode
|
|
|
|
enum
|
|
|
|
consolidated | split
|
|
|
|
primary_member_id
|
|
|
|
uuid FK
|
|
|
|
Nullable — references the primary contact member on this account. Auto-set when first member is added.
|
|
|
|
is_active
|
|
|
|
boolean
|
|
|
|
Default true — soft delete flag. Financial history must be retained.
|
|
|
|
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
|
|
|
|
tax_exempt_status
|
|
|
|
enum
|
|
|
|
none | pending | approved — default "none"
|
|
|
|
tax_exempt_certificate_number
|
|
|
|
varchar
|
|
|
|
State resale certificate or exemption number
|
|
|
|
tax_exempt_certificate_expiry
|
|
|
|
date
|
|
|
|
Nullable — expiration date of certificate
|
|
|
|
tax_exempt_approved_by
|
|
|
|
uuid FK
|
|
|
|
Employee who verified and approved exemption
|
|
|
|
tax_exempt_approved_at
|
|
|
|
timestamptz
|
|
|
|
When exemption was verified
|
|
|
|
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
|
|
|
|
member_number
|
|
|
|
varchar
|
|
|
|
Auto-generated 6-digit random number, unique per company. Human-readable member ID.
|
|
|
|
profile_image_file_id
|
|
|
|
uuid FK
|
|
|
|
Nullable — references file.id. Profile photo for the member. Uploaded via the file storage API with entity_type = member, category = profile.
|
|
|
|
first_name
|
|
|
|
varchar
|
|
|
|
|
|
|
|
last_name
|
|
|
|
varchar
|
|
|
|
|
|
|
|
date_of_birth
|
|
|
|
date
|
|
|
|
Optional — used to derive is_minor if set
|
|
|
|
is_minor
|
|
|
|
boolean
|
|
|
|
Default false. Auto-derived from date_of_birth if provided, but can be set manually (e.g. parent declines to give DOB). Manual flag takes precedence over DOB calculation.
|
|
|
|
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
|
|
|
|
updated_at
|
|
|
|
timestamptz
|
|
|
|
|
|
|
|
|
|
|
|
## 3.3 member_identifier
|
|
|
|
Identity documents associated with a member. Supports multiple IDs per member (driver's license, passport, school ID). Images stored as base64 in Postgres for simplified backup/restore.
|
|
|
|
Column | Type | Notes
|
|
id | uuid PK |
|
|
member_id | uuid FK | References member
|
|
company_id | uuid FK | Tenant scoping
|
|
type | varchar | Constrained: drivers_license, passport, school_id
|
|
label | varchar | Optional display label
|
|
value | varchar | The ID number
|
|
issuing_authority | varchar | e.g. "State of Texas", "US Dept of State"
|
|
issued_date | date |
|
|
expires_at | date |
|
|
image_front | text | Base64-encoded front image
|
|
image_back | text | Base64-encoded back image
|
|
notes | text |
|
|
is_primary | boolean | Default false — which ID to display by default
|
|
created_at | timestamptz |
|
|
updated_at | timestamptz |
|
|
|
|
|
|
|
|
## 3.4 account_processor_link
|
|
|
|
Links an account to a payment processor's customer record. This is processor-agnostic — supports Stripe, Global Payments, or any future processor. Replaces the previous `stripe_customer_id` column that was directly on the account table.
|
|
|
|
Column
|
|
|
|
Type
|
|
|
|
Notes
|
|
|
|
id
|
|
|
|
uuid PK
|
|
|
|
|
|
|
|
account_id
|
|
|
|
uuid FK
|
|
|
|
|
|
|
|
company_id
|
|
|
|
uuid FK
|
|
|
|
Tenant scoping
|
|
|
|
processor
|
|
|
|
enum
|
|
|
|
stripe | global_payments
|
|
|
|
processor_customer_id
|
|
|
|
varchar
|
|
|
|
The processor's customer ID (e.g. cus_xxx for Stripe, GP customer token for Global Payments)
|
|
|
|
is_active
|
|
|
|
boolean
|
|
|
|
Allows deactivating without deleting
|
|
|
|
created_at
|
|
|
|
timestamptz
|
|
|
|
|
|
|
|
An account may have links to multiple processors simultaneously — this is expected during migration from one processor to another (e.g. AIM legacy → Stripe transition).
|
|
|
|
## 3.5 account_payment_method
|
|
|
|
Tracks payment methods on file. Card data lives in the processor — this table only stores references for display and selection.
|
|
|
|
Column
|
|
|
|
Type
|
|
|
|
Notes
|
|
|
|
id
|
|
|
|
uuid PK
|
|
|
|
|
|
|
|
account_id
|
|
|
|
uuid FK
|
|
|
|
|
|
|
|
company_id
|
|
|
|
uuid FK
|
|
|
|
Tenant scoping
|
|
|
|
processor
|
|
|
|
enum
|
|
|
|
stripe | global_payments
|
|
|
|
processor_payment_method_id
|
|
|
|
varchar
|
|
|
|
Processor's payment method reference (pm_xxx for Stripe, token for GP)
|
|
|
|
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 migrated from legacy processor and needs re-entry
|
|
|
|
created_at
|
|
|
|
timestamptz
|
|
|
|
|
|
|
|
|
|
|
|
## 3.6 tax_exemption
|
|
|
|
Tax exemptions are tracked in a separate table (not on account) for audit history. An account can have multiple exemption records over time (expired, revoked, renewed).
|
|
|
|
Column | Type | Notes
|
|
id | uuid PK |
|
|
account_id | uuid FK |
|
|
company_id | uuid FK | Tenant scoping
|
|
status | enum | none, pending, approved
|
|
certificate_number | varchar | Required
|
|
certificate_type | varchar | e.g. "resale", "nonprofit"
|
|
issuing_state | varchar(2) | State abbreviation
|
|
expires_at | date |
|
|
approved_by | uuid | Employee who verified
|
|
approved_at | timestamptz |
|
|
revoked_by | uuid |
|
|
revoked_at | timestamptz |
|
|
revoked_reason | text |
|
|
notes | text |
|
|
created_at | timestamptz |
|
|
updated_at | timestamptz |
|
|
|
|
Action endpoints: POST /tax-exemptions/:id/approve, POST /tax-exemptions/:id/revoke (requires reason).
|
|
|
|
|
|
|
|
# 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
|
|
|
|
- Account numbers auto-generated as 6-digit random numbers, unique per company
|
|
|
|
- Member numbers auto-generated as 6-digit random numbers, unique per company
|
|
|
|
- First member added to an account is auto-set as primary_member_id
|
|
|
|
- Members can be moved between accounts via POST /members/:id/move (optional accountId — creates new account if omitted)
|
|
|
|
- isMinor on member: explicit flag takes precedence, else derived from date_of_birth, else defaults to false
|
|
|
|
- Tax exemptions tracked in separate tax_exemption table for audit history — not on account directly
|
|
|
|
- Tax-exempt accounts must have a valid certificate number and expiry date before status can be set to "approved"
|
|
|
|
- Expired tax exemption certificates revert to "pending" — staff prompted to collect updated certificate
|
|
|
|
- Tax exemption status changes logged with who approved/revoked, when, and reason
|
|
|
|
|
|
|
|
# 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 |