Data Model & Integrity
Understand the data architecture and validate relationships
Data Model Architecture
Understanding the relationships between entities
Customer / Contact
User or Contact entity - the customer
Order
Top-level container for quote/sale lifecycle
LineItem
Individual items in an order - pricing source of truth
BookingV2
Time-based reservation for schedulable items
Invoice
Payment document generated from Order
Item
Catalog item (Studio, Gear, Ticket, etc.)
Project
Optional: groups related orders/bookings
Booking Lifecycle Flow
The journey from inquiry to fulfillment
Inquiry
Customer requests booking → Order created (status: inquiry)
Quote Draft
Staff adds LineItems → Order.totals calculated
Quote Sent
Order status → sent, customer receives quote
Accepted
Customer accepts → BookingV2 created (if schedulable)
Invoice
Invoice generated → stripe_payment_link created
Paid
Payment confirmed → Booking.status → confirmed
Fulfilled
LineItems fulfilled (tickets issued, gear allocated)
Entity Relationships
Data Model Rules
✅ Required Patterns
- • Every BookingV2 MUST have an order_id
- • Every LineItem MUST have order_id and item_id
- • Order.booking_ids[] must sync with actual BookingV2 records
- • LineItem is the SOURCE OF TRUTH for pricing, not BookingV2
- • Order.totals are CALCULATED from LineItems
⚠️ Optional Patterns
- • LineItem.booking_id is optional (only for schedulable items)
- • Order.project_id is optional (only if order belongs to project)
- • Invoice.booking_id is optional (can invoice entire order)
- • Non-schedulable items (merch, credits, memberships) have LineItems but NO BookingV2
🚫 Anti-Patterns to Avoid
- • Never store pricing in BookingV2 - always use LineItem
- • Never create BookingV2 without a parent Order
- • Never create LineItem without order_id and item_id
- • Never manually calculate totals - use calculateOrderTotals function
- • Never create orphaned records (always validate foreign keys)
Example: Studio Booking Flow
// Step 1: Customer submits inquiry
Order.create({
customer_email: "user@example.com",
status: "inquiry",
inquiry_details: "Need Studio A for 4 hours"
})// Step 2: Staff creates quote (adds line items)
LineItem.create({
order_id: order.id,
item_id: "studio_a_id",
quantity: 4, // 4 hours
unit_price_usd: 50,
line_total_usd: 200
})calculateOrderTotals(order.id) // Updates Order.totals
Order.update(order.id, { status: "sent" })// Step 3: Customer accepts quote
Order.update(order.id, { status: "accepted" })// Create booking for time slot
BookingV2.create({
order_id: order.id,
item_id: "studio_a_id",
start_at: "2026-03-15T14:00:00Z",
end_at: "2026-03-15T18:00:00Z",
status: "reserved"
})// Update Order with booking reference
Order.update(order.id, { booking_ids: [booking.id] })
LineItem.update(lineItem.id, { booking_id: booking.id })// Step 4: Staff sends invoice
Invoice.create({
order_id: order.id,
booking_id: booking.id, // optional
customer_email: order.customer_email,
totals: order.totals, // inherited from order
stripe_payment_link: stripeUrl
})// Step 5: Payment received (webhook)
Order.update(order.id, { status: "paid" })
Invoice.update(invoice.id, { status: "paid" })
BookingV2.update(booking.id, { status: "confirmed" })// Fulfill line items
fulfillOrder(order.id) // Issues tickets, allocates resources
Data Integrity Checklist
When Creating Records:
- ✓Order first, then LineItems, then BookingV2 (if schedulable)
- ✓Always validate Item exists before creating LineItem
- ✓Update Order.booking_ids when creating BookingV2
- ✓Run calculateOrderTotals after modifying LineItems
When Deleting Records:
- ✓Delete in reverse order: BookingV2, then LineItems, then Order
- ✓Delete related Invoices when deleting Order
- ✓Delete related BookingMessages when deleting Booking
- ✓Update Order.booking_ids when deleting BookingV2