• English
  • Data Models

    Database schema

    The Primary device runs a Drift (SQLite) database at schema version 66 with 67 tables. The schema is defined in:

    • lib/core/database/app_database.dart (53 KB) — AppDatabase class extending generated _$AppDatabase
    • lib/core/database/tables.dart (51 KB) — all Drift table definitions
    • lib/core/database/app_database.g.dart (2.3 MB) — Drift-generated DAO code

    Schema design principles

    • Guarded migrations: _hasColumn(), _hasTable(), _addColumnIfMissing() make upgrades safe to resume after partial failure and safe for multi-version jumps
    • Denormalized counters: Customers table carries totalSpend, visitCount, pointsBalance for instant dashboard reads
    • Device-aware rows: Every table carries deviceId, version, and syncedToCloud columns for multi-device conflict resolution
    • Financial snapshots: Tickets rows write subtotal, tax, surcharge, tip, total, discountAmount once at checkout — they are immutable after close

    Major table groups

    DomainTables
    Customerscustomers, customer_memberships, customer_tags, customer_notes, customer_addresses
    Staffstaff, staff_roles, staff_permissions, staff_schedules, staff_time_clocks, staff_sessions
    Services & Productsservices, products, categories, service_categories, inventory_items, inventory_transactions
    Tickets & Orderstickets, ticket_services, ticket_products, ticket_payments, ticket_staff, ticket_notes, ticket_audit_log
    Appointmentsappointments, appointment_services, appointment_staff, appointment_resources, booking_requests
    Paymentspayments, payment_splits, card_transactions, gift_cards, gift_card_transactions, loyalty_transactions, store_credit_transactions
    Packagesservice_packages, package_items, customer_packages, package_redemptions
    Sessionsbusiness_sessions, cash_movements, cash_denominations
    Resourcesresources, resource_types, resource_bookings
    Settingsapp_settings, store_info, printers, terminals
    Sync & infrasync_outbox, sync_buffer, idempotency_keys, app_notifications, device_roster, observability_events

    Cloud sync outbox

    The sync outbox is a write-ahead log that tracks every mutation for cloud upload.

    Tables

    • sync_outbox: One row per mutation (upsert or delete), with table_name, pk_value, operation, sequence
    • sync_buffer: EmergencyBuffer queue for Secondary offline writes

    Upload pipeline

    Mutation (any table)
      → Drift trigger inserts row into sync_outbox
      → CloudSyncCollector.collectBatch()     ← re-reads live rows, builds envelopes
      → CloudSyncClient.postBatch()           ← POST /v1/sync/batch via Dio
      → CloudSyncWorker.runOnce()             ← drains in batches (50 batches × 200 rows/tick)
      → CloudSyncScheduler (30-second timer)

    Key source files: lib/core/sync/cloud_sync_worker.dart, cloud_sync_collector.dart, cloud_sync_client.dart, cloud_sync_scheduler.dart

    Sync table registry

    lib/core/sync/sync_table_registry.dart declares which tables sync to the cloud:

    • 47 tables are syncable (in kSyncTableRegistry)
    • 6 tables excluded: sync_outbox, sync_buffer, app_settings, idempotency_keys, booking_requests, app_notifications
    • Each SyncTableSpec declares: table name, PK column, excluded columns, redacted columns
    • An anti-drift test ensures every AppDatabase table is either in the registry or the exclusion set

    Coalescing

    The collector performs outbox coalescing: for multiple mutations to the same (table, pk), only the highest-sequence entry is kept.

    Conflict handling

    • 409 Conflict: Cloud signals a data conflict — the row is skipped for that tick
    • 401 Unauthorized: Lease lost — triggers demotion
    • 403 Forbidden: Store pending activation
    • Network errors: Exponential backoff with jitter

    Restore pipeline

    GET /v1/restore/rows (paginated, cursor-based)
      → CloudRestoreApplier       ← upserts rows, resolves conflicts
      → RestoreGate               ← gates restore to protect live data

    Domain-aware cache invalidation

    lib/core/data/data_domain.dart and domain_dispatch.dart:

    • DataDomain enum: 14 domains — tickets, appointments, staff, staffSchedule, catalogue, membership, resources, reporting, waitlist, giftCards, sessions, bookingRequests, packages
    • domainProviders(domain): Maps each domain to its feature-owned Riverpod provider list
    • invalidateDomains(ref, domains): Invalidates all providers for the given domains
    • Pre-built bundles: dashboardDomains (4 domains), checkoutDomains (8 domains)

    Shared domain models

    lib/shared/models/:

    ModelLocationPurpose
    TicketWithDetailslib/shared/models/ticket_with_details.dart (15 KB)Central order aggregate
    CheckoutModelslib/shared/models/checkout_models.dartCheckout API request/response contracts
    CustomerUiModellib/shared/models/customer_ui_model.dartUI-friendly customer with parsed tags
    OrderAuditEntrylib/shared/models/order_audit_entry.dartTyped audit trail entries
    StaffSessionlib/shared/models/staff_session.dartCurrent staff login session

    Demo seed data

    lib/core/database/demo_seed.dart (81 KB) populates the database with sample data for development and testing. Called during bootstrap via seedAllDemoData().