Skip to main content

upp-data -- Data Objects

The upp-data library provides a schema-driven data object system that forms the backbone of the entire application. Every persistent entity -- users, places, products, tickets -- is represented as a DataObject with a declarative schema that defines its fields, types, default values, and relationships to other objects.

This system exists for three reasons:

  1. Synchronization. The application works offline and syncs changes with the backend. The schema tells the sync engine exactly which fields to send, how to serialize them, and which related objects must be committed together.
  2. Validation. Field types are enforced at runtime. Setting a number field to a string throws an error immediately, catching bugs early instead of corrupting the database.
  3. Relationship management. Parent-child and reference relationships are declared once in the schema. The framework then generates typed getters, setters, Add/Del methods, and handles cascading commits and deep copies automatically.

Understanding DataObjects is essential before working on any feature, because virtually all application logic reads from and writes to these objects.


How Schemas Work

Every DataObject starts with a schema definition -- a const object that acts as a DSL (domain-specific language) describing the database table it maps to. The schema has three top-level properties:

PropertyTypePurpose
namestringThe database table name (e.g. "PRODUCT", "TICKET")
fields_schemaColumn[]Array of column definitions describing each field
relate_schemaRelate[]Array of relationship definitions connecting this object to others

Here is a simplified example from the User object:

const _schema = {
name: "USER",
fields: [
{ name: "status", type: "string", default: "PA" },
{ name: "email", type: "string" },
{ name: "firstname", type: "string" },
{ name: "lastname", type: "string" },
{ name: "photo", type: "file" },
{ name: "phone", type: "string" },
{ name: "lang", type: "string" },
{ name: "license", type: "boolean", default: false },
],
relate: [
{ direction: "<", target: "PLACE", by: "user", name: "place", reason: "places", child: true, class: PlaceType },
{ direction: "<", target: "STAFF", by: "user", name: "employee", reason: "employees", child: true, class: EmployeeType },
{ direction: "<", target: "SESSION", by: "user", name: "session", reason: "sessions", child: true, class: SessionType }
]
} as const;

The as const assertion is critical -- it enables TypeScript to infer literal types from the schema, which CreateObject() uses to generate a fully typed class.

Field Definitions

Each entry in the fields array is a _schemaColumn with the following properties:

PropertyTypeRequiredDescription
namestringYesColumn name as it appears in the database
typestringYesOne of the supported field types (see below)
defaultanyNoDefault value assigned when the object is created locally
aliasstringNoAlternative property name in the TypeScript class. For example, the field prodopt in the database is exposed as option in the class when alias: "option" is set
volatilebooleanNoWhen true, the field is not sent to the server during commits. Used for locally-computed or server-managed fields like created and updated timestamps

Field Types

The schema supports six field types. Each type determines how data is validated on write, serialized for the server, and deserialized when loaded from the database:

TypeTypeScript TypeStored As (Server)Description
stringstring | nullVARCHAR/TEXTPlain text value
filestring | nullFile pathA urlfile object internally ({ url, b64 }). The getter returns the URL or base64 string; the setter accepts either a URL or a data-URI
numbernumber | nullDECIMAL/INT (as string)Numeric value. Sent as a string to the server, parsed back to number on load
dateDate | nullMySQL datetime stringJavaScript Date object. Converted to/from MySQL datetime format automatically
booleanboolean | null'0' or '1'Boolean value. Stored as a single character in the database
objidstring | nullINT (foreign key)A foreign key reference to another object. Fields of this type are usually paired with a relationship definition in relate

Relationship Definitions

Each entry in the relate array is a _schemaRelate that describes how this object connects to another:

PropertyTypeDescription
direction'>' or '<'> means this object references another (outgoing, 1:1). < means other objects reference this one (incoming, 1:N)
targetstringThe database table name of the related object
bystringThe field on the related object that stores the reference. For > relations, this is the field name on the child side; for < relations, it is the field on the child that points back to this parent
namestringThe accessor name for 1:1 relations (used as the getter/setter property name)
reasonstringThe key used to store the relationship internally. For 1:N relations, this becomes the list accessor name (e.g. products, tickets)
childbooleanWhen true, this related object is considered a child -- it will be included in commit operations and deep copies when the parent is committed or copied
classclassOptional typed class reference for proper TypeScript casting of the related object

Outgoing Relations (> -- 1:1)

An outgoing relation means "this object holds a reference to another object." The reason field stores the objid of the referenced object. The framework generates:

  • A getter that returns the related object instance (or null)
  • A setter that accepts a DataObject or null, updating the reference

For example, a Ticket has { direction: ">", target: "PLACE", name: "place", reason: "place" }. This means ticket.place returns the Place object that the ticket belongs to, and ticket.place = somePlace updates the reference.

Incoming Relations (< -- 1:N)

An incoming relation means "other objects reference this one." The framework generates:

  • A list getter using the reason name (e.g. place.products returns an array of Product objects)
  • An Add<Name> method (e.g. place.AddProduct(product))
  • A Del<Name> method (e.g. place.DelProduct(product))

The name property (singular) is used to build the Add/Del method names with the first letter capitalized.

Child Relations

When child: true, the relationship has cascading behavior:

  • Commits: When the parent is committed via DoCommit(), all child objects are included in the commit payload automatically. This ensures the server receives the complete object graph in one operation.
  • Deep copies: When the parent is copied via Copy(), all child objects are recursively copied as well, maintaining the relationship structure in the new copy.
  • Overwrite: When a copied parent is overwritten back onto the original via Overwrite(), the child structure is restored recursively.

Non-child relations (like Ticket > Place) are simple references -- the ticket points to the place, but committing a ticket does not commit the place.


The Class Hierarchy

Understanding the inheritance chain helps when reading the source code or extending objects:

BaseObject              Abstract base. Tracks commit state (_toInsert, _toUpdate, _toDelete),
| manages the unique identifier (_uuid/objid), and defines the commit pipeline.
|
v
RelatedObject Adds relationship management: _children, _chldlist, SetChild/AddChild/DelChild,
| and the bidirectional relation tracking (_related map).
|
v
DataObject Adds the view layer (ViewObject/viewProxy), deep copy support (Copy/Overwrite),
| refresh propagation (OnRefresh, UpRefresh), and patchValue for change tracking.
|
v
BaseClass<T> Generated by CreateObject(). Dynamically defines typed getters/setters for all
| schema fields, implements Change/Depend/Children for the commit pipeline,
| and handles Data/Info serialization based on the schema.
|
v
[Your Object] Your concrete class (e.g. Ticket, Product). Extends the generated BaseClass
with domain-specific logic: custom getters, business rules, flag management,
and the viewProxy override for the UI layer.

CreateObject -- The Schema Compiler

CreateObject(schema) is the function that bridges the gap between a static schema definition and a live TypeScript class. It:

  1. Validates the schema structure (field types, relation directions)
  2. Creates an abstract class extending BaseClass<T> with the schema baked in
  3. Defines getters/setters on the prototype for every field, with runtime type validation
  4. Defines relationship accessors on the prototype (1:1 getters/setters, 1:N list getters and Add/Del methods)
  5. Returns the class constructor, which you then extend with your concrete class

When a derived class overrides a field getter/setter (common for status, photo, payment), the base accessor is remapped to __base__<fieldname>. This allows the derived class to call this['__base__status'] to read/write the raw value while adding business logic in the override.


Object Lifecycle

Creating a New Object

When you instantiate a DataObject with objid = null, the framework treats it as a new object that needs to be inserted into the server:

const ticket = new Ticket(null, dataService);
ticket.place = currentPlace;
ticket.qrcode = currentTable;
ticket.status = 'PR';

At this point:

  • _toInsert = true -- the object is flagged for insertion
  • objid is null -- the server will assign a real ID during sync
  • _uuid is auto-generated -- used as a temporary identifier until the server responds with a real objid

Loading From the Server (Sync)

When data arrives from the server (during synchronization), the framework calls OnUpdate(info) on the object. This method:

  1. Checks if the object can be updated (_CanUpdate)
  2. Compares the incoming data against the last update to skip no-ops
  3. Calls _OnUpdate(info) which deserializes database values into typed properties via the schema
  4. Resolves or replaces the object in storage (maps the server-assigned objid to the local _uuid)
  5. Triggers DoRefresh() to notify the view layer
  6. Marks _isLoaded = true and completes the _waitLoaded subject

After loading, the object has a real objid and is fully resolved in the data store.

Modifying an Object

Setting any schema-defined property triggers the generated setter, which:

  1. Validates the value type against the schema
  2. Calls patchValue() to check if the value actually changed
  3. If changed, sets ToUpdate = true, which flags the object for the next commit
product.name = "Cafe con leche";
product.price = 2.50;
product.taxrate = 10;

Each property change is tracked internally. The Change getter later collects all current field values for the commit payload.

Committing Changes

Committing sends changes to the server. The process works through a recursive commit map:

await ticket.DoCommit();
  1. _CommitMap() walks the object and all its child: true relations recursively, collecting every object that has _toInsert, _toUpdate, or _toDelete flags set
  2. For each object, it builds a CommitChange containing:
    • relation: identifies the object (table + objid or uuid)
    • entrynfo: the serialized field data plus the action (do_insert, do_update, do_delete)
    • requires: dependency information ensuring related objects exist before this one
  3. data.Commit(changes) queues all changes
  4. data.Flush(force) sends them to the server (immediately if force = true, otherwise on next sync cycle)

This design ensures that an entire object graph (e.g. a ticket with all its products, options, offers, and discounts) is committed atomically.

Deep Copies and Overwrite

The copy/overwrite pattern is used for editing workflows where you want to modify an object without affecting the original until the user confirms:

const copy = ticket.Copy();

copy.status = 'RD';
copy.AddProduct(newProduct);

const original = copy.Overwrite();
await original.DoCommit();
  • Copy() creates a deep clone of the object and all child: true relations, setting _copyof on each clone to point back to the original
  • Overwrite() transfers the copy's data back to the original, recursively restoring all relationships, and removes the copy from the data store

Object Status Convention

Most objects use a status string field with these common values:

StatusMeaning
ACActive -- the object is live and valid
PAPending -- awaiting activation (common for new users)
DEDeleted -- logically deleted (soft delete)
PRPreparing -- used for tickets that are being built
RDReady -- used for tickets that are ready to serve
CCCancelled -- used for tickets that were cancelled
CTCatalog -- used for products that come from the catalog template

The status setter in most objects includes a guard: if (this.status == 'DE') return; -- once an object is deleted, its status cannot be changed back. When a non-committed object is deleted, it is also removed from its parent's child list automatically.


Core Objects

User

Table: USER | Default status: PA (Pending)

The User object represents an authenticated person in the system. A user owns places and has sessions on devices.

Key fields:

FieldTypeDescription
statusstringPA = pending activation, AC = active, DE = deleted
emailstringLogin email address. Unique per user
firstnamestringFirst name
lastnamestringLast name
photofileProfile photo (URL or base64)
phonestringPhone number
langstringPreferred language code (e.g. "es", "en")
licensebooleanWhether the user holds a valid license

Computed properties:

  • fullname -- concatenates firstname and lastname
  • IsActive -- true when status == 'AC'
  • IsPending -- true when status == 'PA'

Relationships:

DirectionTargetAccessorChildWhat it means
childPLACEplacesYesPlaces owned by this user
childSTAFFemployeesYesEmployee roles this user holds (one per place)
childSESSIONsessionsYesActive device sessions

A user can own multiple places and be an employee at other users' places simultaneously.


Session

Table: SESSION | Default status: AC

A Session represents a device that is currently logged in. It ties a user to a device and optionally to a specific QR code (table) they scanned.

Key fields:

FieldTypeDescription
statusstringAC = active, DE = deleted
idstringA session identifier (used as the UUID override -- _uuid returns this.id)
deviceidstringUnique device identifier

Computed properties:

  • IsActive -- true when status == 'AC' and updated is within the last 600 seconds (10 minutes). This means sessions expire automatically when the device stops sending updates.

Relationships:

DirectionTargetAccessorChildWhat it means
refUSERuserNoThe logged-in user
refQRCODEqrcodeNoThe QR code (table) the session is associated with
childFCMfcmNoFirebase Cloud Messaging tokens for push notifications

Employee

Table: STAFF | Default status: AC

An Employee links a User to a Place with specific permissions. This is the authorization model -- what a person is allowed to do at a particular place.

Key fields:

FieldTypeDescription
statusstringAC = active, DE = deleted
aliasstringDisplay name override for this place
allowstringPermission bitmask stored as a string of '0' and '1' characters
emailstringContact email (may differ from the user's main email)
photofilePhoto override for this place

Delegated properties (read from the linked User):

  • firstname, lastname, fullname, phone

Permission system: The allow field is a positional bitmask where each position corresponds to an EmployeePermission enum value:

PositionPermissionDescription
0StaffManageCan manage employees
1OfferManageCan manage offers
2ProductManageCan manage the product catalog
3TableManageCan manage QR codes/tables
4ExtraManageCan manage extras/surcharges
5PlaceManageCan manage place settings
6LicenceManageCan manage the licence
7CanComplimentCan apply complimentary items
8CanAvailableCan set availability
9POSManageCan use the POS interface
10ReportAccessCan view reports
11TillManageCan manage the cash register
12TillOpenCan open the cash drawer
13CanReopenCan reopen closed tickets
14CanModifyCan modify existing tickets
15CanCancelCan cancel tickets
16CanReturnCan process returns

Use employee.IsAllowedTo(EmployeePermission.ProductManage) to check and employee.SetAllowTo(EmployeePermission.ProductManage, true) to grant.

Relationships:

DirectionTargetAccessorChildWhat it means
refUSERuserNoThe underlying user account
refPLACEplaceNoThe place this employment belongs to

Place and Infrastructure

Place

Table: PLACE | Default status: AC

A Place is the central entity of the system -- it represents a physical business location (restaurant, bar, shop). Everything else (products, tickets, tables, employees) belongs to a place.

Key fields:

FieldTypeDescription
statusstringAC = active
namestringBusiness name
descriptionstringDescription shown to customers
photofileMain photo. Falls back to a generated avatar from the name
logofileBusiness logo
expiresdateLicense expiration date
businessstringType of business
companybooleanWhether it is a registered company
activitystringBusiness activity code
taxidstringTax identification number (CIF/NIF)
phonestringContact phone

Computed properties:

  • avatar -- generates a placeholder image URL from the place name
  • photo -- returns the uploaded photo, or falls back to the avatar
  • IsValid -- true when status == 'AC'
  • IsExpired -- true when expires is in the past
  • Initialize() -- emits on OnInitialize to signal that the place has been fully loaded for the first time

Relationships:

DirectionTargetAccessorChildWhat it means
refUSERuserNoThe owner of this place
refADDRESSaddressYesPhysical address (geocoded)
refADDRESSfiscalYesFiscal/billing address
childQRCODEqrcodesYesTables/QR codes
childPRODUCTproductsYesProduct catalog
childOFFERoffersYesActive offers/menus
childEXTRAextrasYesSurcharges
childDISCOUNTdiscountsYesDiscounts
childSTAFFemployeesYesEmployees
childTICKETticketsYesAll tickets (orders)
childSTRIPEstripesYesPayment provider accounts
childPAYACCOUNTaccountsYesCustomer payment accounts
childRASPPIservicesYesConnected hardware services
childPLACEOPToptionsYesPlace configuration options
childPLACELINKlinksYesExternal links
childINVOICEBUSINESSclientsYesInvoice clients
childAUDITauditsYesAudit trail entries
childCASHCHANGEcashchangesYesCash register changes
childPAYMENTtransactionsYesPayment transactions

The Place object is the root of the data tree. During synchronization, the entire place graph (products, offers, tickets, etc.) is loaded as children of the place.


Address

Table: ADDRESS

An Address stores geocoded location data. It is always a child of another object (typically a Place for both the physical and fiscal addresses).

Key fields:

FieldTypeDescription
formattedstringFull formatted address string (e.g. "Calle Mayor 5, 28001 Madrid")
doorstringDoor/apartment number
latitudenumberGPS latitude
longitudenumberGPS longitude
accuracynumberLocation accuracy in meters
timezonestringIANA timezone (defaults to 'UTC')
streetstringStreet name
numberstringStreet number
zipcodestringPostal code
townstringCity/town
provincestringProvince/state
countrystringCountry

No relationships. Address objects are always embedded as children of a parent object.


QrCode

Table: QRCODE | Default status: AC

A QrCode represents a table or service point in a place. Customers scan the QR code to access the menu and place orders. In a restaurant context, each table has its own QR code.

Key fields:

FieldTypeDescription
statusstringAC = active, PA = pending setup
aliasstringHuman-readable name (e.g. "Table 5", "Bar")
numbernumberTable number
tillobjidReference to the cash register this table belongs to

Computed properties:

  • IsPending -- true when status == 'PA'

Relationships:

DirectionTargetAccessorChildWhat it means
refPLACEplaceNoThe place this table belongs to
refPLACEAREAareaYesThe area/zone of the place (e.g. terrace, indoor)
refDRAWITEMdrawYesVisual representation on a floor plan
childASKWAITERrequestsYesWaiter call requests from this table
childEXTRATABLEextrasYesExtras/surcharges specific to this table
childTICKETticketsYesActive tickets on this table

QrScan

Table: QRSCAN | Default status: AC

A QrScan is the global entry point created when a customer scans a QR code. It is used as the sync root for the guest "table" load -- when a guest scans, the system creates or retrieves a QrScan that gives them access to the appropriate place and table data.

Key fields:

FieldTypeDescription
statusstringAC = active
authorizationstringAuth token for the scan session
qrcodeobjidThe QR code that was scanned
numbernumberTable number (copied from QrCode)
aliasstringTable alias (copied from QrCode)

Relationships:

DirectionTargetAccessorChild
refPLACEplaceNo

PrintDevice

Table: PRINTER | Default status: AC

Represents a physical printer connected to the system (typically a thermal receipt printer).

Key fields:

FieldTypeDescription
namestringPrinter display name
devicestringDevice path or address
typestringPrinter type/protocol
mt, mr, mb, mlnumberMargins in millimeters (top, right, bottom, left)
widthnumberPaper width in millimeters
fontnumberFont size
copiesnumberNumber of copies to print (default: 1)
updatesbooleanPrint order updates
posudtsbooleanPrint POS updates
oncashbooleanPrint on cash payment
oncardbooleanPrint on card payment
boldbooleanUse bold font
stylestringPrint style template (default: 'NULL')

No relationships defined in the schema.


Catalog Objects

The catalog is the product hierarchy of a place. It consists of products, their options (customizations), categories (groupings of options), preselects (preset option combinations), families (product variants), and associated scheduling.

Product

Table: PRODUCT | Default status: AC

A Product is the core item in the catalog -- anything that can be ordered. Products can form hierarchies (a product can have a parent product), and they can be linked together in display order via next.

Key fields:

FieldTypeDescription
statusstringAC = active, DE = deleted, CT = catalog template
namestringDisplay name
descriptionstringDescription shown to customers
photofileProduct image. Falls back to a generated avatar
pricenumberBase price (tax-exclusive)
taxratenumberTax rate percentage. Defaults to AppConstants.defaultTaxRate if not set
codestringInternal product code (e.g. barcode, SKU)
ctgrefstringCatalog reference -- links to a product template in the global catalog
typestringProduct type classifier
sortnumberSort order within its parent or place
isgroupbooleanWhether this product is a visual group/header (not orderable itself)
genericbooleanWhether this is a generic/customizable product
isfamilybooleanWhether this product has family variants
flagsstringBitmask for visibility and pricing flags

Flag system: Similar to the Employee permission bitmask:

PositionFlagDescription
0HideAppHide from the customer-facing QR app
1HidePdfHide from the PDF menu
2HideWebHide from the web menu
3MarketPrice (IsMarket)Product does not have a fixed price (price entered at sale time)
4Weighted (IsWeighted)Product is sold by weight

Computed properties:

  • avatar / photo -- fallback photo generation from the product name
  • taxrate -- returns AppConstants.defaultTaxRate when no explicit rate is set
  • family -- returns the first valid Family from the families list

Catalog behavior: Products can come from a shared catalog template. When IsCatalog is set to false, it cascades down to all children, transitioning them from CT (catalog) to AC (active) status.

Relationships:

DirectionTargetAccessorChildWhat it means
refPLACEplaceNoThe place this product belongs to
refPRODUCTparentYesParent product in a hierarchy
refPRODUCTnextNoNext product in display order
childCATEGORYcategoriesYesOption categories for this product
childPRESELECTselectsYesPreset option combinations
childFAMILYfamiliesYesProduct family variants

Category

Table: CATEGORY | Default status: AC

A Category groups related options for a product. For example, a burger might have categories "Size" (with options Small/Medium/Large) and "Extras" (with options Cheese/Bacon/Egg).

Key fields:

FieldTypeDescription
namestringCategory name (e.g. "Size", "Toppings")
sortnumberDisplay order
typestringCategory type
fxminnumberMinimum number of selections required
fxmaxnumberMaximum number of selections allowed

The fxmin/fxmax fields enforce selection constraints. For example, fxmin: 1, fxmax: 1 means the customer must pick exactly one option.

Relationships:

DirectionTargetAccessorChildWhat it means
refPRODUCTproductNoThe product this category belongs to
childPRODUCTOPToptionsYesThe options within this category
childCATEGORYDEPdependsYesDependencies on other categories

ProductOpt

Table: PRODUCTOPT | Default status: AC

A ProductOpt (product option) is a selectable customization within a category. For example, "Large" in a "Size" category, or "Extra cheese" in a "Toppings" category.

Key fields:

FieldTypeDescription
namestringOption name
pricenumberAdditional price for this option
descriptionstringDescription
sortnumberDisplay order within the category
flagsstringOption flags
ctgrefstringCatalog reference (volatile -- not persisted)

Relationships:

DirectionTargetAccessorChild
refPRODUCTproductNo
refCATEGORYcategoryNo

Preselect

Table: PRESELECT | Default status: AC

A Preselect represents a preset combination of options for a product. Instead of choosing options one by one, the customer can select a preselect that applies a predefined set of choices. Think of it as a "variant" or "combo" of a product.

Key fields:

FieldTypeDescription
namestringPreselect name (e.g. "Classic Combo")
photofilePhoto
descriptionstringDescription
codestringInternal code
sortnumberDisplay order

Computed properties:

  • IsMarket, IsWeighted -- delegated from the parent product

Relationships:

DirectionTargetAccessorChild
refPRODUCTproductNo
childPRESELECTOPToptionsYes

Family, FamilyProduct, FamilyPeriod

The Family system allows a product to have different variants or to be available only during certain time periods.

Family (FAMILY):

FieldTypeDescription
alldaybooleanWhether the family is available all day
DirectionTargetAccessorChild
refPRODUCTproductNo
childFAMILYPRODUCTproductsYes
childFAMILYPERIODperiodsYes

FamilyProduct (FAMILYPRODUCT) -- links a family to specific products and preselects:

FieldTypeDescription
sortnumberDisplay order

Its IsValid getter checks both product.IsValid and preselect.IsValid.

DirectionTargetAccessorChild
refFAMILYfamilyNo
refPRESELECTpreselectNo
refPRODUCTproductNo

FamilyPeriod (FAMILYPERIOD) -- defines when a family is available:

FieldTypeDescription
weekdaysstringBitmask of active weekdays
inistringStart time
endstringEnd time
timezonestringTimezone for the period

Pricing Objects

Offer

Table: OFFER | Default status: AC

An Offer is a special price or menu deal for a place. It can bundle multiple products at a set price and be restricted to certain time periods.

Key fields:

FieldTypeDescription
namestringOffer name (e.g. "Lunch Menu")
descriptionstringDescription
photofilePhoto
pricenumberOffer price
ismenubooleanWhether this is a structured menu (with categories like starters, mains, desserts)
alldaybooleanWhether the offer is available all day

Relationships:

DirectionTargetAccessorChildWhat it means
refPLACEplaceNoOwning place
childOFFERPRODUCTproductsYesProducts included in the offer
childOFFERPERIODperiodsYesTime periods when the offer is active

OfferProduct (OFFERPRODUCT) -- a product within an offer:

FieldTypeDescription
groupednumberGrouping count (for menus: how many of this item are included)
menuctgstringMenu category label (e.g. "Starters", "Mains")

OfferOption (OFFERPRODUCTOPT) -- preselected options for an offer product:

DirectionTargetAccessor
refOFFERPRODUCTproduct
refPRODUCTOPToption

Note: prodopt is aliased to option for cleaner access.

OfferPeriod (OFFERPERIOD) -- availability windows (same structure as FamilyPeriod).


Extra

Table: EXTRA | Default status: AC

An Extra is a surcharge or additional service that can be applied to orders. Examples: table service charge, delivery fee, rush order premium.

Key fields:

FieldTypeDescription
namestringExtra name
descriptionstringDescription
photofilePhoto
typestringExtra type
pricenumberPrice
alldaybooleanAvailable all day
allprdbooleanApplies to all products (vs. specific product selection)

Relationships:

DirectionTargetAccessorChildWhat it means
refPLACEplaceNoOwning place
childEXTRATABLEqrcodesYesSpecific tables this extra applies to
childEXTRAPRODUCTproductsYesSpecific products this extra applies to
childEXTRAPERIODperiodsYesTime periods when the extra is active

ExtraProduct (EXTRAPRODUCT), ExtraPeriod (EXTRAPERIOD), ExtraTable (EXTRATABLE), and ExtraOption (EXTRAPRODUCTOPT) follow the same pattern as the Offer sub-objects.


Discount

Table: DISCOUNT | Default status: AC

A Discount defines a price reduction that can be applied to tickets. It can target all products or specific ones, and be restricted to time windows.

Key fields:

FieldTypeDescription
namestringDiscount name
typestringDiscount type (percentage, fixed amount, etc.)
valuenumberDiscount value (percentage or fixed amount depending on type)
alldaybooleanAvailable all day
allprdbooleanApplies to all products
cumulativebooleanWhether this discount can be combined with other discounts

Relationships:

DirectionTargetAccessorChild
refPLACEplaceNo
childDISCOUNTPRODUCTproductsYes
childDISCOUNTPERIODperiodsYes

DiscountProduct (DISCOUNTPRODUCT) -- IsValid also checks that the linked product.IsValid.

DiscountPeriod (DISCOUNTPERIOD) -- same time-window structure as other periods.


Ticket Objects

The ticket domain is the transactional heart of the system. A Ticket represents a customer order, and it contains products, offers, extras, discounts, payment information, and audit history.

Ticket

Table: TICKET | Default status: PR (Preparing)

A Ticket is a customer order at a specific table in a place. It progresses through a lifecycle from preparation to payment.

Key fields:

FieldTypeDescription
statusstringLifecycle state (see below)
pricenumberComputed total price
refundnumberTotal refunded amount (default: 0)
taxesnumberComputed tax amount
ordernostringHuman-readable order number
seriesstringInvoice series (default: 'S00000000')
invcenostringInvoice number
elapsednumberTime elapsed since order creation
commentsstringOrder comments/notes
paymentstringPayment method used
givennumberAmount given by the customer (for cash change calculation)
flagsstringState flags bitmask
tmcommitdateTimestamp when the ticket was committed
paidondatePayment timestamp
createddateCreation timestamp (volatile)
updateddateLast update timestamp (volatile)

Ticket lifecycle:

StatusMeaning
PRPreparing -- the order is being built
RDReady -- the order is ready to be served/picked up
PDPaid -- the order has been paid
CCCancelled
DEDeleted

When status is set to RD, the ReadyFlag is automatically set to true.

Payment types (TicketPayment):

ValueMeaning
PAYCASHCash payment
PAYCARDCard payment
PAYLINEOnline payment
PAYMIXEDMixed payment (part cash, part card, etc.)
PAYACCOUNTPayment to a customer account

The payment setter includes backwards compatibility mapping for legacy values (CASHPAY -> PAYCASH, CARDPAY -> PAYCARD, etc.).

Flag system:

PositionFlagDescription
0PrintedFlagThe order has been sent to the printer
1NotifiedFlagThe "order ready" notification was sent to the customer
2ReadyFlagThe order is ready for pickup/service
3ToPayFlagPayment has been requested
4PaidFlagThe order has been paid
5ReopenedFlagThe ticket was reopened after payment

Computed properties:

  • invoice -- returns the first valid TicketInvoice from the invoices list
  • IsCancelled -- true when status is CC or DE
  • IsDeleted -- true when status is DE

Relationships:

DirectionTargetAccessorChildWhat it means
refSESSIONsessionNoSession that created the order
refPLACEplaceNoPlace where the order was made
refQRCODEqrcodeNoTable where the order was placed
refSESSIONpaidbyNoSession that processed the payment
refPAYACCOUNTaccountYesCustomer account used for payment
refACCOUNTINVOICEinvceacYesAccount invoice
childTICKETPRODUCTproductsYesOrdered products
childTICKETOFFERoffersYesApplied offers
childTICKETEXTRAextrasYesApplied extras/surcharges
childTICKETDISCOUNTdiscountsYesApplied discounts
childTICKETMIXEDPAYmixedYesSplit payment entries
childASKWAITERrequestsYesWaiter call requests
childTICKETCHANGEchangesYesChange history (audit trail)
childTICKETINVOICEinvoicesYesGenerated invoices
childTICKETAUDITauditsYesDetailed audit entries

TicketProduct

Table: TICKETPRODUCT | Default status: AC

Represents a specific product line item on a ticket.

Key fields:

FieldTypeDescription
amountnumberQuantity ordered. Setting to 0 automatically sets status = 'DE'
pricenumberUnit price at the time of order
chargenumberTotal charge (amount * price + modifiers)
fixedbooleanWhether the price is fixed (vs. market price entered manually)
refundnumberRefunded amount for this line
taxratenumberTax rate applied
marketnumberMarket price (for IsMarket products)
weightnumberWeight (for IsWeighted products)
commentsstringLine-item comments (e.g. "no onion")
sortnumberDisplay order
flagsstringFlags

Custom behavior:

  • Setting amount = 0 triggers status = 'DE'
  • Setting sort triggers a refresh on the parent ticket
  • IsValid excludes status RC (returned) and DE, and requires amount > 0

Relationships:

DirectionTargetAccessorChild
refTICKETticketNo
refPRODUCTproductNo
refTICKETOFFERofferYes
childTICKETOPTIONoptionsYes

TicketOffer

Table: TICKETOFFER | Default status: AC

When a customer orders an offer/menu, a TicketOffer is created, and its TicketProduct children represent the individual items in the offer.

Key fields:

FieldTypeDescription
amountnumberQuantity
pricenumberOffer price
chargenumberTotal charge
fixedbooleanFixed price flag
refundnumberRefund amount

Relationships:

DirectionTargetAccessorChild
refTICKETticketNo
refOFFERofferNo
childTICKETPRODUCTproductsYes

TicketExtra, TicketDiscount, TicketOption

These objects record the extras, discounts, and options that were applied to a specific ticket or ticket product:

TicketExtra (TICKETEXTRA) -- an extra/surcharge applied to the ticket:

  • taxrate, charge, refund -- pricing at the time of application
  • References: ticket, extra

TicketDiscount (TICKETDISCOUNT) -- a discount applied to the ticket:

  • charge -- the discount amount
  • References: ticket, discount

TicketOption (TICKETOPTION) -- a product option selected for a ticket product:

  • prodopt (aliased as option) -- the ProductOpt that was selected
  • References: product (TicketProduct), option (ProductOpt)

TicketChange

Table: TICKETCHANGE

Records every significant change to a ticket for audit and compliance purposes (particularly important for Spanish fiscal regulations like TicketBAI and VeriFactu).

Key fields:

FieldTypeDescription
reasonstringChange reason code
seriesstringInvoice series at time of change
invoicestringInvoice number at time of change
totalnumberTotal amount at time of change
basenumberTax base at time of change
taxesnumberTax amount at time of change

Change reason codes:

CodeMeaning
FFirst -- initial ticket creation
PPrice changed
IInvoice created
DDestination change
CCancelled

Relationships:

DirectionTargetAccessorChild
refTICKETticketNo
refTICKETCHANGEprevNo
refSESSIONsessionNo
childVERIFACTUverifactusYes
childTICKETBAIticketbaisYes

The prev reference creates a linked list of changes, allowing you to trace the complete history of a ticket.


TicketInvoice and MixedPayment

TicketInvoice (TICKETINVOICE) -- links a ticket to a client for invoicing:

  • References: ticket, client (INVOICEBUSINESS)

MixedPayment (TICKETMIXEDPAY) -- when payment is split across methods:

  • payment -- the payment type for this portion
  • amount -- the amount paid with this method
  • References: ticket

Creating a New DataObject

Follow this step-by-step process to add a new data object to the system.

Step 1: Define the Schema

Create a new file in libs/upp-data/src/modules/model/objects/. Define a const schema with as const:

import { dataService } from '../../data';
import { DataObject, ObjectOptions } from "../base";
import { CreateObject } from "../item";

import { PlaceType } from '../model';

const _schema = {
name: "RESERVATION",
fields: [
{ name: "status", type: "string", default: "AC" },
{ name: "place", type: "objid" },
{ name: "name", type: "string" },
{ name: "date", type: "date" },
{ name: "guests", type: "number" },
{ name: "comments", type: "string" },
{ name: "phone", type: "string" },
],
relate: [
{ direction: ">", target: "PLACE", by: "place", name: "place", reason: "place", child: false, class: PlaceType },
]
} as const;

Step 2: Generate the Base Class

const _ReservationClass = CreateObject(_schema);
if (!_ReservationClass) {
throw new Error("Failed to create BaseClass for '" + _schema.name + "'");
}

Step 3: Create the Concrete Class

export class Reservation extends _ReservationClass {
constructor(objid: string | null, data: dataService, objoptions: ObjectOptions | null = null) {
super(_schema, objid, data, objoptions || {});
}

Copy(store: Array<DataObject> = []): Reservation {
return this._Copy(store) as Reservation;
}

get status(): string | null {
return this['__base__status'];
}

set status(value: string) {
if (this.status == 'DE') {
return;
}
this['__base__status'] = value;
if (this.place) {
if ((this.status == 'DE') && !this.Commited) {
this.place.DelReservation(this);
} else {
this.place.DoRefresh(this.table);
}
}
}

get IsValid(): boolean {
return (!!this.status && (this.status != 'DE'));
}
}

Step 4: Register in the Object Factory

Add the new class to the ObjectFactory so the framework can instantiate it during sync:

ObjectFactory.register('RESERVATION', Reservation);

Step 5: Add the Relationship to the Parent

If Place should have a list of reservations, add a relation to the Place schema:

{ direction: "<", target: "RESERVATION", by: "place", name: "reservation", reason: "reservations", child: true, class: ReservationType }

Step 6: Export and Wire Up

Export the class from the model barrel file and create a type reference for use in other schemas.


Common Patterns

Querying Objects

Objects are accessed through relationships, not direct queries. Start from the root (Place) and navigate:

const activeProducts = place.products.filter(p => p.IsValid);

const pendingTickets = place.tickets.filter(t => t.status === 'PR');

const employeeList = place.employees.filter(e => e.IsValid);

Filtering by Status

The convention IsValid means "not deleted." For more specific checks:

const activeUsers = user.places
.filter(p => p.IsValid)
.filter(p => !p.IsExpired);

const unpaidTickets = place.tickets
.filter(t => t.IsValid && !t.IsCancelled && !t.PaidFlag);

Working with Relations

Setting a 1:1 relation:

ticket.place = myPlace;
ticket.qrcode = myTable;
ticket.session = currentSession;

Adding to a 1:N relation:

place.AddProduct(newProduct);
ticket.AddProduct(newTicketProduct);

Removing from a 1:N relation:

place.DelProduct(oldProduct);

Creating and Committing a Ticket

const ticket = new Ticket(null, dataService);
ticket.place = currentPlace;
ticket.qrcode = currentTable;
ticket.session = currentSession;

const tp = new TicketProduct(null, dataService);
tp.product = someProduct;
tp.amount = 2;
tp.price = someProduct.price;
tp.taxrate = someProduct.taxrate;
ticket.AddProduct(tp);

await ticket.DoCommit();

Editing with Copy/Overwrite

const copy = ticket.Copy();

copy.comments = "Updated comment";
for (const tp of copy.products) {
if (tp.product === someProduct) {
tp.amount = 3;
}
}

const original = copy.Overwrite();
await original.DoCommit();

Waiting for an Object to Load

When you have a reference to an object that may not have received its data from the server yet:

await product.WaitLoaded();
console.log(product.name);

Working with Flags

Both Ticket and Product use a bitmask flags field. Each has named getters/setters:

ticket.ReadyFlag = true;
if (ticket.PaidFlag) { ... }

product.HideApp = true;
if (product.IsMarket) { ... }

Subscribing to Changes

Objects emit observables you can subscribe to:

ticket.OnRefresh.subscribe(tables => {
console.log("Ticket refreshed due to changes in:", tables);
});

ticket.OnCommit.subscribe(() => {
console.log("Ticket committed successfully");
});

ticket.OnChildChanged.subscribe(relationName => {
console.log("Children changed for relation:", relationName);
});