Place and Catalog
The place and catalog system in upp-data manages business entities, their product catalogs, pricing modifiers (offers, extras, discounts), and the QR code table infrastructure. The primary classes are Place (DataObject), _PlaceView (ViewObject), and the supporting Catalog and SearchTool utilities.
PlaceView
_PlaceView extends ViewObject<Place> and is the main interface for working with a business location. It wraps the Place DataObject with computed collections, configuration accessors, and management actions.
Initialization
The PlaceView waits for the underlying Place object's OnInitialize event before performing its own initialization. This ensures all related objects (products, offers, etc.) are loaded before the view attempts to compute its collections.
constructor(object: Place, data: dataService) {
super(object, data);
this.place.OnInitialize.pipe(take(1)).subscribe(() => {
this.Initialize();
});
}
Lazy Services
Services are resolved lazily from the Angular injector to avoid circular dependencies:
| Service | Purpose |
|---|---|
adhocService | Ad-hoc HTTP requests (catalog loading, QR code printing) |
clockService | Time management and refresh ticks |
languageService | Translations |
toastService | User notifications |
alertService | Modal alerts |
configService | Local device configuration (ticket numbering) |
Children Collections
All collections are cached and invalidated when the corresponding entity type changes (via RefreshView(targets)). Each collection filters for IsValid objects and wraps them as ViewObjects:
| Getter | Type | Source | Invalidated by |
|---|---|---|---|
user | UserView | place.user via alives.get() | — |
tables | QrCodeView[] | place.qrcodes | QRCODE |
products | ProductView[] | place.products | PRODUCT |
offers | OfferView[] | place.offers | OFFER |
extras | ExtraView[] | place.extras | EXTRA |
discounts | DiscountView[] | place.discounts | DISCOUNT |
employees | EmployeeView[] | place.employees | STAFF |
tickets | TicketView[] | place.tickets (sorted by updated desc) | TICKET |
The products getter has special logic: it skips catalog items that have been overridden (i.e., IsCatalog && next && next.IsValid), showing only the effective version.
ProductsLength returns the count of non-group products (excludes containers).
Refresh Mechanism
The RefreshView(targets: string[]) method is called when any child entity changes. It invalidates the corresponding cached collection and triggers DoRefreshView():
private RefreshView(targets: string[]) {
if (targets.includes('PRODUCT')) {
this._validproducts = null;
this._groupproducts = null;
this.DoRefreshView();
}
// ... similar for QRCODE, OFFER, EXTRA, DISCOUNT, STAFF, TICKET
}
doRegister
Subscribes to the Place object's OnRefresh and each QR code's OnRefresh (for alert tracking). Uses WeakRef(this) to prevent the subscription from keeping the PlaceView alive.
Alert System
Tracks QR code tables that require waiter attention:
| Property | Type | Description |
|---|---|---|
AlertSize | number | Number of tables currently needing attention |
AlertTables | QrCode[] | Array of QrCode objects needing attention |
RefreshAlerts(qrcode) checks qrcode.IsValid && qrcode.Attention to decide whether a table should be in the alert set.
Place Configuration
The PlaceView exposes configuration properties that control ticket and payment behavior. These are intended to read from place-level configuration (currently returning defaults as TODOs):
| Property | Type | Default | Description |
|---|---|---|---|
taxrate | number | AppConstants.defaultTaxRate | Default tax rate for the place |
TicketPrepayment | boolean | false | If true, tickets must be paid before they can be "opened" |
TicketPreparation | boolean | false | If true, tickets go through PR→AC→RD; if false, auto-ready |
CanCash | boolean | true | Cash payment is enabled |
CanCard | boolean | true | Card payment is enabled |
CanAccount | boolean | false | Account payment is enabled |
OpenOnCash | boolean | true | Open cash drawer when paying with cash |
OpenOnCard | boolean | false | Open cash drawer when paying with card |
CashModal | boolean | true | Show cash payment modal |
CardModal | boolean | true | Show card payment modal |
OptionSend | string | '' | Fiscal system to send tickets to: '' (none), 'BAI' (TicketBAI), 'VFCT' (Verifactu). Stored as PLACEOPT optionSend. |
SendVFTEnabled | boolean | — | Verifactu fiscal integration is active (OptionSend === 'VFCT') |
SendBAIEnabled | boolean | — | TicketBAI fiscal integration is active (OptionSend === 'BAI') |
Ticket Numbering
The PlaceView manages per-device sequential numbering for ticket series and invoices, stored locally in configService.
TicketDeviceInfo
interface TicketDeviceInfo {
X: number | null; // last provisional (unpaid) ticket number
T: number | null; // last ticket number
F: number | null; // last invoice number
R: number | null; // last rectificative number
A: number | null; // last ticket (recapitulative) number
C: number | null; // last ticket (with invoice) number
bai: string | null; // last sent ticketbai signature
}
InitializeTickets()
Called during PlaceView initialization:
- Loads the last ticket numbers from the local
configService(theticketstoreproperty). - Calls
_LastServerTickets()to fetch the latest numbers from the server. - Reconciles local and server numbers, taking the greater value for each type.
NextDeviceTicket(serialType, invoiceType): Promise<[string | null, string | null]>
Generates the next serial and invoice number:
- Reads the current counter from
TicketDeviceInfofor the giveninvoiceType. - Increments the counter.
- Formats the serial as
{SerialType}{padded_number}(e.g.,S00000042). - Formats the invoice similarly.
- Saves the updated
TicketDeviceInfotoconfigService. - Returns
[serial, invoice].
Product Catalog
Product Hierarchy
Products form a tree structure through the parent relation:
| Flag | Meaning |
|---|---|
isgroup | Product is a container (folder) for other products |
isfamily | Product has associated Family objects for time-based grouping |
IsCatalog | Product comes from a shared catalog template |
Catalog Items
Products with status='CT' are catalog templates loaded from the server. When a place customizes a catalog product, a new Product is created with next pointing to the original catalog product. The PlaceView's products getter filters out overridden catalog items.
GroupedProducts
The GroupedProducts getter organizes products into display groups:
type GroupedInfo = {
parent: ProductView | null;
children: ProductView[];
};
type CatalogGrouped = GroupedInfo[];
Products are grouped by their parent:
- Root products (no parent or parent is the place) form standalone groups.
- Products whose parent has
isgroup=trueare nested under their parent group. - The result is an array of
GroupedInfo, each with an optional parent and an array of children.
Catalog Loading
get catalog(): Catalog
The Catalog class (lazy-initialized) handles loading product templates from the server via adhocService. It provides products that can be added to the place's catalog.
Backend endpoints:
GET catalog/catalog— Returns the list of demo catalogs (JSON).POST catalog/import— Imports a catalog ZIP via chunked transfer (base64 chunks).resources/catalog/images/— Static path for catalog product images (relative tobaseURL).
Product Search
get search(): SearchTool
The SearchTool (lazy-initialized) provides text search over products and preselects. Used by the UI for product lookup by name or code.
doSearch(criteria: string): ProductView[] | PreselectView[]
Table Management
| Method | Description |
|---|---|
CreateTables(count) | Creates the specified number of new QrCode objects for the place |
DeleteTables(tables) | Marks the specified QrCode objects as DE |
PrintQrCodes(tables) | Sends a request to the server to generate a PDF with QR codes for download |
Offers System
Offers represent special pricing for product bundles (e.g., a menu).
Offer Structure
| Entity | Table | Description |
|---|---|---|
Offer | OFFER | The offer definition: name, price, ismenu, allday |
OfferProduct | OFFERPRODUCT | Links an offer to a product, with grouped count and menuctg |
OfferPeriod | OFFERPERIOD | Defines when the offer is valid (weekdays, time range) |
OfferOption | OFFERPRODUCTOPT | Links an OfferProduct to a specific ProductOpt |
Offer Application
Offers are auto-applied during price calculation (_PriceInfo). The algorithm:
- Collects all active offers from the place.
- For each offer, calculates the potential savings if applied to the current ticket.
- Picks the offer with the greatest savings.
- Creates a
TicketOfferand assigns matching products to it. - Repeats until no more offers improve savings.
An offer's AppliesTo(ticketView) method checks:
- The offer's periods are currently active.
- The ticket contains the required products (matching the offer's
OfferProductentries). - The required product amounts are available.
Menu Offers
When ismenu=true, the offer represents a fixed-price menu. Products in the menu are organized by menuctg (menu category), and each grouped value indicates how many items from that product can be selected.
Extras System
Extras are additional charges applied to tickets automatically (e.g., table service charge, terrace surcharge).
Extra Structure
| Entity | Table | Description |
|---|---|---|
Extra | EXTRA | The extra definition: name, type, price, allday, allprd |
ExtraProduct | EXTRAPRODUCT | Links an extra to a specific product (when allprd=false) |
ExtraPeriod | EXTRAPERIOD | Defines when the extra is valid |
ExtraTable | EXTRATABLE | Links an extra to a specific QR code table |
ExtraOption | EXTRAPRODUCTOPT | Links an ExtraProduct to a specific ProductOpt |
Extra Application
Extras are auto-applied during price calculation:
- For each existing
TicketExtra, check if its sourceExtrastill applies. Remove if not. - For each active
Extrain the place, check if it applies to this ticket. Add if applicable and not already present.
An extra applies when:
- Its periods are currently active (or
allday=true). - It applies to all products (
allprd=true) or the ticket contains a matching product. - It applies to all tables or the ticket's QR code matches an
ExtraTable.
Discounts System
Discounts reduce the ticket total, either as a fixed amount or a percentage.
Discount Structure
| Entity | Table | Description |
|---|---|---|
Discount | DISCOUNT | The discount definition: name, type, value, allday, allprd, cumulative |
DiscountProduct | DISCOUNTPRODUCT | Links a discount to a specific product |
DiscountPeriod | DISCOUNTPERIOD | Defines when the discount is valid |
Discount Properties
| Property | Description |
|---|---|
type | Discount type (percentage or fixed amount) |
value | The discount value |
cumulative | If true, can be combined with other discounts |
allprd | If true, applies to all products; if false, only to linked products |
allday | If true, always active; if false, follows period schedules |
Discount Application
Discounts are manually added by the user (via AddDiscount()), not auto-applied. During price calculation, ApplyDiscounts() triggers calcinfo on each TicketDiscountView, which computes the charge based on the discount's type and value against the current ticket total.
Period Objects (Shared Pattern)
All period objects (OfferPeriod, ExtraPeriod, DiscountPeriod, FamilyPeriod) share the same structure for defining time-based validity:
| Field | Type | Description |
|---|---|---|
status | string | 'AC' for active, 'DE' for deleted |
weekdays | string | Bitmask or comma-separated list of active days |
ini | string | Start time (e.g., '09:00') |
end | string | End time (e.g., '14:00') |
timezone | string | Timezone for evaluation |
Each period object references its parent (offer, extra, discount, or family) and follows the standard status setter pattern with parent notification on 'DE'.
Family System
Families group products with time-based activation, typically for daily menus or special schedules.
Family Structure
| Entity | Table | Description |
|---|---|---|
Family | FAMILY | The family definition, linked to a product with isfamily=true |
FamilyProduct | FAMILYPRODUCT | Links a family to a product or preselect, with sort order |
FamilyPeriod | FAMILYPERIOD | Defines when the family is active |
A product's family getter finds its first valid Family child. When isfamily=true, the product acts as a family container, and its FamilyProduct children define the actual items available during the family's active periods.
FamilyProduct
Each FamilyProduct can link to either:
- A
Productdirectly, or - A
Preselect(which itself groups product options).
IsValid on FamilyProduct checks both its own status and the validity of the linked product/preselect.