From e7853f59f2bb72bff82ef5a60715adcb467aca0f Mon Sep 17 00:00:00 2001 From: Ryan Moon Date: Fri, 27 Mar 2026 20:53:01 -0500 Subject: [PATCH] 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 --- planning/02_Domain_Accounts_Customers.md | 46 ++++ planning/03_Domain_Inventory.md | 325 ++++++++++++++++++++++- planning/04_Domain_Rentals.md | 131 ++++++++- planning/06_Domain_Repairs.md | 122 ++++++++- planning/07_Domain_Sales_POS.md | 253 +++++++++++++++++- planning/08_Domain_Payments_Billing.md | 181 ++++++++++++- planning/15_Licensing_Modules_Pricing.md | 36 +++ planning/22_Audit_Phase2_Review.md | 40 ++- 8 files changed, 1107 insertions(+), 27 deletions(-) diff --git a/planning/02_Domain_Accounts_Customers.md b/planning/02_Domain_Accounts_Customers.md index 7f9396e..8d4064b 100644 --- a/planning/02_Domain_Accounts_Customers.md +++ b/planning/02_Domain_Accounts_Customers.md @@ -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 diff --git a/planning/03_Domain_Inventory.md b/planning/03_Domain_Inventory.md index 3c4a7e7..7fbc9bf 100644 --- a/planning/03_Domain_Inventory.md +++ b/planning/03_Domain_Inventory.md @@ -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 \ No newline at end of file +- 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 \ No newline at end of file diff --git a/planning/04_Domain_Rentals.md b/planning/04_Domain_Rentals.md index 2026255..4bb7c8c 100644 --- a/planning/04_Domain_Rentals.md +++ b/planning/04_Domain_Rentals.md @@ -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 \ No newline at end of file +- 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 \ No newline at end of file diff --git a/planning/06_Domain_Repairs.md b/planning/06_Domain_Repairs.md index e2557b2..b124bd8 100644 --- a/planning/06_Domain_Repairs.md +++ b/planning/06_Domain_Repairs.md @@ -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 \ No newline at end of file +- 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 \ No newline at end of file diff --git a/planning/07_Domain_Sales_POS.md b/planning/07_Domain_Sales_POS.md index ce6e6a8..a3cc01d 100644 --- a/planning/07_Domain_Sales_POS.md +++ b/planning/07_Domain_Sales_POS.md @@ -228,4 +228,255 @@ Account charge Internal -Charges to account balance — billed later \ No newline at end of file +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 \ No newline at end of file diff --git a/planning/08_Domain_Payments_Billing.md b/planning/08_Domain_Payments_Billing.md index e724a84..fc3151b 100644 --- a/planning/08_Domain_Payments_Billing.md +++ b/planning/08_Domain_Payments_Billing.md @@ -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. \ No newline at end of file +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 \ No newline at end of file diff --git a/planning/15_Licensing_Modules_Pricing.md b/planning/15_Licensing_Modules_Pricing.md index 9b6ff23..6027dbe 100644 --- a/planning/15_Licensing_Modules_Pricing.md +++ b/planning/15_Licensing_Modules_Pricing.md @@ -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 diff --git a/planning/22_Audit_Phase2_Review.md b/planning/22_Audit_Phase2_Review.md index 0730dbe..5ed2ca2 100644 --- a/planning/22_Audit_Phase2_Review.md +++ b/planning/22_Audit_Phase2_Review.md @@ -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.