Add a Kitchen Product to a TPV Ticket
Add a kitchen-prepared catalogue product, such as Fish and chips at Harbor Soho, to an open TPV ticket. The line is unpaid and `production_status` is `draft`, shown as Requested, until a later action sends it to production.
In this article
Use this guide when front-of-house adds a prepared product to an editable TPV ticket before sending anything to the kitchen. The action belongs to the ticket detail screen: the operator chooses a product from the catalogue, the ticket updates, and the kitchen send remains separate.
For en-GB documentation, use Harbor Demo Group, Harbor Soho, Olivia Hart and the GB runtime values. Fish and chips is the preferred example because the GB profile marks it with `requires_kitchen=true` and the TPV can show it as Fish.
Before you start
- The operator can open the TPV ticket detail for Harbor Soho.
- A mutable `TPV_TICKET_ID` is available with `TPV_TERMINAL_ID` and `TPV_STAFF_ID`; in the UI the ticket must not be `void` or `closed`.
- The Harbor Soho catalogue includes a kitchen product with `requires_kitchen=true`, preferably Fish and chips shown as Fish.
- Normal TPV access with `tpv:use` needs the ticket to belong to the active `service_session`; `tickets:manage` can bypass that backend session restriction.
- Mutating captures must run in the GB documentation runtime and be cleaned up after the run.
Instructions
- 1.
Open the TPV ticket
Open `/:orgId/:locationId/tpv/ticket?ticketId=:ticketId&terminalId=:terminalId&staffId=:staffId&sourceTab=floor`. The screen should show the ticket detail and the product catalogue in the same operator context.
- 2.
Choose a kitchen product
Select a prepared catalogue item, normally Fish and chips. The product tile may show the short label Fish, while the ticket detail still resolves the full product data from the catalogue.
- 3.
Tap the product once
When `ticketId` exists and `canMutateTicket` is true, the app creates a ticket item with quantity 1, `payment_status` `unpaid` and `production_status` `draft`. If an equivalent draft unpaid line already exists, the UI increments that line instead of creating another row.
- 4.
Check the Requested line and totals
After sync, confirm that the item appears on the ticket as Requested and that totals update. Backend fills missing name, price, tax category and `requires_kitchen` from the product when `product_id` is supplied.
- 5.
Leave kitchen sending for the next action
Do not treat this action as a KDS send. Kitchen output jobs are created only later, when a production-status action moves eligible draft items to `sent`.
Validations and limits
- Product tiles are disabled when `canMutateTicket` is false, covering `void` and `closed` tickets in the UI.
- `POST /api/app/{orgId}/{locationId}/tickets/{ticketId}/items` creates the line; `PUT /api/app/{orgId}/{locationId}/tickets/{ticketId}/items/{itemId}` increments an equivalent editable draft line.
- `product_id` must exist and belong to the same location when scoped; `tax_category_id`, when present or inherited, must belong to the same location.
- If `product_id` cannot fill the details, `name` is required. `unit_price` cannot be negative and `quantity` must be greater than 0 after normalisation.
- Backend defaults `production_status` to `draft` and `payment_status` to `unpaid` when missing. Non-kitchen items cannot use `sent`, `preparing` or `ready`.
- If `discount_amount > 0`, the user needs `tickets:manage` or `tpv:discount`, and the discount cannot exceed the gross item total.
- This add-item action does not create or update `tpv_output_jobs`; kitchen output is queued only when a later production-status change sends items to `sent`.
- Backend `AddTicketItem` explicitly blocks `void`; the UI also blocks `closed`, so avoid using a closed-ticket backend negative case unless documenting that UI/backend difference.
Expected result
The TPV ticket has an unpaid Requested draft line for the kitchen product, or the matching draft line has a higher quantity. Ticket subtotal, tax and total are recalculated, and no kitchen send has happened yet.