364 lines
10 KiB
Markdown
364 lines
10 KiB
Markdown
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
|
||
|
||
|
||
|
||
# 7. Rental Agreement Contracts
|
||
|
||
Every rental requires a signed agreement before the instrument leaves the store. Agreements are generated from templates, capture all rental terms, and are stored as signed documents.
|
||
|
||
## 7.1 rental_agreement_template
|
||
|
||
Store-configurable contract templates. Most stores have one template per rental type, but can create variations for schools, events, etc.
|
||
|
||
Column | Type | Notes
|
||
id | uuid PK |
|
||
company_id | uuid FK | Tenant scoping
|
||
name | varchar | e.g. "Standard Month-to-Month", "School RTO Agreement", "Short-Term Event Rental"
|
||
rental_type | enum | month_to_month | rent_to_own | short_term | lease_purchase | all
|
||
body | text | Contract body with merge fields (see §7.3)
|
||
requires_signature | boolean | Default true — some internal/school agreements may waive
|
||
requires_guardian_signature | boolean | Default true if member.is_minor
|
||
include_insurance_terms | boolean | Whether insurance section is included
|
||
include_rto_terms | boolean | Whether rent-to-own buyout terms are included
|
||
include_damage_policy | boolean | Whether damage/loss liability section is included
|
||
is_default | boolean | Default template for this rental_type
|
||
is_active | boolean |
|
||
created_at | timestamptz |
|
||
updated_at | timestamptz |
|
||
|
||
## 7.2 rental_agreement
|
||
|
||
A generated, signed agreement instance linked to a specific rental.
|
||
|
||
Column | Type | Notes
|
||
id | uuid PK |
|
||
company_id | uuid FK |
|
||
rental_id | uuid FK | The rental this agreement covers
|
||
template_id | uuid FK | Template used to generate
|
||
account_id | uuid FK |
|
||
member_id | uuid FK | Member receiving the instrument
|
||
agreement_number | varchar | Human-readable ID (auto-generated)
|
||
generated_body | text | Full rendered contract text with all merge fields resolved — immutable after signing
|
||
status | enum | draft | pending_signature | signed | voided
|
||
signed_at | timestamptz | When signature was captured
|
||
signer_name | varchar | Name of person who signed
|
||
signer_relationship | varchar | Nullable — e.g. "parent", "guardian", "self"
|
||
guardian_signed_at | timestamptz | Nullable — when guardian signed (if minor)
|
||
guardian_name | varchar | Nullable
|
||
signature_method | enum | in_store_tablet | in_store_paper | email | portal
|
||
signature_data | text | Nullable — base64 signature image for tablet/paper capture
|
||
document_url | varchar | Nullable — URL to stored PDF in object storage
|
||
ip_address | varchar | Nullable — for email/portal signatures
|
||
voided_reason | text | Nullable — why agreement was voided
|
||
voided_by | uuid FK | Nullable
|
||
created_at | timestamptz |
|
||
updated_at | timestamptz |
|
||
|
||
## 7.3 Merge Fields
|
||
|
||
Templates use merge fields that are resolved at generation time. The generated_body stores the fully resolved text so the agreement is a permanent record even if account details change later.
|
||
|
||
Field | Resolves To
|
||
{{account_name}} | account.name
|
||
{{account_email}} | account.email
|
||
{{account_phone}} | account.phone
|
||
{{account_address}} | account.address (formatted)
|
||
{{member_name}} | member.first_name + member.last_name
|
||
{{member_is_minor}} | "Yes" / "No"
|
||
{{instrument_description}} | product.name + product.brand + product.model
|
||
{{instrument_size}} | inventory_unit.instrument_size or product.instrument_size
|
||
{{serial_number}} | inventory_unit.serial_number
|
||
{{instrument_condition}} | inventory_unit.condition at rental start
|
||
{{rental_type}} | Formatted rental type name
|
||
{{monthly_rate}} | rental.monthly_rate
|
||
{{deposit_amount}} | rental.deposit_amount
|
||
{{start_date}} | rental.start_date (formatted)
|
||
{{end_date}} | rental.end_date or "Open-ended"
|
||
{{rto_purchase_price}} | rental.rto_purchase_price (if RTO)
|
||
{{rto_equity_percent}} | rental.rto_equity_percent (if RTO)
|
||
{{company_name}} | company.name
|
||
{{company_address}} | company.address
|
||
{{company_phone}} | company.phone
|
||
{{today_date}} | Current date
|
||
{{agreement_number}} | rental_agreement.agreement_number
|
||
|
||
## 7.4 Agreement Workflow
|
||
|
||
1. Staff creates rental — system selects default template for rental_type (or staff picks one)
|
||
2. Agreement generated: merge fields resolved, generated_body populated, status = "draft"
|
||
3. Staff reviews agreement on screen — can edit before finalizing if needed
|
||
4. Signature capture:
|
||
a. **In-store tablet**: customer signs on screen, signature_data captured as base64
|
||
b. **In-store paper**: staff prints, customer signs physical copy, staff scans/uploads
|
||
c. **Email**: agreement emailed to account email with secure signing link
|
||
d. **Portal**: customer signs via self-service portal (if MOD-PORTAL licensed)
|
||
5. Agreement status updated to "signed" — PDF generated and stored
|
||
6. Rental cannot activate until agreement is signed (unless overridden by manager)
|
||
7. Signed agreement PDF available on rental record, account history, and customer portal
|
||
|
||
## 7.5 Guardian Signatures
|
||
|
||
- If member.is_minor = true, agreement requires guardian signature in addition to (or instead of) member signature
|
||
- Guardian must be an adult member on the same account, or signer_relationship recorded for non-member guardian
|
||
- Both signature fields must be completed before agreement is fully signed
|
||
- Email/portal signing flow sends to account primary email (assumed to be parent/guardian)
|
||
|
||
## 7.6 Agreement Delivery
|
||
|
||
- **Print**: generated as PDF, sent to receipt printer or standard printer
|
||
- **Email**: PDF attached to email sent to account.email
|
||
- **Portal**: PDF available in customer portal under "My Agreements"
|
||
- **All signed agreements**: stored in object storage (S3-compatible), URL recorded on rental_agreement.document_url
|
||
|
||
## 7.7 Voiding and Re-signing
|
||
|
||
- Voiding an agreement requires reason and manager approval
|
||
- Voiding does not cancel the rental — but rental cannot remain active without a signed agreement
|
||
- Common void reasons: incorrect terms, wrong instrument, customer requested change
|
||
- After voiding, a new agreement is generated from template (or modified) and signed
|
||
- Voided agreements retained for audit — never deleted
|
||
|
||
## 7.8 Business Rules
|
||
|
||
- Rental cannot activate without a signed agreement (manager override available with reason logged)
|
||
- Agreement text is immutable after signing — edits require void and re-sign
|
||
- generated_body is the legal record — template changes do not affect existing agreements
|
||
- Signature data retained for the life of the agreement (minimum 7 years per record retention policy)
|
||
- Agreement PDF regenerated on demand from generated_body + signature_data — not dependent on template
|
||
- Schools with bulk rentals (MOD-BATCH): single master agreement can cover multiple instruments for a school account
|
||
- Short-term rentals may use a simplified template with fewer terms
|
||
- Agreement history visible on account record — all past and current agreements listed |