Ticket System
The ticket system in upp-data manages the complete lifecycle of sales tickets — from creation, through product selection, pricing, payment, and fiscal document generation. The core classes are Ticket (DataObject), _TicketView (ViewObject), TicketCommit (commit orchestrator), and TicketPrint (printing orchestrator).
Ticket Lifecycle
Status Codes
| Status | Name | Description |
|---|---|---|
PR | Preparation | Initial state when a ticket is created but not yet activated |
AC | Active | Ticket is open and accepting modifications |
RD | Ready | Kitchen/preparation is complete, ticket awaits payment |
PD | Paid | Ticket has been paid (cash, card, or online) |
PP | Paid (account) | Paid via account (pending settlement) |
CC | Cancelled | Ticket has been cancelled |
DE | Deleted | Ticket has been soft-deleted |
SP | Splitter | Visual separator line in the product list (not a real ticket status) |
RC | Recycled | Internal status used during offer recalculation |
UN | Undo | Pending refund on a reopened ticket |
Flags (Bit String)
Tickets carry a bit string in the flags field, with each flag tracking a specific lifecycle event:
| Flag | Meaning |
|---|---|
OrderPrinted | The order has been printed to the kitchen printer |
ReadyNotified | A "ready" notification has been sent |
TicketReady | The ticket is marked as ready for pickup/delivery |
TicketToPay | Payment has been requested |
TicketPaid | Payment has been confirmed |
TicketReopened | The ticket was reopened after being paid |
State Diagram
Status Getters on TicketView
The _TicketView exposes convenient boolean getters that combine status and flags:
get IsRecent(): boolean // updated within AppConstants.recentMs
get IsEmpty(): boolean // no valid products
get IsOpened(): boolean // active, not cancelled, not ready (or ready but unpaid without prepayment)
get IsClosed(): boolean // inverse of IsOpened
get IsReady(): boolean // ReadyFlag is set
get IsToPay(): boolean // ToPayFlag is set
get IsPaid(): boolean // PaidFlag is set
get IsCancel(): boolean // ticket.IsCancelled && no pending refunds
IsOpened has special logic for places with prepayment enabled: a ticket in RD status remains "open" only if place.TicketPrepayment is false.
Adding Products (ToCart)
The ToCart method on _TicketView is the primary API for adding products to a ticket:
ToCart(product: ProductView, options: Array<ProductOptView>, amount: number, info: ToCartInfo | null)
The flow:
- Create TicketProduct: A new
TicketProductis instantiated withticket,product,amount,taxrate(from the product), andstatus='AC'. - Create TicketOptions: For each selected option, a
TicketOptionis created withproduct(the TicketProduct) andoption(the ProductOpt). - Auto-grouping: Before inserting, the method checks if an identical product already exists in the ticket (same product, same options, same offer). If found, the existing product's
amountis incremented instead of creating a new line. - Sort position: New products receive a
sortvalue placing them at the end of the list; existing products are shifted to make room.
The ToCartInfo interface allows passing optional market price, weight, and comments:
interface ToCartInfo {
market?: number | null;
weight?: number | null;
comments?: string | null;
}
Related Product Operations
| Method | Description |
|---|---|
AddOneOf(ticketproduct) | Copies a product (with its options) and adds 1 unit — used for "+1" buttons |
DelOneOf(ticketproduct) | Finds the grouped product with the highest amount and decrements it |
AddSplit() | Inserts a visual splitter (status='SP') to separate product groups |
AddDiscount(discount) | Applies a DiscountView to the ticket, creating a TicketDiscount |
Price Calculation (_PriceInfo)
The priceinfo getter triggers a full price recalculation when the cached value is invalidated (any product, offer, extra, or discount changes). The _PriceInfo(recalculate) method orchestrates the entire pricing pipeline.
Calculation Pipeline
Step Details
1. ClearOffers: Removes the offer reference from all ticket products, resetting them to unaffiliated state.
2. GroupProducts(false) — Merge equal products:
- Splits products into sections (delimited by
SPsplitter products). - Within each section, finds products that are "equal" (same underlying product, same options, same offer) via
IsEqual(). - Merges equal products by summing their amounts and marking the duplicate as
RC(recycled).
3. ApplyOffers: Iterates over all active offers from the place:
- For each offer, calls
SavingAmount(ticketview)to calculate potential savings. - Picks the offer with the greatest savings.
- Creates a
TicketOfferand assigns matching products to it. - If an offer applies to only part of a product's amount, the product is split: the original keeps the unapplied portion, and a new (or recycled) product gets the applied portion.
- Loops until no more offers can improve savings (with a safety limit to prevent infinite loops).
4. GroupProducts(true) — Display grouping: After offers are applied, products are regrouped for display. Products with the same visual identity are linked via DisplayTo for the UI to show them as a single line.
5. ApplyExtras: Checks which extras from the place apply to this ticket:
- Removes extras that no longer apply (e.g., period expired, product removed).
- Adds new extras that now apply (e.g., table-specific extra, all-product extra).
6. ApplyDiscounts: Triggers calcinfo on each TicketDiscountView, which computes the discount charge.
7. Final summation: Using PriceInfo from upp-defs:
- Each product contributes
charge × amountat itstaxrate. - Each extra contributes its
chargeat itstaxrate. - Each discount is subtracted from the total.
- The result updates
ticket.price(total with tax) andticket.taxes.
Product Recycling
During offer recalculation, products may be split and recombined. To avoid unnecessary creation/deletion cycles, the system maintains a _torecycle array:
AddRecycle(product): Marks a product asRCand stores it for potential reuse.GetRecycle(product): Finds a recycled product matching the requested one (viaIsJoin), restores its amount and status, and returns it.DelRecycle(): After recalculation, any remaining recycled products are markedDE.
Payment
Setting the payment property on a TicketView triggers the payment flow:
set payment(value: TicketPayment | null)
Payment types (TicketPayment): 'PAYCASH', 'PAYCARD', 'PAYLINE', 'PAYMIXED', 'PAYACCOUNT'.
When payment is set (non-null):
- If the ticket is not already paid/to-pay:
- Schedules a print operation (deferred to commit) if cash drawer should open (
OpenOnCashorOpenOnCardfrom place config). - Sets
paidby(current session),paidon(current date). - Sets status to
PPfor account payments,PDotherwise.
- Schedules a print operation (deferred to commit) if cash drawer should open (
- Ticket
paymentfield is updated.
When payment is removed (null):
- Clears
paidby,given,paidon,paidin. - Resets status to
AC.
TicketCommit Flow
The TicketCommit class orchestrates the full commit sequence. It is accessed via ticket.Commiter (lazy-initialized).
CommitTicket Sequence
Step-by-Step
-
Status adjustment: If the ticket is in
PR, it moves toAC. If the ticket is empty (no products), it moves toCC. -
_OrderNo()(async): Requests a unique order number from the server. Runs in parallel with subsequent steps. -
_OpenDrawer(): If the print module has a pending drawer-open request (ToOpen), it fires theOpenDrawer()event immediately. -
_TicketChange()(async): Creates the fiscalTicketChangerecord. See TicketChange Numbering below. -
Await order number: Waits for the server to return the order number before proceeding to print.
-
_PrintTicket(): If the print module has a pending print request (ToPrint), it fires thePrintTicket()event. -
_CommitTicket()(async):- If the ticket is a copy (
CopyOfis set), callsOverwrite()to apply the copy's data back to the original. - Pushes the ticket to the transaction (if provided) or calls
DoCommit()directly. - Adds the ticket to the place's and QR code's ticket collections.
- If the ticket is a copy (
-
_LogLocalChange(): Logs the commit activity locally.
TicketChange Numbering
Each paid ticket generates a TicketChange record containing the fiscal series and invoice number. The numbering system uses two enums defined in place.ts:
SerialType (Series)
| Value | Meaning | When used |
|---|---|---|
X | Provisional | Unpaid tickets |
S | Ticket | Paid tickets without invoice |
C | Ticket + Invoice | Paid tickets with invoice at creation |
F | Invoice | Invoice created after payment |
R | Rectification | Price changes on reopened tickets |
InvoiceType (Invoice Number)
| Value | Meaning | When used |
|---|---|---|
X | Provisional | Unpaid tickets |
T | Ticket | Standard ticket number |
C | Ticket + Invoice | Combined ticket/invoice number |
F | Invoice | Standalone invoice number |
R | Rectification | Rectification number |
A | Recapitulative | Recapitulative invoice |
Numbering Logic
The _TicketChange() method determines what fiscal action is needed based on the ticket's state:
First Commit (no previous TicketChange)
| Scenario | Series | Invoice | TicketChange? | Reason |
|---|---|---|---|---|
| Unpaid ticket | X | X | No | Provisional numbering only; no fiscal document |
| Paid without invoice | S | T | Yes | Reason 'F' (first) |
| Paid with invoice | C | C | Yes | Reason 'F' (first) |
Subsequent Commits (ticket reopened)
| Scenario | Series | Invoice | Reason |
|---|---|---|---|
| Price changed | R | R | 'P' (price changed) |
| Invoice created after payment | F | F | 'I' (invoice created) |
| Cancelled | — | — | 'C' (cancelled) — no new TicketChange |
The _InvoiceNumber(reason) method calls place.NextDeviceTicket(serialType, invoiceType) which manages per-device sequential numbering stored in configService.
TicketChange Record Structure
When a TicketChange is created, it stores:
| Field | Source |
|---|---|
ticket | The parent ticket |
prev | Previous TicketChange (null for first) |
session | Current session |
series | Serial number (from NextDeviceTicket) |
invoice | Invoice number (from NextDeviceTicket) |
total | priceinfo.tax.total |
base | priceinfo.tax.base |
taxes | priceinfo.tax.taxes |
TicketBAI / Verifactu Integration
After creating a TicketChange, the commit flow checks if fiscal integrations are enabled on the place. The system to use is configured via place.OptionSend, an enumerated value: '' (none), 'BAI' (TicketBAI), 'VFCT' (Verifactu). Only one system can be active per place.
Verifactu
If place.SendVFTEnabled is true (i.e. OptionSend === 'VFCT'):
- A new
Verifactuobject is created. - Its
changeproperty is set to the newly createdTicketChange. verifactu.Refresh()is called (async) to populate the fiscal data.- If successful, the Verifactu object is added as a child of the
TicketChange.
TicketBAI
If place.SendBAIEnabled is true (i.e. OptionSend === 'BAI'):
- A new
Ticketbaiobject is created. - Its
changeproperty is set to theTicketChange. ticketbai.Refresh()is called to generate the TicketBAI signature.- If successful, it is added as a child of the
TicketChange.
Both integrations run sequentially within the _TicketChange() step, before the actual commit to the server.
TicketPrint
The TicketPrint class manages print requests and cash drawer operations with a deferred execution model.
API
| Method | Parameters | Description |
|---|---|---|
DoPrint(drawer, isauto, oncommit) | drawer: boolean, isauto: boolean, oncommit: boolean | Schedules or executes a print. If oncommit=true, stores flags for deferred execution. |
OpenDrawer() | — | Fires the drawer-open event and resets the request flag |
PrintTicket() | — | Fires the print event and resets all request flags |
Observables
| Observable | Event | Payload |
|---|---|---|
OnPrint | Print requested | PrintInfo { printer?, isauto, drawer } |
OnDrawer | Drawer open requested | PrintInfo { isauto, drawer } |
Deferred Printing
When oncommit=true in DoPrint(), the flags are stored:
_print_requested: A print is pending._dopen_requested: A drawer open is pending._onpay_requested: The print was triggered by a payment.
During CommitTicket, the _OpenDrawer() and _PrintTicket() steps check these flags and fire the events. This ensures the printer and drawer are only activated after the commit data is ready.
Ticket Actions Summary
| Action | Method | Description |
|---|---|---|
| Create ticket | new Ticket(null, data) | Creates a ticket in PR status |
| Add product | ticketView.ToCart(product, options, amount, info) | Adds product with auto-grouping |
| Add one more | ticketView.AddOneOf(ticketproduct) | Copies and adds 1 unit |
| Remove one | ticketView.DelOneOf(ticketproduct) | Decrements the highest-amount group |
| Add splitter | ticketView.AddSplit() | Inserts a visual separator |
| Add discount | ticketView.AddDiscount(discount) | Applies a discount |
| Mark ready | ticketView.OnReady() | Sets status to RD |
| Cancel | ticketView.OnCancel() | Sets status to CC |
| Reopen | ticketView.OnOpen() | Clears payment, sets ReopenedFlag, status AC, deletes UN items |
| Merge tickets | ticketView.OnMerge(other) | Moves products and discounts from another ticket, deletes it |
| Commit | ticketView.OnCommit(payment, transaction?) | Full commit flow via TicketCommit |