Add planning docs for trade-ins, returns, tax exemptions, cycle counts, POs, bundles, backorders, barcode labels, instrument sizing, warranties, maintenance schedules, gift cards, layaway, rental agreements, and in-home trials
This commit is contained in:
@@ -101,6 +101,12 @@ enum
|
||||
|
||||
consolidated | split
|
||||
|
||||
is_active
|
||||
|
||||
boolean
|
||||
|
||||
Default true — soft delete flag. Financial history must be retained.
|
||||
|
||||
notes
|
||||
|
||||
text
|
||||
@@ -125,6 +131,36 @@ 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
|
||||
@@ -217,6 +253,10 @@ created_at
|
||||
|
||||
timestamptz
|
||||
|
||||
updated_at
|
||||
|
||||
timestamptz
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -377,6 +417,12 @@ timestamptz
|
||||
|
||||
- Duplicate account detection on email and phone during creation
|
||||
|
||||
- Tax-exempt accounts must have a valid certificate number and expiry date before status can be set to "approved"
|
||||
|
||||
- Expired tax exemption certificates revert account to "pending" — staff prompted to collect updated certificate
|
||||
|
||||
- Tax exemption status changes logged in audit trail (who approved, when, certificate details)
|
||||
|
||||
|
||||
|
||||
# 5. Key Workflows
|
||||
|
||||
@@ -224,7 +224,19 @@ timestamptz
|
||||
|
||||
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
|
||||
id, product_id, company_id, location_id, serial_number,condition (new|excellent|good|fair|poor),status (available|sold|rented|on_trial|in_repair|layaway|lost|retired),purchase_date, purchase_cost, notes,legacy_id, created_at
|
||||
|
||||
### Status Values
|
||||
|
||||
Status | Description | Set By
|
||||
available | In stock, ready for sale or rental | Default, return, restock
|
||||
sold | Purchased by customer | Sale transaction
|
||||
rented | Out on active rental contract | Rental activation
|
||||
on_trial | Out with customer on in-home trial | In-home trial checkout (07_Domain_Sales_POS.md §9)
|
||||
in_repair | In repair shop for service | Repair intake
|
||||
layaway | Reserved for layaway customer, not available | Layaway creation (08_Domain_Payments_Billing.md §9)
|
||||
lost | Unrecovered — trial, rental, or inventory discrepancy | Overdue escalation or cycle count
|
||||
retired | Permanently removed from inventory | Manual retirement
|
||||
|
||||
|
||||
|
||||
@@ -924,4 +936,313 @@ timestamptz
|
||||
|
||||
- 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
|
||||
- Negative qty_on_hand not permitted — system warns technician when stock is insufficient
|
||||
|
||||
|
||||
|
||||
# 8. Instrument Sizing
|
||||
|
||||
String instruments come in fractional sizes — critical for rentals, school orders, and customer matching. Size is tracked on both the product catalog and individual inventory units.
|
||||
|
||||
## 8.1 Size Values
|
||||
|
||||
Size | Instruments | Notes
|
||||
4/4 (full) | Violin, viola, cello, bass, guitar | Default adult size
|
||||
3/4 | Violin, cello, bass, guitar | Older students, smaller adults
|
||||
1/2 | Violin, cello, bass | Intermediate students
|
||||
1/4 | Violin, cello | Younger students
|
||||
1/8 | Violin | Beginner young students
|
||||
1/10 | Violin | Smallest common size
|
||||
1/16 | Violin | Rare — very young students
|
||||
15" | Viola | Measured in inches for viola
|
||||
15.5" | Viola | Common intermediate
|
||||
16" | Viola | Full-size standard
|
||||
16.5" | Viola | Large full-size
|
||||
|
||||
## 8.2 Schema Changes
|
||||
|
||||
**product** — add column:
|
||||
|
||||
Column | Type | Notes
|
||||
instrument_size | varchar | Nullable — only set for sized instruments. Free text to support both fractional (1/2) and inch (15.5") formats.
|
||||
|
||||
**inventory_unit** — add column:
|
||||
|
||||
Column | Type | Notes
|
||||
instrument_size | varchar | Nullable — size of this specific physical unit. May differ from product default (e.g. product "Yamaha Model 5 Violin" has units in multiple sizes).
|
||||
|
||||
## 8.3 Business Rules
|
||||
|
||||
- Size is optional — only relevant for sized instruments (strings, some guitars)
|
||||
- Product-level size is the default/catalog size — inventory units can override per-unit
|
||||
- Size is searchable and filterable in product lists and POS lookup
|
||||
- Rental matching: when a student needs a size, search filters by instrument_size
|
||||
- Size changes on rental returns are common (student grew) — logged in rental history
|
||||
|
||||
|
||||
|
||||
# 9. Inventory Cycle Counts
|
||||
|
||||
Physical inventory reconciliation ensures system counts match actual shelf counts. Cycle counts can be full-store or targeted (by category, location area, or supplier).
|
||||
|
||||
## 9.1 count_session
|
||||
|
||||
Column | Type | Notes
|
||||
id | uuid PK |
|
||||
company_id | uuid FK | Tenant scoping
|
||||
location_id | uuid FK | Which location is being counted
|
||||
name | varchar | e.g. "Q1 2025 Full Count", "Guitar Room Spot Check"
|
||||
count_type | enum | full | category | spot
|
||||
status | enum | draft | in_progress | review | completed | cancelled
|
||||
category_id | uuid FK | Nullable — set if count_type = category
|
||||
started_by | uuid FK | Employee who initiated
|
||||
started_at | timestamptz | When counting began
|
||||
completed_at | timestamptz | When finalized
|
||||
notes | text |
|
||||
created_at | timestamptz |
|
||||
|
||||
## 9.2 count_entry
|
||||
|
||||
One row per product counted in a session. For serialized products, one row per unit.
|
||||
|
||||
Column | Type | Notes
|
||||
id | uuid PK |
|
||||
count_session_id | uuid FK |
|
||||
product_id | uuid FK |
|
||||
inventory_unit_id | uuid FK | Nullable — set for serialized items
|
||||
expected_qty | integer | System qty at time count started
|
||||
counted_qty | integer | Physical count entered by staff
|
||||
variance | integer | counted_qty - expected_qty (computed)
|
||||
counted_by | uuid FK | Employee who counted this item
|
||||
counted_at | timestamptz | When this entry was recorded
|
||||
notes | text | Explanation for variance
|
||||
created_at | timestamptz |
|
||||
|
||||
## 9.3 count_adjustment
|
||||
|
||||
When a count session is completed, variances are applied as inventory adjustments. Each adjustment is an auditable record.
|
||||
|
||||
Column | Type | Notes
|
||||
id | uuid PK |
|
||||
count_session_id | uuid FK |
|
||||
count_entry_id | uuid FK |
|
||||
product_id | uuid FK |
|
||||
inventory_unit_id | uuid FK | Nullable
|
||||
previous_qty | integer | qty_on_hand before adjustment
|
||||
adjusted_qty | integer | qty_on_hand after adjustment
|
||||
adjustment_reason | enum | cycle_count | damaged | stolen | found | data_entry_error
|
||||
approved_by | uuid FK | Manager who approved the adjustment
|
||||
approved_at | timestamptz |
|
||||
created_at | timestamptz |
|
||||
|
||||
## 9.4 Cycle Count Workflow
|
||||
|
||||
1. Manager creates count session — selects scope (full, category, or spot check)
|
||||
2. System snapshots expected quantities for all products in scope
|
||||
3. Staff count physical inventory — enter counts per product/unit
|
||||
4. System calculates variances and flags discrepancies
|
||||
5. Manager reviews variances — adds reason codes for each
|
||||
6. Manager approves adjustments — qty_on_hand updated, adjustment records created
|
||||
7. Session marked completed — immutable after completion
|
||||
|
||||
## 9.5 Business Rules
|
||||
|
||||
- Count sessions lock affected products from sale/receiving while in_progress — prevents count drift
|
||||
- Variances above a configurable threshold (default: 5% or $50 value) require manager approval
|
||||
- All adjustments are append-only audit records — never modified after creation
|
||||
- Serialized items: count confirms unit is present and in expected status
|
||||
- Completed sessions cannot be reopened — start a new session to re-count
|
||||
- Spot checks do not lock inventory — used for quick verification without disrupting sales
|
||||
|
||||
|
||||
|
||||
# 10. Purchase Orders
|
||||
|
||||
Formal purchase order workflow extends the existing stock_receipt flow. POs track what was ordered, what was received, and flag discrepancies.
|
||||
|
||||
## 10.1 purchase_order
|
||||
|
||||
Column | Type | Notes
|
||||
id | uuid PK |
|
||||
company_id | uuid FK | Tenant scoping
|
||||
location_id | uuid FK | Receiving location
|
||||
po_number | varchar | Human-readable PO number (auto-generated)
|
||||
supplier_id | uuid FK |
|
||||
status | enum | draft | submitted | partial | received | cancelled
|
||||
order_date | date | When PO was sent to supplier
|
||||
expected_date | date | Expected delivery date
|
||||
received_date | date | When fully received
|
||||
subtotal | numeric(10,2) | Sum of line totals
|
||||
shipping_cost | numeric(10,2) |
|
||||
tax | numeric(10,2) |
|
||||
total | numeric(10,2) |
|
||||
notes | text | Internal notes or special instructions
|
||||
created_by | uuid FK | Employee who created
|
||||
created_at | timestamptz |
|
||||
updated_at | timestamptz |
|
||||
|
||||
## 10.2 purchase_order_line
|
||||
|
||||
Column | Type | Notes
|
||||
id | uuid PK |
|
||||
purchase_order_id | uuid FK |
|
||||
product_id | uuid FK |
|
||||
supplier_sku | varchar | Supplier's SKU for this product
|
||||
description | varchar | Line item description
|
||||
qty_ordered | integer |
|
||||
qty_received | integer | Updated as items arrive — default 0
|
||||
unit_cost | numeric(10,2) | Agreed cost per unit
|
||||
line_total | numeric(10,2) | qty_ordered * unit_cost
|
||||
created_at | timestamptz |
|
||||
|
||||
## 10.3 Three-Way Match
|
||||
|
||||
When receiving against a PO, the system compares:
|
||||
1. **PO line** — what was ordered (qty_ordered, unit_cost)
|
||||
2. **Packing slip** — what supplier says they shipped (entered by staff at receiving)
|
||||
3. **Physical count** — what actually arrived (counted by staff)
|
||||
|
||||
Discrepancies flagged: short shipment, over shipment, wrong item, cost mismatch.
|
||||
|
||||
## 10.4 PO → Stock Receipt Flow
|
||||
|
||||
- Receiving against a PO auto-creates stock_receipt records for each line received
|
||||
- stock_receipt.purchase_order_id links receipt back to originating PO
|
||||
- Partial receives update PO status to "partial" — remaining lines stay open
|
||||
- Final receive updates PO status to "received"
|
||||
|
||||
## 10.5 Business Rules
|
||||
|
||||
- POs in draft status are editable — submitted POs are locked (create new revision if needed)
|
||||
- Supplier auto-populated from product's preferred supplier if set
|
||||
- Unit cost defaults to last stock_receipt cost for that product from that supplier
|
||||
- PO approval workflow optional — configurable threshold requiring manager sign-off
|
||||
- Cancelled POs retained for audit — soft cancel with reason code
|
||||
- Reorder report: products below qty_reorder_point generate suggested PO lines grouped by preferred supplier
|
||||
|
||||
|
||||
|
||||
# 11. Product Bundles & Kits
|
||||
|
||||
Bundles group multiple products into a single sellable item at a package price. Common in music retail: instrument + case + accessories starter packs.
|
||||
|
||||
## 11.1 product_bundle
|
||||
|
||||
Column | Type | Notes
|
||||
id | uuid PK |
|
||||
company_id | uuid FK | Tenant scoping
|
||||
product_id | uuid FK | The bundle's parent product record (is_bundle = true)
|
||||
created_at | timestamptz |
|
||||
|
||||
## 11.2 product_bundle_item
|
||||
|
||||
Column | Type | Notes
|
||||
id | uuid PK |
|
||||
bundle_id | uuid FK | References product_bundle
|
||||
component_product_id | uuid FK | The included product
|
||||
qty | integer | How many of this component per bundle (default 1)
|
||||
sort_order | integer | Display order in bundle breakdown
|
||||
created_at | timestamptz |
|
||||
|
||||
## 11.3 Schema Changes
|
||||
|
||||
**product** — add column:
|
||||
|
||||
Column | Type | Notes
|
||||
is_bundle | boolean | Default false. True = this product is a bundle of other products.
|
||||
|
||||
## 11.4 Pricing Models
|
||||
|
||||
Model | Description
|
||||
fixed | Bundle has a set price — component prices ignored at POS
|
||||
sum_discount | Bundle price = sum of component prices minus a bundle discount (percent or fixed)
|
||||
|
||||
Bundle price is stored on the parent product.price field. The pricing model determines how the discount is displayed on receipts (single line vs itemized with discount).
|
||||
|
||||
## 11.5 Inventory Behavior
|
||||
|
||||
- Bundle does not have its own qty_on_hand — availability derived from component stock
|
||||
- Bundle is "in stock" only if ALL components are in stock at required quantities
|
||||
- Selling a bundle decrements each component product's qty_on_hand (or marks serialized units as sold)
|
||||
- Stock receipt never targets a bundle directly — components are received individually
|
||||
- Low stock alert triggers if any component falls below its reorder point
|
||||
|
||||
## 11.6 Business Rules
|
||||
|
||||
- A bundle component cannot itself be a bundle (no nesting)
|
||||
- Bundles appear in POS search like any product — clearly labeled as bundle
|
||||
- Receipt shows bundle name and price, with component breakdown below
|
||||
- Returning a bundle returns all components — partial bundle returns handled as individual item returns
|
||||
- Bundle price must be less than or equal to sum of component prices (enforced at creation)
|
||||
|
||||
|
||||
|
||||
# 12. Barcode Label Printing
|
||||
|
||||
Bulk label printing for inventory receiving, repricing, and cycle count preparation.
|
||||
|
||||
## 12.1 Label Templates
|
||||
|
||||
Template | Use Case | Content
|
||||
standard_price | Shelf labels | SKU, name, price, barcode (UPC or SKU)
|
||||
serialized | Individual units | Serial number, SKU, name, barcode (serial)
|
||||
clearance | Sale items | SKU, name, original price (struck), sale price, barcode
|
||||
receiving | Incoming stock | SKU, name, price, received date, barcode
|
||||
bundle | Bundle items | Bundle name, bundle price, component list, barcode
|
||||
|
||||
## 12.2 Print Jobs
|
||||
|
||||
Label printing is triggered from:
|
||||
- **Stock receipt** — auto-prompt to print labels for received items
|
||||
- **Price change** — bulk print updated labels for repriced items
|
||||
- **Cycle count prep** — print labels for products missing barcodes
|
||||
- **Manual** — select products from inventory list, choose template, print
|
||||
|
||||
## 12.3 Technical
|
||||
|
||||
- Labels rendered server-side as PDF (ZPL support planned for thermal printers)
|
||||
- Standard label sizes: 1.25" x 0.875" (Dymo), 2" x 1" (thermal roll), 4" x 6" (shipping)
|
||||
- Barcode formats: Code 128 (SKU-based), UPC-A (manufacturer UPC), QR code (serial number)
|
||||
- Print queue supports batching — up to 500 labels per job
|
||||
|
||||
|
||||
|
||||
# 13. Backorders
|
||||
|
||||
Customer order queue for out-of-stock items. Tracks demand and notifies customers when stock arrives.
|
||||
|
||||
## 13.1 backorder
|
||||
|
||||
Column | Type | Notes
|
||||
id | uuid PK |
|
||||
company_id | uuid FK | Tenant scoping
|
||||
location_id | uuid FK |
|
||||
product_id | uuid FK | What was requested
|
||||
account_id | uuid FK | Who wants it
|
||||
member_id | uuid FK | Nullable — specific member on account
|
||||
qty | integer | Quantity requested
|
||||
status | enum | pending | ordered | received | fulfilled | cancelled
|
||||
purchase_order_line_id | uuid FK | Nullable — linked to PO when ordered from supplier
|
||||
deposit_transaction_id | uuid FK | Nullable — if deposit collected
|
||||
notes | text |
|
||||
requested_date | date | When customer placed backorder
|
||||
fulfilled_date | date | When customer received item
|
||||
created_by | uuid FK | Employee who took the order
|
||||
created_at | timestamptz |
|
||||
updated_at | timestamptz |
|
||||
|
||||
## 13.2 Workflow
|
||||
|
||||
1. Customer wants an out-of-stock product — staff creates backorder
|
||||
2. Optional: collect deposit via POS (deposit_transaction_id recorded)
|
||||
3. When creating next PO for that supplier, backorder quantities surfaced as suggested lines
|
||||
4. Stock receipt against PO triggers backorder match — status updated to "received"
|
||||
5. Staff notified to contact customer — notification sent via preferred channel
|
||||
6. Customer picks up item — backorder marked "fulfilled", deposit applied to sale
|
||||
|
||||
## 13.3 Business Rules
|
||||
|
||||
- Multiple backorders can exist for the same product — filled in request date order (FIFO)
|
||||
- Backorder quantities included in reorder report alongside reorder point calculations
|
||||
- Cancelled backorders refund any deposit collected
|
||||
- Backorder demand report: shows products with pending backorders — informs purchasing decisions
|
||||
@@ -232,4 +232,133 @@ When billing_group is null, the rental has its own independent Stripe subscripti
|
||||
|
||||
- Final invoice generated for any partial month charges
|
||||
|
||||
- If damage found, repair ticket created automatically and linked to rental record
|
||||
- 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
|
||||
@@ -432,4 +432,124 @@ Margin on flat-rate services — e.g. are rehair rates covering costs
|
||||
|
||||
- Repair complete status triggers customer notification via preferred channel
|
||||
|
||||
- Delivered status set by delivery domain completion — not manually
|
||||
- Delivered status set by delivery domain completion — not manually
|
||||
|
||||
|
||||
|
||||
# 7. Warranty Tracking
|
||||
|
||||
Warranties are tracked per inventory unit — both manufacturer warranties and store-offered extended warranties. Warranty status is surfaced during repair intake to determine billing responsibility.
|
||||
|
||||
## 7.1 warranty
|
||||
|
||||
Column | Type | Notes
|
||||
id | uuid PK |
|
||||
company_id | uuid FK | Tenant scoping
|
||||
inventory_unit_id | uuid FK | The specific instrument covered
|
||||
account_id | uuid FK | Account that owns the warranty
|
||||
warranty_type | enum | manufacturer | extended | store
|
||||
provider | varchar | Warranty provider name (e.g. "Yamaha", "Store Protection Plan")
|
||||
coverage_description | text | What's covered — free text or template
|
||||
start_date | date | When coverage begins
|
||||
end_date | date | When coverage expires
|
||||
purchase_price | numeric(10,2) | Nullable — price paid for extended warranty (0 for manufacturer)
|
||||
transaction_id | uuid FK | Nullable — sale transaction where warranty was purchased
|
||||
status | enum | active | expired | claimed | voided
|
||||
max_claims | integer | Nullable — max number of claims allowed (null = unlimited)
|
||||
claims_used | integer | Default 0
|
||||
notes | text |
|
||||
created_at | timestamptz |
|
||||
updated_at | timestamptz |
|
||||
|
||||
## 7.2 warranty_claim
|
||||
|
||||
Column | Type | Notes
|
||||
id | uuid PK |
|
||||
warranty_id | uuid FK |
|
||||
repair_ticket_id | uuid FK | The repair covered by this claim
|
||||
claim_date | date |
|
||||
issue_description | text | What went wrong
|
||||
resolution | text | How it was resolved
|
||||
claim_amount | numeric(10,2) | Cost covered by warranty
|
||||
status | enum | submitted | approved | denied | completed
|
||||
denied_reason | text | Nullable — why claim was denied
|
||||
processed_by | uuid FK | Employee who processed
|
||||
created_at | timestamptz |
|
||||
|
||||
## 7.3 Warranty Flow
|
||||
|
||||
1. **At sale**: staff optionally adds extended warranty to transaction — warranty record created linked to inventory_unit
|
||||
2. **Manufacturer warranty**: auto-created when new serialized item is sold, using manufacturer's standard warranty period
|
||||
3. **At repair intake**: system checks if instrument has active warranty — surfaces to staff
|
||||
4. **If under warranty**: repair ticket linked to warranty claim — customer not billed (or reduced billing)
|
||||
5. **Claim processing**: store submits claim to manufacturer for reimbursement if applicable
|
||||
6. **Expiry**: system tracks end_date — warranty status auto-updated to "expired"
|
||||
|
||||
## 7.4 Business Rules
|
||||
|
||||
- Warranty checked automatically during repair intake — staff sees "UNDER WARRANTY" or "WARRANTY EXPIRED"
|
||||
- Extended warranties are sold as a line item on the original sale transaction
|
||||
- Manufacturer warranty periods are configurable per brand (e.g. Yamaha = 5 years, generic = 1 year)
|
||||
- Warranty claims reduce repair ticket billing — covered amount shown on invoice as "warranty credit"
|
||||
- Voided warranties (e.g. customer damage outside coverage) require reason code and manager approval
|
||||
- Warranty report: active warranties by expiry date, claim history, claim approval rate
|
||||
|
||||
|
||||
|
||||
# 8. Maintenance Schedules
|
||||
|
||||
Preventive maintenance recommendations and reminders per instrument. Helps stores build recurring service revenue and keeps customer instruments in good condition.
|
||||
|
||||
## 8.1 maintenance_schedule_template
|
||||
|
||||
Company-configurable templates define recommended maintenance intervals by instrument type.
|
||||
|
||||
Column | Type | Notes
|
||||
id | uuid PK |
|
||||
company_id | uuid FK | Tenant scoping
|
||||
instrument_type | varchar | e.g. "violin", "trumpet", "clarinet", "flute"
|
||||
service_name | varchar | e.g. "Bow Rehair", "Valve Oil & Cleaning", "Pad Replacement Check"
|
||||
interval_months | integer | Recommended interval between services
|
||||
description | text | Customer-facing description of what the service includes
|
||||
estimated_cost | numeric(10,2) | Typical cost — for customer expectation setting
|
||||
sort_order | integer | Display order
|
||||
is_active | boolean |
|
||||
created_at | timestamptz |
|
||||
|
||||
## 8.2 maintenance_reminder
|
||||
|
||||
Tracks scheduled reminders for specific instruments owned by customers.
|
||||
|
||||
Column | Type | Notes
|
||||
id | uuid PK |
|
||||
company_id | uuid FK |
|
||||
account_id | uuid FK |
|
||||
member_id | uuid FK | Nullable — specific member
|
||||
inventory_unit_id | uuid FK | Nullable — specific instrument if tracked
|
||||
template_id | uuid FK | Which maintenance template this follows
|
||||
instrument_description | varchar | Fallback description if no inventory_unit linked
|
||||
last_service_date | date | When this service was last performed
|
||||
next_due_date | date | Computed: last_service_date + interval_months
|
||||
status | enum | upcoming | due | overdue | completed | dismissed
|
||||
notification_sent | boolean | Whether customer has been notified
|
||||
repair_ticket_id | uuid FK | Nullable — linked to repair ticket when service is scheduled
|
||||
created_at | timestamptz |
|
||||
updated_at | timestamptz |
|
||||
|
||||
## 8.3 Maintenance Workflow
|
||||
|
||||
1. When a repair is completed, system suggests creating maintenance reminders based on instrument type
|
||||
2. Reminders auto-calculated: next_due_date = completed_date + template.interval_months
|
||||
3. Daily job checks for due/overdue reminders — generates notification queue
|
||||
4. Customer notified via preferred channel (email, SMS, portal notification)
|
||||
5. When customer brings instrument in, staff sees pending maintenance recommendations
|
||||
6. Completed service updates the reminder: last_service_date = today, next recalculated
|
||||
|
||||
## 8.4 Business Rules
|
||||
|
||||
- Maintenance reminders are suggestions, not obligations — customer can dismiss
|
||||
- Templates are seeded with common defaults, fully customizable per store
|
||||
- Reminders auto-created for rental fleet instruments — store maintains own fleet on schedule
|
||||
- Overdue reminders escalate: due → 30 days overdue (second notice) → 90 days (final notice, then dismissed)
|
||||
- Maintenance history visible on customer account and instrument record
|
||||
- Revenue report: maintenance service revenue, conversion rate from reminder to repair ticket
|
||||
@@ -228,4 +228,255 @@ Account charge
|
||||
|
||||
Internal
|
||||
|
||||
Charges to account balance — billed later
|
||||
Charges to account balance — billed later
|
||||
|
||||
|
||||
|
||||
# 6. Trade-In Workflow
|
||||
|
||||
Trade-ins are a core music retail workflow. A customer brings in a used instrument and receives credit toward a purchase. The trade-in creates both an inventory intake (new used product) and a credit applied to a transaction.
|
||||
|
||||
## 6.1 trade_in
|
||||
|
||||
Column | Type | Notes
|
||||
id | uuid PK |
|
||||
company_id | uuid FK | Tenant scoping
|
||||
location_id | uuid FK |
|
||||
account_id | uuid FK | Nullable — walk-in trade-ins allowed
|
||||
appraised_by | uuid FK | Employee who appraised
|
||||
instrument_description | varchar | What the customer is trading in
|
||||
serial_number | varchar | Nullable
|
||||
condition | enum | excellent | good | fair | poor
|
||||
appraisal_value | numeric(10,2) | What store offers the customer
|
||||
credit_applied | numeric(10,2) | Amount applied to transaction (may equal appraisal or less if partial)
|
||||
transaction_id | uuid FK | Nullable — the sale transaction where credit was applied
|
||||
product_id | uuid FK | Nullable — the product record created for the traded-in item
|
||||
inventory_unit_id | uuid FK | Nullable — the inventory unit created (if serialized)
|
||||
status | enum | appraised | accepted | declined | sold
|
||||
notes | text | Condition details, customer negotiation notes
|
||||
appraised_at | timestamptz |
|
||||
created_at | timestamptz |
|
||||
|
||||
## 6.2 Trade-In Flow
|
||||
|
||||
1. Customer brings instrument — staff appraises condition and fair market value
|
||||
2. Staff enters trade-in: description, serial, condition, appraisal value
|
||||
3. Customer accepts or declines the offer
|
||||
4. If accepted:
|
||||
a. New product/inventory_unit created for the traded-in instrument (used, condition noted)
|
||||
b. Trade-in credit applied to the current or future transaction
|
||||
c. Transaction shows trade-in credit as a line item (negative amount)
|
||||
5. Traded-in instrument enters inventory as available for resale
|
||||
|
||||
## 6.3 Business Rules
|
||||
|
||||
- Appraisal value is a negotiation — staff can adjust within min/max guidelines
|
||||
- Trade-in credit can exceed purchase price — difference paid as store credit (not cash refund)
|
||||
- Trade-in without immediate purchase: credit stored on account as store credit balance
|
||||
- Traded-in items default to "used" condition and get a new product record (not merged with new inventory)
|
||||
- Trade-in appraisal records are immutable — if value changes, create new appraisal
|
||||
- Trade-in value reported separately from sales discount for accounting purposes
|
||||
- Manager approval required for trade-in value above configurable threshold
|
||||
|
||||
|
||||
|
||||
# 7. Returns & Exchanges
|
||||
|
||||
Structured returns workflow beyond simple refund transactions. Handles RMA tracking, reason codes, restocking, and return window enforcement.
|
||||
|
||||
## 7.1 return_request
|
||||
|
||||
Column | Type | Notes
|
||||
id | uuid PK |
|
||||
company_id | uuid FK | Tenant scoping
|
||||
location_id | uuid FK |
|
||||
return_number | varchar | Human-readable RMA number (auto-generated)
|
||||
original_transaction_id | uuid FK | The sale being returned against
|
||||
account_id | uuid FK | Nullable — anonymous returns allowed with receipt
|
||||
status | enum | pending | approved | completed | denied
|
||||
return_type | enum | refund | exchange | store_credit
|
||||
initiated_by | uuid FK | Employee processing return
|
||||
approved_by | uuid FK | Nullable — manager if approval required
|
||||
reason_code | enum | defective | wrong_item | changed_mind | duplicate | other
|
||||
reason_detail | text | Free-text explanation
|
||||
restocking_fee | numeric(10,2) | Default 0 — configurable per category
|
||||
refund_amount | numeric(10,2) | Amount returned to customer (after restocking fee)
|
||||
refund_method | enum | original_payment | cash | store_credit
|
||||
refund_transaction_id | uuid FK | The refund transaction created
|
||||
created_at | timestamptz |
|
||||
updated_at | timestamptz |
|
||||
|
||||
## 7.2 return_line_item
|
||||
|
||||
Column | Type | Notes
|
||||
id | uuid PK |
|
||||
return_request_id | uuid FK |
|
||||
original_line_item_id | uuid FK | Line item from original transaction
|
||||
product_id | uuid FK |
|
||||
inventory_unit_id | uuid FK | Nullable — for serialized items
|
||||
qty_returned | integer |
|
||||
unit_price | numeric(10,2) | Price at time of original sale
|
||||
condition_returned | enum | new | excellent | good | fair | poor | defective
|
||||
restock | boolean | Whether item goes back to available inventory
|
||||
created_at | timestamptz |
|
||||
|
||||
## 7.3 Return Policies
|
||||
|
||||
return_policy — configurable per company, overridable per category:
|
||||
|
||||
Column | Type | Notes
|
||||
id | uuid PK |
|
||||
company_id | uuid FK |
|
||||
category_id | uuid FK | Nullable — null = company-wide default
|
||||
return_window_days | integer | Days after purchase returns are accepted (e.g. 30)
|
||||
restocking_fee_percent | numeric(5,2) | Default restocking fee (e.g. 15%)
|
||||
requires_receipt | boolean | Whether original receipt/transaction required
|
||||
requires_approval | boolean | Whether manager approval needed
|
||||
exchange_only | boolean | Some categories only allow exchange, not refund
|
||||
is_active | boolean |
|
||||
created_at | timestamptz |
|
||||
|
||||
## 7.4 Return Workflow
|
||||
|
||||
1. Customer requests return — staff looks up original transaction
|
||||
2. System checks return window — warns if outside policy (manager can override)
|
||||
3. Staff selects items being returned, enters reason code and condition
|
||||
4. System calculates refund amount (original price minus restocking fee if applicable)
|
||||
5. Manager approves if required by policy or amount threshold
|
||||
6. Refund processed: original payment method, cash, or store credit
|
||||
7. Returned items: restocked (condition updated) or marked defective (removed from available)
|
||||
|
||||
## 7.5 Exchange Flow
|
||||
|
||||
1. Return initiated with return_type = exchange
|
||||
2. Customer selects replacement item(s)
|
||||
3. System calculates difference — customer pays overage or receives credit for underage
|
||||
4. Single transaction captures both the return credit and the new sale
|
||||
5. Both the return and new sale are linked for reporting
|
||||
|
||||
## 7.6 Business Rules
|
||||
|
||||
- Return window enforced at POS — staff sees clear "within policy" / "outside policy" indicator
|
||||
- Outside-policy returns require manager override with reason
|
||||
- Defective items never restocked — routed to repair assessment or disposal
|
||||
- Serialized items: inventory unit status reverted from "sold" to "available" on restock
|
||||
- Non-serialized items: qty_on_hand incremented on restock
|
||||
- Refund to original payment method preferred — Stripe refund API called for card transactions
|
||||
- Store credit refunds create a credit balance on the account
|
||||
- No cash refunds above configurable threshold without manager approval
|
||||
- Return fraud tracking: system flags accounts with high return frequency
|
||||
|
||||
|
||||
|
||||
# 8. Tax Exemptions at POS
|
||||
|
||||
Tax-exempt customers (schools, churches, resellers) are common in music retail. Tax exemption is checked at transaction time and the exemption is recorded on each qualifying transaction.
|
||||
|
||||
## 8.1 Transaction Tax Flow
|
||||
|
||||
1. Transaction started — account attached (or anonymous)
|
||||
2. System checks account.tax_exempt_status:
|
||||
- If "approved": tax automatically set to $0.00 on all eligible lines
|
||||
- If "pending" or "none": standard tax calculation applies
|
||||
3. Tax exemption displayed on POS screen — staff sees "TAX EXEMPT" indicator
|
||||
4. Receipt and invoice show $0.00 tax with exemption reference number
|
||||
5. Tax-exempt transactions flagged in reporting for audit compliance
|
||||
|
||||
## 8.2 Manual Override
|
||||
|
||||
- Staff can manually mark a transaction as tax-exempt (e.g. walk-in school purchaser with certificate)
|
||||
- Manual exemptions require: certificate number entry and manager approval
|
||||
- Manual exemptions logged in tax_exemption_audit for compliance
|
||||
|
||||
## 8.3 Business Rules
|
||||
|
||||
- Tax exemption applies to the entire transaction, not individual line items
|
||||
- Resale certificates have expiration dates — system warns when nearing expiry
|
||||
- Expired certificates block auto-exemption until renewed — staff can override with manager approval
|
||||
- Tax-exempt transaction count and value reported monthly for state compliance
|
||||
- Rental transactions for schools are tax-exempt if the school account is exempt
|
||||
|
||||
|
||||
|
||||
# 9. In-Home Trials
|
||||
|
||||
In-home trials let a customer take an instrument home to try before committing to a purchase. Common for higher-value instruments (guitars, violins, horns) where feel, sound, and fit matter. The instrument leaves the store, so precise inventory tracking is critical.
|
||||
|
||||
## 9.1 instrument_trial
|
||||
|
||||
Column | Type | Notes
|
||||
id | uuid PK |
|
||||
company_id | uuid FK | Tenant scoping
|
||||
location_id | uuid FK | Originating location
|
||||
account_id | uuid FK | Customer taking the trial
|
||||
member_id | uuid FK | Nullable — specific member
|
||||
inventory_unit_id | uuid FK | The specific instrument being trialed
|
||||
trial_number | varchar | Human-readable ID (auto-generated)
|
||||
status | enum | active | returned | converted | overdue | cancelled
|
||||
checkout_date | date | When instrument left the store
|
||||
due_date | date | When instrument must be returned
|
||||
returned_date | date | Nullable — when actually returned
|
||||
duration_days | integer | Trial length (default configurable, e.g. 3-7 days)
|
||||
deposit_amount | numeric(10,2) | Nullable — optional hold or deposit collected
|
||||
deposit_transaction_id | uuid FK | Nullable — POS transaction for deposit
|
||||
condition_out | enum | new | excellent | good | fair | poor — condition at checkout
|
||||
condition_in | enum | Nullable — condition at return
|
||||
checked_out_by | uuid FK | Employee who processed checkout
|
||||
checked_in_by | uuid FK | Nullable — employee who processed return
|
||||
sale_transaction_id | uuid FK | Nullable — if trial converted to sale
|
||||
notes | text |
|
||||
created_at | timestamptz |
|
||||
updated_at | timestamptz |
|
||||
|
||||
## 9.2 Inventory Unit Status
|
||||
|
||||
**inventory_unit.status** — add value:
|
||||
|
||||
Value | Notes
|
||||
on_trial | Instrument is out with a customer on an in-home trial. Not available for sale, rental, or display.
|
||||
|
||||
This status ensures the instrument is accounted for in all inventory views. Staff can see exactly which instruments are out on trial, with whom, and when they're due back.
|
||||
|
||||
## 9.3 Trial Workflow
|
||||
|
||||
1. Customer interested in an instrument — staff creates trial, selects inventory unit
|
||||
2. Condition documented at checkout (condition_out)
|
||||
3. Optional: deposit collected via POS (hold against card or cash deposit)
|
||||
4. inventory_unit.status set to "on_trial"
|
||||
5. Customer takes instrument home
|
||||
6. Daily job checks for overdue trials — flags on dashboard, notifies staff
|
||||
7. Customer returns instrument:
|
||||
a. Condition assessed at return (condition_in)
|
||||
b. inventory_unit.status reverted to "available" (or "in_repair" if damaged)
|
||||
c. Deposit refunded if collected
|
||||
8. **Conversion**: customer decides to buy — trial marked "converted", sale transaction created, deposit applied as payment
|
||||
|
||||
## 9.4 Multiple Trials
|
||||
|
||||
- A customer may trial multiple instruments simultaneously (e.g. comparing two violins)
|
||||
- Each instrument gets its own trial record
|
||||
- Staff can see all active trials per customer on the account view
|
||||
- Common scenario: customer takes two, returns one, buys the other
|
||||
|
||||
## 9.5 Overdue Handling
|
||||
|
||||
- Trial due_date is firm — system escalates overdue trials:
|
||||
- Day of due date: reminder notification sent to customer
|
||||
- 1 day overdue: staff alerted on dashboard
|
||||
- 3 days overdue: manager notified, customer contacted directly
|
||||
- 7+ days overdue: escalation to store owner, deposit may be charged
|
||||
- Overdue trials appear prominently on the daily operations dashboard
|
||||
- If deposit was collected, store can charge the deposit after configurable overdue period
|
||||
|
||||
## 9.6 Business Rules
|
||||
|
||||
- Trial requires an account — no anonymous trials (need contact info for follow-up and liability)
|
||||
- Only serialized inventory units can go on trial (need serial number tracking)
|
||||
- Instrument must be in "available" status to start a trial
|
||||
- Maximum concurrent trials per account configurable (default: 3)
|
||||
- Maximum trial duration configurable per company (default: 7 days, max: 30 days)
|
||||
- Condition documented at checkout and return — discrepancies flagged for review
|
||||
- Deposit optional but recommended for high-value instruments — threshold configurable
|
||||
- Trial history visible on both the account record and the inventory unit record
|
||||
- Conversion rate report: trials started vs converted to sale, by instrument category and employee
|
||||
- Lost instrument on trial: if not returned and unresolved, inventory_unit.status set to "lost", deposit charged, account flagged
|
||||
@@ -198,4 +198,183 @@ The billing service checks `store.payment_processor` to determine the flow:
|
||||
|
||||
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.
|
||||
All incoming webhook events are stored before processing. This enables replay if processing fails.
|
||||
|
||||
|
||||
|
||||
# 8. Gift Cards & Store Credits (MOD-GIFTCARD)
|
||||
|
||||
Gift cards and store credits are a premium module. Gift cards are purchasable/redeemable stored-value instruments. Store credits are system-issued balances (from returns, trade-ins, or adjustments).
|
||||
|
||||
## 8.1 gift_card
|
||||
|
||||
Column | Type | Notes
|
||||
id | uuid PK |
|
||||
company_id | uuid FK | Tenant scoping
|
||||
card_number | varchar | Unique card identifier (printed on physical card or emailed)
|
||||
pin | varchar | Nullable — optional PIN for security (hashed)
|
||||
type | enum | physical | digital
|
||||
initial_balance | numeric(10,2) | Original loaded amount
|
||||
current_balance | numeric(10,2) | Remaining balance
|
||||
status | enum | active | redeemed | expired | disabled
|
||||
purchased_by_account_id | uuid FK | Nullable — who bought it
|
||||
recipient_email | varchar | Nullable — for digital cards
|
||||
recipient_name | varchar | Nullable
|
||||
purchase_transaction_id | uuid FK | The sale transaction where card was purchased
|
||||
expiry_date | date | Nullable — some jurisdictions prohibit expiry
|
||||
is_reloadable | boolean | Default false
|
||||
created_at | timestamptz |
|
||||
updated_at | timestamptz |
|
||||
|
||||
## 8.2 gift_card_transaction
|
||||
|
||||
Append-only ledger of all balance changes on a gift card.
|
||||
|
||||
Column | Type | Notes
|
||||
id | uuid PK |
|
||||
gift_card_id | uuid FK |
|
||||
transaction_type | enum | purchase | reload | redemption | refund | adjustment | expiry
|
||||
amount | numeric(10,2) | Positive for loads, negative for redemptions
|
||||
balance_after | numeric(10,2) | Running balance after this transaction
|
||||
related_transaction_id | uuid FK | Nullable — the POS transaction where redeemed/purchased
|
||||
performed_by | uuid FK | Employee
|
||||
notes | text |
|
||||
created_at | timestamptz |
|
||||
|
||||
## 8.3 store_credit
|
||||
|
||||
Store credits are account-level balances issued by the system, not purchasable products.
|
||||
|
||||
Column | Type | Notes
|
||||
id | uuid PK |
|
||||
company_id | uuid FK |
|
||||
account_id | uuid FK | Account that holds the credit
|
||||
reason | enum | return | trade_in | adjustment | promotion | gift_card_conversion
|
||||
original_amount | numeric(10,2) |
|
||||
remaining_balance | numeric(10,2) |
|
||||
issued_by | uuid FK | Employee who issued
|
||||
related_return_id | uuid FK | Nullable — if from a return
|
||||
related_trade_in_id | uuid FK | Nullable — if from a trade-in
|
||||
expires_at | timestamptz | Nullable — configurable expiry
|
||||
status | enum | active | depleted | expired | voided
|
||||
created_at | timestamptz |
|
||||
updated_at | timestamptz |
|
||||
|
||||
## 8.4 store_credit_transaction
|
||||
|
||||
Append-only ledger for store credit balance changes.
|
||||
|
||||
Column | Type | Notes
|
||||
id | uuid PK |
|
||||
store_credit_id | uuid FK |
|
||||
transaction_type | enum | issued | applied | adjustment | expired | voided
|
||||
amount | numeric(10,2) |
|
||||
balance_after | numeric(10,2) |
|
||||
related_transaction_id | uuid FK | Nullable
|
||||
performed_by | uuid FK |
|
||||
created_at | timestamptz |
|
||||
|
||||
## 8.5 POS Integration
|
||||
|
||||
- Gift card sold as a product at POS — triggers gift_card creation with initial balance
|
||||
- Gift card redemption is a payment method: customer presents card, balance checked, amount deducted
|
||||
- Partial redemption supported — remaining balance stays on card
|
||||
- Split tender: gift card covers part, remaining on card/cash
|
||||
- Store credit auto-applied: when account has credit balance, POS prompts "Apply $X.XX store credit?"
|
||||
- Both gift card and store credit balances visible on account summary
|
||||
|
||||
## 8.6 Business Rules
|
||||
|
||||
- Gift card numbers generated with check digit to prevent typos
|
||||
- Physical cards activated at POS — not active until purchased (prevents theft of unactivated cards)
|
||||
- Digital cards emailed immediately with card number and optional message
|
||||
- Gift card balance inquiries available at POS and customer portal
|
||||
- Expiry rules vary by jurisdiction — configurable per company, default: no expiry
|
||||
- Store credits cannot be cashed out — applied to purchases only
|
||||
- All balance changes are append-only ledger entries — no direct balance edits
|
||||
- Gift card liability tracked for accounting: total outstanding balances reported as liability
|
||||
- Reloadable gift cards allow additional value to be added after purchase
|
||||
|
||||
|
||||
|
||||
# 9. Layaway & Payment Plans (MOD-LAYAWAY)
|
||||
|
||||
Layaway allows a customer to reserve an item with a deposit and pay it off over time. The item is held (not available for sale) until fully paid.
|
||||
|
||||
## 9.1 layaway
|
||||
|
||||
Column | Type | Notes
|
||||
id | uuid PK |
|
||||
company_id | uuid FK | Tenant scoping
|
||||
location_id | uuid FK |
|
||||
layaway_number | varchar | Human-readable ID (auto-generated)
|
||||
account_id | uuid FK |
|
||||
status | enum | active | completed | defaulted | cancelled
|
||||
total_price | numeric(10,2) | Full price of items on layaway
|
||||
deposit_amount | numeric(10,2) | Initial deposit collected
|
||||
amount_paid | numeric(10,2) | Total paid to date (including deposit)
|
||||
balance_remaining | numeric(10,2) | total_price - amount_paid
|
||||
payment_frequency | enum | weekly | biweekly | monthly
|
||||
next_payment_date | date | Next scheduled payment
|
||||
payment_amount | numeric(10,2) | Scheduled payment amount per period
|
||||
max_duration_days | integer | Maximum days to complete layaway (e.g. 90)
|
||||
expires_at | date | Deposit date + max_duration_days
|
||||
cancellation_fee | numeric(10,2) | Fee charged if customer cancels (configurable)
|
||||
notes | text |
|
||||
created_by | uuid FK |
|
||||
created_at | timestamptz |
|
||||
updated_at | timestamptz |
|
||||
|
||||
## 9.2 layaway_item
|
||||
|
||||
Column | Type | Notes
|
||||
id | uuid PK |
|
||||
layaway_id | uuid FK |
|
||||
product_id | uuid FK |
|
||||
inventory_unit_id | uuid FK | Nullable — for serialized items, unit is reserved
|
||||
description | varchar |
|
||||
qty | integer |
|
||||
unit_price | numeric(10,2) |
|
||||
line_total | numeric(10,2) |
|
||||
created_at | timestamptz |
|
||||
|
||||
## 9.3 layaway_payment
|
||||
|
||||
Column | Type | Notes
|
||||
id | uuid PK |
|
||||
layaway_id | uuid FK |
|
||||
transaction_id | uuid FK | The POS transaction for this payment
|
||||
amount | numeric(10,2) |
|
||||
balance_after | numeric(10,2) | Remaining balance after payment
|
||||
payment_number | integer | 1, 2, 3... sequential
|
||||
is_deposit | boolean | True for initial deposit
|
||||
paid_at | timestamptz |
|
||||
created_at | timestamptz |
|
||||
|
||||
## 9.4 Layaway Workflow
|
||||
|
||||
1. Customer selects items — staff creates layaway with deposit (minimum deposit configurable, e.g. 20%)
|
||||
2. Serialized items: inventory_unit.status set to "layaway" (held, not available for sale)
|
||||
3. Non-serialized items: qty reserved (decremented from available)
|
||||
4. Customer makes scheduled payments at POS — each payment recorded as layaway_payment
|
||||
5. When balance reaches $0: layaway completed, items released to customer, standard sale transaction created
|
||||
6. Missed payments: system sends reminders at configurable intervals
|
||||
7. Default: after configurable missed payments or past expiry — manager reviews for cancellation
|
||||
|
||||
## 9.5 Cancellation
|
||||
|
||||
- Customer-initiated cancellation: deposit refunded minus cancellation fee, items returned to available inventory
|
||||
- Default cancellation (non-payment): same as above, but store may retain more of deposit per policy
|
||||
- Cancellation fee configurable per company — can be fixed amount or percentage of amount paid
|
||||
- All payments refunded via original payment method or store credit (store's choice per policy)
|
||||
|
||||
## 9.6 Business Rules
|
||||
|
||||
- Minimum deposit enforced (configurable — default 20% of total)
|
||||
- Items on layaway are held — not available for sale to other customers
|
||||
- Price locked at layaway creation — price changes don't affect existing layaways
|
||||
- Payment reminders sent before due date (configurable: 3 days before default)
|
||||
- Overdue payments flagged on dashboard — staff follows up
|
||||
- Layaway report: active layaways, total held value, upcoming payments, overdue accounts
|
||||
- Maximum layaway duration configurable per company (default 90 days)
|
||||
- Layaway items can be exchanged (size swap) with manager approval — price difference adjusted
|
||||
@@ -174,6 +174,18 @@ API access — REST API for third-party integrations, webhooks, data export. Rat
|
||||
|
||||
Technical integrations
|
||||
|
||||
MOD-GIFTCARD
|
||||
|
||||
Gift cards and store credits — physical and digital gift cards, store credit balances, redemption at POS, balance tracking.
|
||||
|
||||
Stores wanting gift card revenue
|
||||
|
||||
MOD-LAYAWAY
|
||||
|
||||
Layaway and payment plans — item reservation with deposit, scheduled payments, hold management.
|
||||
|
||||
Stores with high-value instruments
|
||||
|
||||
|
||||
|
||||
## 2.3 Payment Processor Modules
|
||||
@@ -240,6 +252,14 @@ MOD-API
|
||||
|
||||
None — can be added to any configuration
|
||||
|
||||
MOD-GIFTCARD
|
||||
|
||||
None — can be added to any configuration
|
||||
|
||||
MOD-LAYAWAY
|
||||
|
||||
None — can be added to any configuration
|
||||
|
||||
|
||||
|
||||
# 3. Self-Hosted — Perpetual Licensing
|
||||
@@ -356,6 +376,22 @@ $80
|
||||
|
||||
REST API access
|
||||
|
||||
MOD-GIFTCARD
|
||||
|
||||
$300
|
||||
|
||||
$60
|
||||
|
||||
Gift cards & store credits
|
||||
|
||||
MOD-LAYAWAY
|
||||
|
||||
$300
|
||||
|
||||
$60
|
||||
|
||||
Layaway & payment plans
|
||||
|
||||
PAY-STRIPE
|
||||
|
||||
$300
|
||||
|
||||
@@ -108,26 +108,26 @@ Consignment | 9/10 | Commission tracking, settlement workflow, accounting
|
||||
Sales commission | 8/10 | Rate hierarchy, per-employee, category overrides
|
||||
Personnel | 8/10 | Time clock, scheduling, time off, payroll export
|
||||
|
||||
## 4.2 Missing — High Priority (Add Before Launch)
|
||||
## 4.2 Previously Missing — Now Planned (added to domain docs)
|
||||
|
||||
Feature | Impact | Notes
|
||||
Gift cards / store credits | High | Common customer request. Needs gift_card table, balance tracking, POS redemption.
|
||||
Trade-in workflow | High | Huge in music retail. Customer brings used instrument, gets credit toward purchase. Needs appraisal + credit + inventory intake flow.
|
||||
Tax exemptions | High | Schools, churches, resellers are core customers. Need tax_exempt flag on account + resale certificate tracking.
|
||||
Inventory cycle counts | High | Physical inventory reconciliation. Need count sessions, variance tracking, adjustment audit trail.
|
||||
Returns/exchanges workflow | High | Structured beyond just refund transactions. Need RMA, reason codes, return window policies, restocking.
|
||||
Purchase orders | Medium | PO generation, receiving against PO, three-way match. stock_receipt is a start but needs formal PO workflow.
|
||||
Feature | Planning Doc | Module
|
||||
Trade-in workflow | 07_Domain_Sales_POS.md §6 | Core
|
||||
Tax exemptions | 02_Domain_Accounts_Customers.md + 07_Domain_Sales_POS.md §8 | Core
|
||||
Inventory cycle counts | 03_Domain_Inventory.md §9 | Core
|
||||
Returns/exchanges workflow | 07_Domain_Sales_POS.md §7 | Core
|
||||
Purchase orders | 03_Domain_Inventory.md §10 | Core
|
||||
Product bundles / kits | 03_Domain_Inventory.md §11 | Core
|
||||
Barcode label printing | 03_Domain_Inventory.md §12 | Core
|
||||
Backorders | 03_Domain_Inventory.md §13 | Core
|
||||
In-home trials | 07_Domain_Sales_POS.md §9 | Core
|
||||
Instrument sizing | 03_Domain_Inventory.md §8 | Core
|
||||
Rental agreement contracts | 04_Domain_Rentals.md §7 | MOD-RENTALS
|
||||
Warranty tracking | 06_Domain_Repairs.md §7 | MOD-REPAIRS
|
||||
Maintenance schedules | 06_Domain_Repairs.md §8 | MOD-REPAIRS
|
||||
Gift cards / store credits | 08_Domain_Payments_Billing.md §8 | MOD-GIFTCARD (premium)
|
||||
Layaway / payment plans | 08_Domain_Payments_Billing.md §9 | MOD-LAYAWAY (premium)
|
||||
|
||||
## 4.3 Missing — Medium Priority
|
||||
|
||||
Feature | Notes
|
||||
Layaway / payment plans | Hold item with partial payment, release on full payment
|
||||
Product bundles / kits | Instrument + case + accessories at bundle price
|
||||
Barcode label printing | Bulk label printing for inventory
|
||||
Backorders | Customer order queue for out-of-stock items
|
||||
Warranty tracking | Warranty period, claim tracking, extended warranty options
|
||||
|
||||
## 4.4 Missing — Lower Priority / Future
|
||||
## 4.3 Still Missing — Lower Priority / Future
|
||||
|
||||
Feature | Notes
|
||||
Customer loyalty / rewards | Point accumulation and redemption
|
||||
@@ -135,9 +135,7 @@ E-commerce / online store | Product catalog, cart, online payments
|
||||
Shipping integration | Carrier APIs, label printing, tracking
|
||||
Multi-currency | Exchange rates, currency-specific pricing
|
||||
|
||||
## 4.5 Music-Specific Gaps
|
||||
## 4.4 Music-Specific Gaps — Remaining
|
||||
|
||||
Feature | Notes
|
||||
Instrument sizing | 1/4, 1/2, 3/4, full — critical for string instrument rentals. Need size field on product/inventory_unit.
|
||||
Rental insurance certificates | Certificate generation for rental instruments. Common parent request.
|
||||
Instrument maintenance schedules | Preventive maintenance recommendations and reminders per instrument.
|
||||
|
||||
Reference in New Issue
Block a user