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:
- 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.
- Validation. Field types are enforced at runtime. Setting a
numberfield to a string throws an error immediately, catching bugs early instead of corrupting the database. - Relationship management. Parent-child and reference relationships are declared once in the schema. The framework then generates typed getters, setters,
Add/Delmethods, 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:
| Property | Type | Purpose |
|---|---|---|
name | string | The 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:
| Property | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Column name as it appears in the database |
type | string | Yes | One of the supported field types (see below) |
default | any | No | Default value assigned when the object is created locally |
alias | string | No | Alternative 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 |
volatile | boolean | No | When 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:
| Type | TypeScript Type | Stored As (Server) | Description |
|---|---|---|---|
string | string | null | VARCHAR/TEXT | Plain text value |
file | string | null | File path | A urlfile object internally ({ url, b64 }). The getter returns the URL or base64 string; the setter accepts either a URL or a data-URI |
number | number | null | DECIMAL/INT (as string) | Numeric value. Sent as a string to the server, parsed back to number on load |
date | Date | null | MySQL datetime string | JavaScript Date object. Converted to/from MySQL datetime format automatically |
boolean | boolean | null | '0' or '1' | Boolean value. Stored as a single character in the database |
objid | string | null | INT (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:
| Property | Type | Description |
|---|---|---|
direction | '>' or '<' | > means this object references another (outgoing, 1:1). < means other objects reference this one (incoming, 1:N) |
target | string | The database table name of the related object |
by | string | The 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 |
name | string | The accessor name for 1:1 relations (used as the getter/setter property name) |
reason | string | The key used to store the relationship internally. For 1:N relations, this becomes the list accessor name (e.g. products, tickets) |
child | boolean | When 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 |
class | class | Optional 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
DataObjectornull, 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
reasonname (e.g.place.productsreturns an array ofProductobjects) - 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:
- Validates the schema structure (field types, relation directions)
- Creates an abstract class extending
BaseClass<T>with the schema baked in - Defines getters/setters on the prototype for every field, with runtime type validation
- Defines relationship accessors on the prototype (1:1 getters/setters, 1:N list getters and
Add/Delmethods) - 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 insertionobjidisnull-- the server will assign a real ID during sync_uuidis auto-generated -- used as a temporary identifier until the server responds with a realobjid
Loading From the Server (Sync)
When data arrives from the server (during synchronization), the framework calls OnUpdate(info) on the object. This method:
- Checks if the object can be updated (
_CanUpdate) - Compares the incoming data against the last update to skip no-ops
- Calls
_OnUpdate(info)which deserializes database values into typed properties via the schema - Resolves or replaces the object in storage (maps the server-assigned
objidto the local_uuid) - Triggers
DoRefresh()to notify the view layer - Marks
_isLoaded = trueand completes the_waitLoadedsubject
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:
- Validates the value type against the schema
- Calls
patchValue()to check if the value actually changed - 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();
_CommitMap()walks the object and all itschild: truerelations recursively, collecting every object that has_toInsert,_toUpdate, or_toDeleteflags set- For each object, it builds a
CommitChangecontaining: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
data.Commit(changes)queues all changesdata.Flush(force)sends them to the server (immediately ifforce = 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 allchild: truerelations, setting_copyofon each clone to point back to the originalOverwrite()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:
| Status | Meaning |
|---|---|
AC | Active -- the object is live and valid |
PA | Pending -- awaiting activation (common for new users) |
DE | Deleted -- logically deleted (soft delete) |
PR | Preparing -- used for tickets that are being built |
RD | Ready -- used for tickets that are ready to serve |
CC | Cancelled -- used for tickets that were cancelled |
CT | Catalog -- 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:
| Field | Type | Description |
|---|---|---|
status | string | PA = pending activation, AC = active, DE = deleted |
email | string | Login email address. Unique per user |
firstname | string | First name |
lastname | string | Last name |
photo | file | Profile photo (URL or base64) |
phone | string | Phone number |
lang | string | Preferred language code (e.g. "es", "en") |
license | boolean | Whether the user holds a valid license |
Computed properties:
fullname-- concatenatesfirstnameandlastnameIsActive--truewhenstatus == 'AC'IsPending--truewhenstatus == 'PA'
Relationships:
| Direction | Target | Accessor | Child | What it means |
|---|---|---|---|---|
| child | PLACE | places | Yes | Places owned by this user |
| child | STAFF | employees | Yes | Employee roles this user holds (one per place) |
| child | SESSION | sessions | Yes | Active 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:
| Field | Type | Description |
|---|---|---|
status | string | AC = active, DE = deleted |
id | string | A session identifier (used as the UUID override -- _uuid returns this.id) |
deviceid | string | Unique device identifier |
Computed properties:
IsActive--truewhenstatus == 'AC'andupdatedis within the last 600 seconds (10 minutes). This means sessions expire automatically when the device stops sending updates.
Relationships:
| Direction | Target | Accessor | Child | What it means |
|---|---|---|---|---|
| ref | USER | user | No | The logged-in user |
| ref | QRCODE | qrcode | No | The QR code (table) the session is associated with |
| child | FCM | fcm | No | Firebase 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:
| Field | Type | Description |
|---|---|---|
status | string | AC = active, DE = deleted |
alias | string | Display name override for this place |
allow | string | Permission bitmask stored as a string of '0' and '1' characters |
email | string | Contact email (may differ from the user's main email) |
photo | file | Photo 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:
| Position | Permission | Description |
|---|---|---|
| 0 | StaffManage | Can manage employees |
| 1 | OfferManage | Can manage offers |
| 2 | ProductManage | Can manage the product catalog |
| 3 | TableManage | Can manage QR codes/tables |
| 4 | ExtraManage | Can manage extras/surcharges |
| 5 | PlaceManage | Can manage place settings |
| 6 | LicenceManage | Can manage the licence |
| 7 | CanCompliment | Can apply complimentary items |
| 8 | CanAvailable | Can set availability |
| 9 | POSManage | Can use the POS interface |
| 10 | ReportAccess | Can view reports |
| 11 | TillManage | Can manage the cash register |
| 12 | TillOpen | Can open the cash drawer |
| 13 | CanReopen | Can reopen closed tickets |
| 14 | CanModify | Can modify existing tickets |
| 15 | CanCancel | Can cancel tickets |
| 16 | CanReturn | Can process returns |
Use employee.IsAllowedTo(EmployeePermission.ProductManage) to check and employee.SetAllowTo(EmployeePermission.ProductManage, true) to grant.
Relationships:
| Direction | Target | Accessor | Child | What it means |
|---|---|---|---|---|
| ref | USER | user | No | The underlying user account |
| ref | PLACE | place | No | The 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:
| Field | Type | Description |
|---|---|---|
status | string | AC = active |
name | string | Business name |
description | string | Description shown to customers |
photo | file | Main photo. Falls back to a generated avatar from the name |
logo | file | Business logo |
expires | date | License expiration date |
business | string | Type of business |
company | boolean | Whether it is a registered company |
activity | string | Business activity code |
taxid | string | Tax identification number (CIF/NIF) |
phone | string | Contact phone |
Computed properties:
avatar-- generates a placeholder image URL from the place namephoto-- returns the uploaded photo, or falls back to the avatarIsValid--truewhenstatus == 'AC'IsExpired--truewhenexpiresis in the pastInitialize()-- emits onOnInitializeto signal that the place has been fully loaded for the first time
Relationships:
| Direction | Target | Accessor | Child | What it means |
|---|---|---|---|---|
| ref | USER | user | No | The owner of this place |
| ref | ADDRESS | address | Yes | Physical address (geocoded) |
| ref | ADDRESS | fiscal | Yes | Fiscal/billing address |
| child | QRCODE | qrcodes | Yes | Tables/QR codes |
| child | PRODUCT | products | Yes | Product catalog |
| child | OFFER | offers | Yes | Active offers/menus |
| child | EXTRA | extras | Yes | Surcharges |
| child | DISCOUNT | discounts | Yes | Discounts |
| child | STAFF | employees | Yes | Employees |
| child | TICKET | tickets | Yes | All tickets (orders) |
| child | STRIPE | stripes | Yes | Payment provider accounts |
| child | PAYACCOUNT | accounts | Yes | Customer payment accounts |
| child | RASPPI | services | Yes | Connected hardware services |
| child | PLACEOPT | options | Yes | Place configuration options |
| child | PLACELINK | links | Yes | External links |
| child | INVOICEBUSINESS | clients | Yes | Invoice clients |
| child | AUDIT | audits | Yes | Audit trail entries |
| child | CASHCHANGE | cashchanges | Yes | Cash register changes |
| child | PAYMENT | transactions | Yes | Payment 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:
| Field | Type | Description |
|---|---|---|
formatted | string | Full formatted address string (e.g. "Calle Mayor 5, 28001 Madrid") |
door | string | Door/apartment number |
latitude | number | GPS latitude |
longitude | number | GPS longitude |
accuracy | number | Location accuracy in meters |
timezone | string | IANA timezone (defaults to 'UTC') |
street | string | Street name |
number | string | Street number |
zipcode | string | Postal code |
town | string | City/town |
province | string | Province/state |
country | string | Country |
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:
| Field | Type | Description |
|---|---|---|
status | string | AC = active, PA = pending setup |
alias | string | Human-readable name (e.g. "Table 5", "Bar") |
number | number | Table number |
till | objid | Reference to the cash register this table belongs to |
Computed properties:
IsPending--truewhenstatus == 'PA'
Relationships:
| Direction | Target | Accessor | Child | What it means |
|---|---|---|---|---|
| ref | PLACE | place | No | The place this table belongs to |
| ref | PLACEAREA | area | Yes | The area/zone of the place (e.g. terrace, indoor) |
| ref | DRAWITEM | draw | Yes | Visual representation on a floor plan |
| child | ASKWAITER | requests | Yes | Waiter call requests from this table |
| child | EXTRATABLE | extras | Yes | Extras/surcharges specific to this table |
| child | TICKET | tickets | Yes | Active 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:
| Field | Type | Description |
|---|---|---|
status | string | AC = active |
authorization | string | Auth token for the scan session |
qrcode | objid | The QR code that was scanned |
number | number | Table number (copied from QrCode) |
alias | string | Table alias (copied from QrCode) |
Relationships:
| Direction | Target | Accessor | Child |
|---|---|---|---|
| ref | PLACE | place | No |
PrintDevice
Table: PRINTER | Default status: AC
Represents a physical printer connected to the system (typically a thermal receipt printer).
Key fields:
| Field | Type | Description |
|---|---|---|
name | string | Printer display name |
device | string | Device path or address |
type | string | Printer type/protocol |
mt, mr, mb, ml | number | Margins in millimeters (top, right, bottom, left) |
width | number | Paper width in millimeters |
font | number | Font size |
copies | number | Number of copies to print (default: 1) |
updates | boolean | Print order updates |
posudts | boolean | Print POS updates |
oncash | boolean | Print on cash payment |
oncard | boolean | Print on card payment |
bold | boolean | Use bold font |
style | string | Print 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:
| Field | Type | Description |
|---|---|---|
status | string | AC = active, DE = deleted, CT = catalog template |
name | string | Display name |
description | string | Description shown to customers |
photo | file | Product image. Falls back to a generated avatar |
price | number | Base price (tax-exclusive) |
taxrate | number | Tax rate percentage. Defaults to AppConstants.defaultTaxRate if not set |
code | string | Internal product code (e.g. barcode, SKU) |
ctgref | string | Catalog reference -- links to a product template in the global catalog |
type | string | Product type classifier |
sort | number | Sort order within its parent or place |
isgroup | boolean | Whether this product is a visual group/header (not orderable itself) |
generic | boolean | Whether this is a generic/customizable product |
isfamily | boolean | Whether this product has family variants |
flags | string | Bitmask for visibility and pricing flags |
Flag system: Similar to the Employee permission bitmask:
| Position | Flag | Description |
|---|---|---|
| 0 | HideApp | Hide from the customer-facing QR app |
| 1 | HidePdf | Hide from the PDF menu |
| 2 | HideWeb | Hide from the web menu |
| 3 | MarketPrice (IsMarket) | Product does not have a fixed price (price entered at sale time) |
| 4 | Weighted (IsWeighted) | Product is sold by weight |
Computed properties:
avatar/photo-- fallback photo generation from the product nametaxrate-- returnsAppConstants.defaultTaxRatewhen no explicit rate is setfamily-- returns the first validFamilyfrom thefamilieslist
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:
| Direction | Target | Accessor | Child | What it means |
|---|---|---|---|---|
| ref | PLACE | place | No | The place this product belongs to |
| ref | PRODUCT | parent | Yes | Parent product in a hierarchy |
| ref | PRODUCT | next | No | Next product in display order |
| child | CATEGORY | categories | Yes | Option categories for this product |
| child | PRESELECT | selects | Yes | Preset option combinations |
| child | FAMILY | families | Yes | Product 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:
| Field | Type | Description |
|---|---|---|
name | string | Category name (e.g. "Size", "Toppings") |
sort | number | Display order |
type | string | Category type |
fxmin | number | Minimum number of selections required |
fxmax | number | Maximum 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:
| Direction | Target | Accessor | Child | What it means |
|---|---|---|---|---|
| ref | PRODUCT | product | No | The product this category belongs to |
| child | PRODUCTOPT | options | Yes | The options within this category |
| child | CATEGORYDEP | depends | Yes | Dependencies 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:
| Field | Type | Description |
|---|---|---|
name | string | Option name |
price | number | Additional price for this option |
description | string | Description |
sort | number | Display order within the category |
flags | string | Option flags |
ctgref | string | Catalog reference (volatile -- not persisted) |
Relationships:
| Direction | Target | Accessor | Child |
|---|---|---|---|
| ref | PRODUCT | product | No |
| ref | CATEGORY | category | No |
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:
| Field | Type | Description |
|---|---|---|
name | string | Preselect name (e.g. "Classic Combo") |
photo | file | Photo |
description | string | Description |
code | string | Internal code |
sort | number | Display order |
Computed properties:
IsMarket,IsWeighted-- delegated from the parentproduct
Relationships:
| Direction | Target | Accessor | Child |
|---|---|---|---|
| ref | PRODUCT | product | No |
| child | PRESELECTOPT | options | Yes |
Family, FamilyProduct, FamilyPeriod
The Family system allows a product to have different variants or to be available only during certain time periods.
Family (FAMILY):
| Field | Type | Description |
|---|---|---|
allday | boolean | Whether the family is available all day |
| Direction | Target | Accessor | Child |
|---|---|---|---|
| ref | PRODUCT | product | No |
| child | FAMILYPRODUCT | products | Yes |
| child | FAMILYPERIOD | periods | Yes |
FamilyProduct (FAMILYPRODUCT) -- links a family to specific products and preselects:
| Field | Type | Description |
|---|---|---|
sort | number | Display order |
Its IsValid getter checks both product.IsValid and preselect.IsValid.
| Direction | Target | Accessor | Child |
|---|---|---|---|
| ref | FAMILY | family | No |
| ref | PRESELECT | preselect | No |
| ref | PRODUCT | product | No |
FamilyPeriod (FAMILYPERIOD) -- defines when a family is available:
| Field | Type | Description |
|---|---|---|
weekdays | string | Bitmask of active weekdays |
ini | string | Start time |
end | string | End time |
timezone | string | Timezone 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:
| Field | Type | Description |
|---|---|---|
name | string | Offer name (e.g. "Lunch Menu") |
description | string | Description |
photo | file | Photo |
price | number | Offer price |
ismenu | boolean | Whether this is a structured menu (with categories like starters, mains, desserts) |
allday | boolean | Whether the offer is available all day |
Relationships:
| Direction | Target | Accessor | Child | What it means |
|---|---|---|---|---|
| ref | PLACE | place | No | Owning place |
| child | OFFERPRODUCT | products | Yes | Products included in the offer |
| child | OFFERPERIOD | periods | Yes | Time periods when the offer is active |
OfferProduct (OFFERPRODUCT) -- a product within an offer:
| Field | Type | Description |
|---|---|---|
grouped | number | Grouping count (for menus: how many of this item are included) |
menuctg | string | Menu category label (e.g. "Starters", "Mains") |
OfferOption (OFFERPRODUCTOPT) -- preselected options for an offer product:
| Direction | Target | Accessor |
|---|---|---|
| ref | OFFERPRODUCT | product |
| ref | PRODUCTOPT | option |
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:
| Field | Type | Description |
|---|---|---|
name | string | Extra name |
description | string | Description |
photo | file | Photo |
type | string | Extra type |
price | number | Price |
allday | boolean | Available all day |
allprd | boolean | Applies to all products (vs. specific product selection) |
Relationships:
| Direction | Target | Accessor | Child | What it means |
|---|---|---|---|---|
| ref | PLACE | place | No | Owning place |
| child | EXTRATABLE | qrcodes | Yes | Specific tables this extra applies to |
| child | EXTRAPRODUCT | products | Yes | Specific products this extra applies to |
| child | EXTRAPERIOD | periods | Yes | Time 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:
| Field | Type | Description |
|---|---|---|
name | string | Discount name |
type | string | Discount type (percentage, fixed amount, etc.) |
value | number | Discount value (percentage or fixed amount depending on type) |
allday | boolean | Available all day |
allprd | boolean | Applies to all products |
cumulative | boolean | Whether this discount can be combined with other discounts |
Relationships:
| Direction | Target | Accessor | Child |
|---|---|---|---|
| ref | PLACE | place | No |
| child | DISCOUNTPRODUCT | products | Yes |
| child | DISCOUNTPERIOD | periods | Yes |
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:
| Field | Type | Description |
|---|---|---|
status | string | Lifecycle state (see below) |
price | number | Computed total price |
refund | number | Total refunded amount (default: 0) |
taxes | number | Computed tax amount |
orderno | string | Human-readable order number |
series | string | Invoice series (default: 'S00000000') |
invceno | string | Invoice number |
elapsed | number | Time elapsed since order creation |
comments | string | Order comments/notes |
payment | string | Payment method used |
given | number | Amount given by the customer (for cash change calculation) |
flags | string | State flags bitmask |
tmcommit | date | Timestamp when the ticket was committed |
paidon | date | Payment timestamp |
created | date | Creation timestamp (volatile) |
updated | date | Last update timestamp (volatile) |
Ticket lifecycle:
| Status | Meaning |
|---|---|
PR | Preparing -- the order is being built |
RD | Ready -- the order is ready to be served/picked up |
PD | Paid -- the order has been paid |
CC | Cancelled |
DE | Deleted |
When status is set to RD, the ReadyFlag is automatically set to true.
Payment types (TicketPayment):
| Value | Meaning |
|---|---|
PAYCASH | Cash payment |
PAYCARD | Card payment |
PAYLINE | Online payment |
PAYMIXED | Mixed payment (part cash, part card, etc.) |
PAYACCOUNT | Payment to a customer account |
The payment setter includes backwards compatibility mapping for legacy values (CASHPAY -> PAYCASH, CARDPAY -> PAYCARD, etc.).
Flag system:
| Position | Flag | Description |
|---|---|---|
| 0 | PrintedFlag | The order has been sent to the printer |
| 1 | NotifiedFlag | The "order ready" notification was sent to the customer |
| 2 | ReadyFlag | The order is ready for pickup/service |
| 3 | ToPayFlag | Payment has been requested |
| 4 | PaidFlag | The order has been paid |
| 5 | ReopenedFlag | The ticket was reopened after payment |
Computed properties:
invoice-- returns the first validTicketInvoicefrom theinvoiceslistIsCancelled--truewhen status isCCorDEIsDeleted--truewhen status isDE
Relationships:
| Direction | Target | Accessor | Child | What it means |
|---|---|---|---|---|
| ref | SESSION | session | No | Session that created the order |
| ref | PLACE | place | No | Place where the order was made |
| ref | QRCODE | qrcode | No | Table where the order was placed |
| ref | SESSION | paidby | No | Session that processed the payment |
| ref | PAYACCOUNT | account | Yes | Customer account used for payment |
| ref | ACCOUNTINVOICE | invceac | Yes | Account invoice |
| child | TICKETPRODUCT | products | Yes | Ordered products |
| child | TICKETOFFER | offers | Yes | Applied offers |
| child | TICKETEXTRA | extras | Yes | Applied extras/surcharges |
| child | TICKETDISCOUNT | discounts | Yes | Applied discounts |
| child | TICKETMIXEDPAY | mixed | Yes | Split payment entries |
| child | ASKWAITER | requests | Yes | Waiter call requests |
| child | TICKETCHANGE | changes | Yes | Change history (audit trail) |
| child | TICKETINVOICE | invoices | Yes | Generated invoices |
| child | TICKETAUDIT | audits | Yes | Detailed audit entries |
TicketProduct
Table: TICKETPRODUCT | Default status: AC
Represents a specific product line item on a ticket.
Key fields:
| Field | Type | Description |
|---|---|---|
amount | number | Quantity ordered. Setting to 0 automatically sets status = 'DE' |
price | number | Unit price at the time of order |
charge | number | Total charge (amount * price + modifiers) |
fixed | boolean | Whether the price is fixed (vs. market price entered manually) |
refund | number | Refunded amount for this line |
taxrate | number | Tax rate applied |
market | number | Market price (for IsMarket products) |
weight | number | Weight (for IsWeighted products) |
comments | string | Line-item comments (e.g. "no onion") |
sort | number | Display order |
flags | string | Flags |
Custom behavior:
- Setting
amount = 0triggersstatus = 'DE' - Setting
sorttriggers a refresh on the parent ticket IsValidexcludes statusRC(returned) andDE, and requiresamount > 0
Relationships:
| Direction | Target | Accessor | Child |
|---|---|---|---|
| ref | TICKET | ticket | No |
| ref | PRODUCT | product | No |
| ref | TICKETOFFER | offer | Yes |
| child | TICKETOPTION | options | Yes |
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:
| Field | Type | Description |
|---|---|---|
amount | number | Quantity |
price | number | Offer price |
charge | number | Total charge |
fixed | boolean | Fixed price flag |
refund | number | Refund amount |
Relationships:
| Direction | Target | Accessor | Child |
|---|---|---|---|
| ref | TICKET | ticket | No |
| ref | OFFER | offer | No |
| child | TICKETPRODUCT | products | Yes |
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 asoption) -- theProductOptthat 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:
| Field | Type | Description |
|---|---|---|
reason | string | Change reason code |
series | string | Invoice series at time of change |
invoice | string | Invoice number at time of change |
total | number | Total amount at time of change |
base | number | Tax base at time of change |
taxes | number | Tax amount at time of change |
Change reason codes:
| Code | Meaning |
|---|---|
F | First -- initial ticket creation |
P | Price changed |
I | Invoice created |
D | Destination change |
C | Cancelled |
Relationships:
| Direction | Target | Accessor | Child |
|---|---|---|---|
| ref | TICKET | ticket | No |
| ref | TICKETCHANGE | prev | No |
| ref | SESSION | session | No |
| child | VERIFACTU | verifactus | Yes |
| child | TICKETBAI | ticketbais | Yes |
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 portionamount-- 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);
});