Skip to content

Data Model Reference

This document describes the MongoDB data model used by the Upvendo backend. All models are implemented as RawModels (plain PHP objects) in app/RawModels/, with corresponding factories in app/RawFactories/.


Database Architecture

Upvendo uses a multi-database multi-tenant approach with MongoDB:

  • Central database (upvendo) -- Shared data: vendors, users, locations, roles, payment profiles, global customers
  • Tenant databases (one per vendor, e.g., vendor_acme_123) -- Vendor-specific data: menus, items, transactions, settings, inventories, etc.

The Vendor.database_name field determines which tenant database a vendor's data lives in.


Core Entity Hierarchy

Vendor (central DB)
  |
  +-- User (central DB) -- team members who manage the vendor
  |
  +-- Location (central DB) -- physical store/restaurant locations
  |     |
  |     +-- Menu (tenant DB) -- menus available at this location
  |     |     +-- DisplayGroup -- visual grouping of items within a menu
  |     |           +-- Item -- products/items in the display group
  |     |
  |     +-- Category (tenant DB) -- item categories
  |     +-- Device (tenant DB) -- kiosk devices at this location
  |     +-- DeviceProfile (tenant DB) -- configuration for devices
  |     +-- Transaction (tenant DB) -- orders placed at this location
  |     +-- Inventory (tenant DB) -- stock levels per item per location
  |     +-- TableSection (tenant DB) -- table/seating sections
  |     +-- Offer (tenant DB) -- promotional offers
  |     +-- Loyalty (tenant DB) -- loyalty program config
  |
  +-- BrandingProfile (tenant DB) -- visual branding settings
  +-- BillingProfile (tenant DB) -- billing/subscription info
  +-- Language (tenant DB) -- supported languages
  +-- Subscription (tenant DB) -- active service subscriptions

Polymorphic Collections

Two collections use a model discriminator field to store multiple entity types:

Model ValueRawModel ClassDescription
itemItemProducts/menu items
menuMenuMenu definitions
modifierModifierIndividual modifiers
modifier_groupModifierGroupGroups of modifiers

settings collection (tenant DB)

Model ValueRawModel ClassDescription
categoryCategoryItem categories
languageLanguageSupported languages
display_groupDisplayGroupMenu display groupings

Always include a model filter when querying these collections to avoid returning the wrong entity type.


Central Database Models

Vendor

Collection: vendors | Connection: mongodb | Soft delete: Yes | Archive: Yes

The top-level entity representing a merchant/business.

FieldTypeDescription
_idObjectIdPrimary key
business_namestringDisplay name of the business
business_categorystringBusiness type (restaurant, frituur, etc.)
database_namestringName of the tenant MongoDB database
slugstringURL-friendly unique identifier
emailstringPrimary contact email
default_language_idstringID of the default Language in tenant DB
branding_profile_idstring?ID of the default BrandingProfile in tenant DB
is_testboolWhether this is a test/demo vendor
stripe_onboarding_completedboolStripe Connect onboarding status
guided_setup_completeboolWhether merchant finished guided setup
provider_integratedstringExternal POS provider (e.g., "square")

Key relationships:

  • Has many Users (via user.vendor_id)
  • Has many Locations (via location.vendor_id)
  • Uses TenancyTrait to switch tenant database

User

Collection: users | Connection: mongodb | Soft delete: Yes

Backoffice team member accounts.

FieldTypeDescription
_idObjectIdPrimary key
emailstringLogin email
first_namestringFirst name
last_namestringLast name
usernamestringUsername
passwordstringHashed password
phonestring?Phone number
vendor_idstringPrimary vendor association
vendor_idsstring[]All vendor associations (multi-vendor access)
role_idsstring[]Assigned role IDs
location_idsstring[]Accessible location IDs
all_location_accessboolIf true, has access to all vendor locations
statusstringAccount status (active, pending, etc.)
otpstring?Current one-time password
last_otp_requestint?Timestamp of last OTP request
resend_counterintOTP resend rate limiter
trusted_devicesarrayList of trusted device fingerprints
remember_meboolRemember me preference
challenge_tokenstring?Active challenge token for sensitive ops
challenge_token_expires_atint?Challenge token expiry timestamp
webauthn_challengestring?Current WebAuthn challenge
passkeysarrayRegistered passkey credentials
photo_templatesarrayPhoto studio templates
invite_tokenstringAccount invitation token
invite_token_expires_atUTCDateTime?Invitation expiry
forget_password_tokenstringPassword reset token
forget_password_atintPassword reset timestamp
forget_password_counterintReset attempt counter

Location

Collection: locations | Connection: mongodb | Soft delete: Yes

Physical store/restaurant locations belonging to a vendor.

FieldTypeDescription
_idObjectIdPrimary key
namestringLocation display name
descriptionstringLocation description
vendor_idstringParent vendor ID
slugstringURL-friendly identifier
statusstringLocation status
categorystringBusiness category override
location_typestringLocation type classification
addressobjectStructured address fields
gmaps_addressobjectGoogle Maps address data with components
pinpointobjectLatitude/longitude coordinates
country_codestringTwo-letter country code (BE, FR, NL, etc.)
currencystringCurrency code (EUR, USD, GBP, etc.)
timezonestringIANA timezone identifier
preferred_languagestringPreferred language for this location
business_hoursobjectWeekly business hours per day
restricted_datesarrayDates when location is closed
contact_informationobjectPhone, email, social media links
average_prep_timestringAverage order preparation time in minutes
snooze_time_out_of_stock_itemsstringDuration to snooze out-of-stock items
branding_profile_idstringBrandingProfile ID in tenant DB
payment_profile_idstringPaymentProfile ID
stripe_customer_idstringStripe customer identifier
viva_wallet_physical_source_codestringViva Wallet physical terminal source
viva_wallet_online_source_codestringViva Wallet online payment source
online_ordering_settingobjectOnline ordering configuration
online_settingsobjectGeneral online platform settings
in_house_settingobjectIn-house/dine-in configuration
qr_ordering_settingobject?QR table ordering configuration
receipt_settingobjectReceipt formatting and display settings
loyalty_subscription_idstring?Loyalty subscription reference
online_ordering_subscription_idstring?Online ordering subscription reference
is_sms_subscribedboolWhether SMS notifications are enabled
landing_page_cloudflare_image_idstring?Cloudflare image ID for landing page
external_dataobjectThird-party integration data
upvote_countintRestaurant suggestion upvotes

Tenant Database Models

Item

Collection: menus | Connection: tenant | Model discriminator: item | Soft delete: Yes

Products/menu items sold by the merchant.

FieldTypeDescription
_idObjectIdPrimary key
category_idstringParent category ID
location_idstringLocation this item belongs to
detailsobjectMulti-language name and description ({lang: {name, description}})
kitchen_namestring?Short name for kitchen display
pricefloatBase price
pricingobjectPlatform-specific pricing ({platform: price})
plustringPLU/barcode code
statusstringItem status (active, disabled, etc.)
tax_rate_codestringTax rate identifier
content_idstringContent/media reference
cloudflare_image_idstringCloudflare Images ID
category_idstringCategory reference
variant_group_idstring?Variant group (e.g., sizes) reference
modifier_group_idsstring[]Associated modifier group IDs
display_group_idsstring[]Display groups this item appears in
offer_idsstring[]Applied offer IDs
platformsstring[]Platforms where item is visible (kiosk, online, etc.)
ingredientsstring[]Ingredient labels
allergensstring[]Allergen labels
dietary_preferencesstring[]Dietary preference tags (vegan, vegetarian, etc.)
dietary_supplementsstring[]Dietary supplement tags
contains_alcoholboolWhether item contains alcohol
alcohol_typestringType of alcohol if applicable
minimum_ageintMinimum age requirement
calorie_countfloatCalorie information
prep_time_secondsintPreparation time in seconds
use_default_prep_timeboolWhether to use location's default prep time
max_order_limitintMaximum quantity per order (0 = unlimited)
external_dataobjectThird-party integration data (keyed by provider)
external_idsobjectThird-party IDs (keyed by provider)
birthday_bonus_idstringLinked birthday bonus
reward_idsstring[]Linked loyalty reward IDs
raw_valueobjectRaw/unprocessed value data

Indices: location_id, status, variant_group_id, plu

Key traits: HasExternalIds, HasLocation, HasModifierGroup, PriceTrait

Collection: menus | Connection: tenant | Model discriminator: menu | Soft delete: Yes

Menu definitions that group display groups and items for a location.

FieldTypeDescription
_idObjectIdPrimary key
namestringMenu name
pos_namestringPOS-specific name
descriptionstringMenu description
location_idstringLocation this menu belongs to
statusstringMenu status
availability_typestringWhen menu is available (LocationDefault, AlwaysAvailable, Custom)
availabilityobjectCustom availability schedule per day
visibilitystring[]Channels where menu is visible (kiosk, online_ordering, etc.)
device_profile_idsstring[]Device profiles that use this menu
display_group_idsstring[]Display groups in this menu
external_dataobjectThird-party integration data

Category

Collection: settings | Connection: tenant | Model discriminator: category | Soft delete: Yes

Item categories for organizing products.

FieldTypeDescription
_idObjectIdPrimary key
detailsobjectMulti-language name/description ({lang: {name, description}})
namestring?Legacy single-language name
content_idstring?Content/media reference
image_urlstringCategory image URL
item_countint?Cached count of items in this category
external_idsobjectThird-party IDs
external_dataobjectThird-party integration data

Transaction

Collection: transactions | Connection: tenant | Soft delete: Yes | Archive: Yes

Order/payment records.

FieldTypeDescription
_idObjectIdPrimary key
idempotency_keystringUnique key to prevent duplicate processing
all_idempotency_keysstring[]All idempotency keys across retries
invalid_idempotency_keysstring[]Invalidated keys
order_nostringHuman-readable order number (10-char random)
receipt_nostringReceipt number (YYMMDD + 6 alphanumeric)
unauthenticated_order_nostring?Order number for unauthenticated customers
location_idstringLocation where order was placed
device_idstringKiosk device ID (empty for online orders)
customer_idstring?CustomerVendor ID
customer_namestringCustomer display name
customer_first_namestringCustomer first name
customer_phonestringCustomer phone
customer_emailstringCustomer email
customer_tokenstringCustomer identification token
customer_addressobject?Delivery address
country_codestringCountry code
dining_optionstringDining option (dine_in, take_out, delivery, for_here, takeout, pickup)
order_channelstringChannel (kiosk, online_ordering, qr_ordering)
order_dateUTCDateTimeScheduled order date/time
order_statusstringKitchen status (preparing, ready, etc.)
statusstringPayment status (complete, pending, etc.)
typestringTransaction type
priorityboolPriority order flag
holdboolHold order flag
table_numberstringTable number for dine-in
pager_idstringPager device ID
order_session_idstringCart/order session identifier
cart_session_idstringCart session for stock reservations
payment_snapshotobjectPayment provider details at time of payment
stripe_account_idstringStripe Connect account
snapshotsobjectFull order snapshot (items, location, language, rewards, offers)
reward_idsstring[]Applied loyalty reward IDs
item_labelstringItem source label (regular, hendrickx, etc.)
external_idsobjectThird-party order IDs
external_dataobjectThird-party order data
status_logsarrayHistory of status changes
sent_atUTCDateTime?When order was sent to kitchen
order_prep_timeUTCDateTime?Estimated prep completion time
split_timeslotsarraySplit delivery timeslots
Money fields:
currencystringCurrency code
subtotalfloatPre-tax, pre-discount total
addonsfloatModifier/addon charges
discountfloatTotal discount amount
feesfloatService fees
delivery_feefloatDelivery fee
tip_amountfloatCustomer tip
vatfloatTotal tax amount
totalfloatFinal total charged

Indices: customer_id, order_date, idempotency_key

Other Tenant Models

ModelCollectionDescription
ModifiermenusIndividual modifier options (extra cheese, etc.)
ModifierGroupmenusGroups of related modifiers
VariantGrouptenantProduct variant groups (sizes, colors)
VariantOptiontenantIndividual variant options
DisplayGroupsettingsVisual grouping of items within a menu
LanguagesettingsSupported language configurations
DevicetenantRegistered kiosk devices
DeviceProfiletenantDevice configuration and menu assignments
InventorytenantStock levels per item per location
InventoryHistorytenantStock change history log
BrandingProfiletenantColors, logos, visual branding
BillingProfiletenantBilling/invoicing configuration
SubscriptiontenantService subscriptions (loyalty, online ordering)
PaymentProfilecentralPayment provider configuration
TableSectiontenantRestaurant table/seating sections
OffertenantPromotional offers and discounts
OfferRedemptiontenantOffer usage tracking
LoyaltytenantLoyalty program configuration
LoyaltyPointtenantCustomer loyalty point records
PointRedemptiontenantLoyalty point redemption tracking
RewardtenantLoyalty rewards definitions
RewardRedemptiontenantReward claim tracking
BirthdayBonustenantBirthday bonus configuration
GiftCardtenantGift card records
ContenttenantMedia/image content records
TaxRatetenantTax rate definitions
TaxRateCustomtenantCustom/override tax rates
TimelinetenantActivity timeline entries
OrderSessiontenantActive ordering sessions
GuidedSetupProgresstenantGuided setup wizard progress

Customer Models

ModelCollectionConnectionDescription
CustomercustomerscentralGlobal customer identity
CustomerAddresscustomer_addressescentralGlobal customer addresses
CustomerVendorcustomer_vendorstenantVendor-specific customer data
CustomerVendorAddresscustomer_vendor_addressestenantVendor-specific delivery addresses
CustomerVendorTokencustomer_vendor_tokenstenantCustomer authentication tokens
FCMTokenfcm_tokenstenantFirebase push notification tokens

Integration Models

ModelCollectionDescription
ThirdPartyIntegrationthird_party_integrationsIntegration configurations
ThirdPartyTransactionthird_party_transactionsSynced third-party transactions
KassanetIntegrationkassanet_integrationsKassanet POS config
SquareIntegrationsquare_integrationsSquare POS config
UberEatsCredentialuber_eats_credentialsUberEats API credentials
HendrickxIntegrationhendrickx_integrationsHendrickx supplier config
VanhoutteIntegrationvanhoutte_integrationsVanhoutte supplier config

Multi-Language Details Pattern

Many entities use a details object for multi-language support:

json
{
  "details": {
    "default": {
      "name": "Frikandel",
      "description": "Classic Dutch snack"
    },
    "nl": {
      "name": "Frikandel",
      "description": "Klassieke Nederlandse snack"
    },
    "fr": {
      "name": "Fricandelle",
      "description": "Snack hollandais classique"
    }
  }
}

The default key is always present and serves as the fallback language. Additional keys correspond to ISO language codes.


Factory Defaults

Each model has a corresponding factory in app/RawFactories/ that defines default field values for new instances. Key factories:

FactoryNotable Defaults
VendorFactoryis_test: false, stripe_onboarding_completed: false
UserFactorystatus: 'active', all_location_access: false, empty arrays for passkeys, trusted_devices
LocationFactoryDefault business hours, empty receipt settings, is_sms_subscribed: false
ItemFactoryprice: 0, status: 'active', empty arrays for allergens/ingredients/modifiers
MenuFactorystatus: 'active', availability_type: 'location_default'
TransactionFactoryAll money fields default to 0, empty arrays for snapshots/rewards
CategoryFactoryEmpty details, empty external data
DeviceFactoryDevice registration defaults
InventoryFactoryDefault stock values
LoyaltyFactoryLoyalty program defaults
OfferFactoryOffer configuration defaults
SubscriptionFactorySubscription lifecycle defaults

Key Patterns for AI Bug Fixing

  1. Check the connection constant. CONNECTION = 'mongodb' means central database, CONNECTION = 'tenant' means tenant database. Querying the wrong connection is a common bug.

  2. Polymorphic collection queries must filter by model. When querying the menus collection, always include model: 'item' (or menu, modifier, modifier_group). Same for settings.

  3. The details object is required for multi-language. If a model has a details field, the default key must always be populated. Missing default causes null name displays.

  4. ObjectId handling. All _id fields and relationship IDs are stored as MongoDB ObjectIds. The toObjectIds() helper converts string arrays. Comparing a string ID to an ObjectId will fail silently.

  5. Soft delete awareness. Models with SOFT_DELETE = true are never physically deleted. Queries should account for deleted_at being null (non-deleted) or non-null (deleted). Repositories handle this automatically.

  6. Transaction snapshots are immutable. The snapshots field captures the full state at order time (items, prices, location settings, language). Do not assume snapshot data matches current entity state.

  7. External data is provider-keyed. external_data and external_ids are objects keyed by provider name (e.g., square, deliveroo, uber_eats). Always access via getExternalData('provider_name').