Music Store Management Platform Domain Design: Sales & Point of Sale # 1. Overview The Sales & POS domain handles all in-person and counter transactions — retail sales, deposits, repair payments, rental fees, and account charges. It integrates with Stripe Terminal for card-present payments and Stripe Elements for keyed entry. # 2. Transaction Types Type Description sale Retail sale of product — instruments, accessories, supplies repair_payment Payment collected at repair ticket pickup rental_deposit Security deposit collected at rental start account_payment Payment against outstanding account balance refund Return of payment — full or partial # 3. Database Schema ## 3.1 transaction Column Type Notes id uuid PK company_id uuid FK Tenant scoping location_id uuid FK Physical location where transaction occurred transaction_number varchar Human-readable ID account_id uuid FK Nullable for anonymous cash sales transaction_type enum sale|repair_payment|rental_deposit|account_payment|refund status enum pending|completed|voided|refunded subtotal numeric(10,2) Before tax and discounts discount_total numeric(10,2) Total discounts applied tax_total numeric(10,2) total numeric(10,2) Final amount charged payment_method enum card_present|card_keyed|cash|check|account_charge stripe_payment_intent_id varchar For card transactions stripe_terminal_reader_id varchar Which reader was used processed_by uuid FK Employee who processed notes text created_at timestamptz ## 3.2 transaction_line_item id, transaction_id, product_id (nullable), inventory_unit_id (nullable),description, qty, unit_price, discount_amount, discount_reason,tax_rate, line_total, created_at ## 3.3 discount id, company_id, location_id, name, discount_type (percent|fixed),discount_value, applies_to (order|line_item|category),requires_approval_above (threshold amount), is_active,valid_from, valid_until, created_at ## 3.4 discount_audit id, transaction_id, transaction_line_item_id, discount_id,applied_by (employee_id), approved_by (employee_id, nullable),original_amount, discounted_amount, reason, created_at # 4. Discount & Adjustment Rules - Discounts are applied at line item or order level - Discounts exceeding manager threshold require approval — logged in discount_audit - Manual price overrides treated as discounts — require reason code - Promotional codes validated against discount table before application - All discounts appear as line items on invoice — original price shown - Discount audit records are immutable — no deletion permitted # 5. Payment Methods Method Integration Notes Card present Stripe Terminal WiFi Desktop — WisePOS E reader Card present (BT) Stripe Terminal BT iOS — Bluetooth reader at conventions Card keyed Stripe Elements Desktop — PCI safe, card data never touches app Cash Manual entry Change calculated, drawer tracked Check Manual entry Check number recorded Account charge Internal Charges to account balance — billed later # 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