declare global { /** * A coupon as it was redeemed onto an order — the grant frozen at * consumption, plus the money it actually took. * * Deliberately NOT a reference to the live `Coupon` row: that row keeps * changing (its caps move, it gets disabled, its terms are edited), and an * order must record what was granted at the till, not what the code means * today. */ interface OrderCoupon { /** Normalized upper-case, matching the `Coupon` row's own `code`. */ code: string; /** The grant's unit, as frozen at redemption. */ type: 'percent' | 'amount'; /** The GRANT, in the unit `type` names — NOT money. See `amount`. */ value: number; /** * The MONEY this coupon took off this order, in the order's currency, * after every clamp (`maxDiscountAmount`, and the cart's gross). */ amount: number; /** ms epoch the redemption was consumed — checkout, never apply. */ redeemedAt: number; } interface Order { storeId: string; orderId: string; customerId: string; customer: Partial; createdAt: number; /** * `YYYYMMDD` in Buenos Aires time (e.g. `20260810`), stamped at creation * and never rewritten — the sort key of the `PK-dated` index the per-day * order queries run on. Required rather than optional: rows predating the * field were backfilled, and that one-shot migration has since been * removed as spent. */ dated: number; updatedAt?: number; readyAt?: number; deliveredAt?: number; deliveredDate?: number; /** * FULFILMENT axis of the two-axis order state model — where the goods * are. See {@link OrderFulfilmentStatus} for the derivation rule and * `ORDER_FULFILMENT_TRANSITIONS` for the legal moves. * * ⚠️ OPTIONAL, and permanently so. Every ORDER row written before this * field existed carries no status-shaped attribute at all, and this * platform is forward-only — nothing will ever backfill them. Absent is * therefore a LEGAL, PERMANENT state of the data, not a migration gap: * a reader must fall back to deriving from `deliveredAt`/`readyAt`, and * every write-time guard must tolerate absence, i.e. * `attribute_not_exists(fulfilmentStatus) OR fulfilmentStatus = :expected` * — the idiom the delivery and disable writers already use for the * timestamps themselves. * * ⚠️ Spelled with ONE `l` throughout (`fulfilment`, `Fulfilment`). */ fulfilmentStatus?: OrderFulfilmentStatus; /** * FINANCIAL axis — what the customer still owes on this order. See * {@link OrderFinancialStatus}; legal moves in * `ORDER_FINANCIAL_TRANSITIONS`. * * ⚠️ Optional and permanently so, exactly as `fulfilmentStatus` above, * and with the same absent-tolerant guard requirement. * * ⚠️ This is a CACHE of a figure the ledger owns. The `ACCOUNT` * partition is authoritative for what is owed; this field is the * ledger's verdict stamped onto the order so a list query does not have * to replay a customer's whole account to colour one row. When the two * disagree, the ledger is right and this field is stale. */ financialStatus?: OrderFinancialStatus; /** * Append-only audit trail of every state move on either axis, oldest * first. * * ⚠️ Appended with `SET statusHistory = list_append(if_not_exists(statusHistory, :empty), :entry)` * — NEVER read-modify-write. Two writers advancing different axes of the * same order in the same instant is ordinary (an operator marks it * delivered while a payment webhook lands), and a read-modify-write * silently drops one of the two entries with no error anywhere. * `if_not_exists` is what makes the first append work on the whole * back-catalogue, none of which carries the attribute. * * NOT capped, unlike `returns` — an order moves state a handful of * times, so the list stays small. The one path that can oscillate is * the financial axis under repeated payment link/unlink; a writer that * finds itself appending in a loop is the bug, not the list. */ statusHistory?: OrderStatusEntry[]; /** * When this order's lines were applied to inventory — the `Product.stock` * decrement and the `SALE#` rows. * * ⚠️ **A WRITE-ONCE LATCH, not a timestamp anyone reads for time.** It exists * so a channel whose order write can fire more than once for the same order * can still move stock exactly once: the applying transaction stamps it under * `attribute_not_exists(stockAppliedAt)`, so a replay loses the condition and * the whole transaction — decrement included — is rejected as a unit. * * ⚠️ Written ONLY by a channel that needs the latch. The ordinary counter and * storefront checkouts mint an order exactly once by construction and do NOT * stamp it, so **absent does not mean "stock was never applied"** — it is not * a field to gate a report or a repair sweep on. Today the one writer is the * MercadoLibre `orders_v2` ingest, whose upsert runs on every notification for * an order's whole life. * * ⚠️ Optional and permanently so, like every other field added after rows * existed — this platform is forward-only and nothing backfills. */ stockAppliedAt?: number; comments?: string; currency: string; currencyValue?: number; currencyValueAt?: number; paymentMethod: number; /** * Expected payment due date, Unix ms. Nothing computes it from payment * terms — it is operator-declared. * * Feeds AFIP `FchVtoPago` at invoice time, which ARCA requires on every * service voucher (Concepto 2/3) and on every FCE regardless of Concepto * (code 10163). The FCE request's own `fchVtoPago` outranks it; absent * both, the voucher falls back to the invoice date, which is the * behaviour every goods order has always had. */ dueDate?: number; /** * The service period this order bills for, Unix ms — the window that * reaches ARCA as `FchServDesde` / `FchServHasta`. * * Exists because a repair received in March and delivered in June is a * genuine multi-month service, and reporting it as a same-day June * service misstates the invoice. Absent on an ordinary goods order, which * then reports same-day exactly as before — the fields are additive and * change nothing for a sale that has no service period. * * Operator-declared, and validated on every write that can carry them: * `POST /orders` (both its insert and its update leg) rejects a * half-declared window and an end preceding its start, with a 400. * `mode: 'edit'` is strict and cannot carry them at all. * * They are therefore revisable before invoicing, which is what an operator * correcting a mistyped intake date needs. Revising them AFTER an invoice * exists does not rewrite it: the invoice stamps its own copy of the window * at issue time, so the issued voucher and the order can legitimately * disagree once someone edits the order. * * These are the SOURCE. `Invoice.serviceStartDate`/`serviceEndDate` are * the copy stamped at issue time; the ARCA drain rebuilds a pending * voucher from the live Order, so anything that must survive a * contingency drain belongs here rather than on the invoice. */ serviceStartDate?: number; serviceEndDate?: number; /** * The `ServiceOrder` this order bills for, set when a delivered service * order mints its order. Absent on every ordinary goods order. * * An order carrying this holds the repair as two `isService: true` product * lines — labour and parts — priced so the PAIR SUMS to the service * order's own `total`. ⚠️ They are NOT priced off `laborCost` / * `partsCost`: those stay GROSS, and the ticket's absolute `discount` is * netted proportionally across the two lines at mint, with the second * derived from the first so rounding cannot leave the pair a centavo off * a fiscal document. Those lines are already-consumed work: the parts left * the shelf when the technician fitted them, so the order's stock * deduction skips a service line rather than moving inventory a second * time. * * `Order.discount` is a percentage and does NOT reach those lines — and * must not, since the two units cannot be added and converting between * them does not round-trip. The service order is the sole owner of its own * total (its own `discount` is already spent in the lines above), so the * figure invoiced is the figure the customer agreed to. The percentage * still applies normally to any goods bought in the same visit. */ serviceOrderId?: string; /** * The `ServiceOrder` the ticket in `serviceOrderId` was a rework OF, copied * forward verbatim at mint. Absent unless that ticket is itself a rework. * * It is the parent SERVICE ORDER, not the parent order — a ticket-to-ticket * pointer, exactly as `ServiceOrder.parentServiceOrderId` stores it. The * resolved order was considered and rejected: the parent SERVICE# row * already carries its own `orderId` and `invoiceId`, so this is one point * read from the parent's money either way, and resolving at mint would add * a read inside the delivery transaction plus a "parent never delivered, so * it has no order" branch on a path whose only failure maps to * `409 SERVICE_ORDER_STATUS_CHANGED`. * * A statutory warranty rework (Ley 24.240 art. 23) deliberately does NOT * reopen the parent — that would destroy the parent's cycle time and its * invoice linkage. This field is what keeps the rework's paperwork joined * to the original repair without reopening anything. The sparse GSI * `PK-parentServiceOrderId` answers "every rework of parent X" directly — * keyed on the SERVICE partition and served by * `GET /services?parentServiceOrderId=`. */ parentServiceOrderId?: string; /** * FK into `Store.deliveryMethods`. OPTIONAL, matching `Customer.deliveryMethod` * — `_deliverOrder.ts`'s mint already omits it when the store's catalog * resolves no canonical pickup method, and `orders/_post.ts`'s write-boundary * validation has always modelled it that way (`z.number().optional()`). A * required type here disagreed with what the api actually produces. * * ⚠️ Same reader contract as `Customer.deliveryMethod`: resolve against the * store's catalog and tolerate a miss, and don't read the id as meaningful on * its own — method ids are per-catalog ordinals. */ deliveryMethod?: number; invoiceMethod?: { condFiscal: number; condFiscalName: string; cuit: string; razonSocial: string; /** * Explicit per-order ARCA receptor identity, decoupled from condFiscal * (ARCA DocTipo: 80 = CUIT, 96 = DNI, 99 = Consumidor Final — a * SEPARATE axis from condFiscal, sharing 96 only by coincidence). * When present, the AFIP invoice builder uses these directly for the * receptor instead of deriving from condFiscal. */ docType?: number; docNumber?: string; }; cost: number; total: number; /** * Order-level discount as a PERCENTAGE (0–100), applied per line over the * GROSS line prices. Not money — the cart's `totals.discount` is the * absolute figure, and the two are different units. * * ⚠️ When a cart coupon AND an operator percentage are both present they * COMPOSE, multiplicatively: * * ``` * discount = 100 × (1 − (1 − coupon/100) × (1 − operator/100)) * ``` * * NOT added. The two cuts stand on different bases — 30% off, then 20% off * the remainder, is 44%, not 50% — so adding them overstates the discount * and two legal grants can sum past 100. And NOT "operator wins": the * coupon's redemption is consumed whether or not its money survives, so * dropping the coupon's half would spend a shopper's coupon and charge them * full price. * * The full order of operations is `line discounts → coupon → order * percentage`, each stage taken on what the previous one left. * * ⚠️ Composed on the CREATE leg only. An update reaches no cart, so an * existing order's `discount` stands exactly as it was. * * ⚠️ A client must NOT re-derive this. The server applies the composed * percentage per line over gross prices; computing `grandTotal − * grandTotal × (operator/100)` on an already-netted total agrees only up to * per-line rounding, so a client-side preview disagrees with the printed * receipt by centavos rather than merely duplicating it. */ discount: number; /** * The cart-level coupon(s) redeemed to mint this order, frozen at the * moment the redemption was CONSUMED. Absent on every order minted * without one, and on every order written before this field existed. * * ⚠️ **An ARRAY even though a cart holds at most one coupon**, and that is * not speculative generality. The asymmetry is what decides it: a CART is * ephemeral, so singular is right there and renaming a live cart field is * a patch bump plus a mechanical sweep. An ORDER ROW IS IMMUTABLE HISTORY, * and this repo is forward-only with no backfills — reshaping a singular * field into an array later means a migration nobody will run, against * rows nobody can rewrite. The array costs nothing now and removes the * only expensive half of a future stacking decision. **Today it holds at * most one entry.** * * ⚠️ **`amount` here is the money the coupon actually took off THIS * order** — not the coupon's `value`, which is the grant in the unit * `type` names. A percent coupon whose `value` is 15 may have an `amount` * of 4 500. Reading `value` as money is the mistake this pair exists to * prevent, and both are `number`, so nothing typechecks it for you. * * ⚠️ **Do not derive "was a coupon used" by arithmetic on `total`.** * Subtracting the line cuts from the item sum will silently absorb * shipping and tax the moment those reserved `CartTotals` slots are * populated. This field is the record; the arithmetic is not. * * A RETURN may report that a redemption was taken against this order. It * must NOT release one — releasing is the failure mode that vendors with * an explicit session-lock primitive exist to manage, and there is no such * primitive here. */ coupons?: OrderCoupon[]; orderPrinted?: boolean; tagPrinted?: boolean; /** * Server-derived ms epoch, stamped by the WSS `ack` handler on an * `ACK_PRINTED` correlating to this row's CURRENT `printJobId`. * Absent = not confirmed printed — never seeded to `0`, unlike * `readyAt`/`deliveredAt`. Cleared on every reprint. Distinct from * `orderPrinted`, which is stamped optimistically at dispatch. */ printedAt?: number; /** BE-minted pointer to the most recent print dispatch. Last-write-wins on reprint. */ printJobId?: string; invoices?: Partial[]; /** * Bounded, embedded projections of this order's returns, capped at 50. * The canonical rows live under `RETURN#${storeId}`. */ returns?: ReturnSummary[]; /** * Customer self-cancellation, DISTINCT from `disabled` (operator * soft-delete) — `disabled` additionally stamps * `readyAt`/`deliveredAt`/`deliveredDate`, which cancellation must NOT do. * All four fields are absent on a non-cancelled order. */ cancelledAt?: number; /** Who cancelled: the customerId for a self-cancellation, else the userId. */ cancelledBy?: string; cancellationSource?: OrderCancellationSource; /** Bounded free text supplied by the canceller. */ cancellationReason?: string; mercadolibreCreditNote?: { creditNoteNumber?: number; emittedAt?: number; status?: "emitted" | "skipped" | "failed"; reason?: string; claimId?: string; source?: 'auto' | 'manual'; }; disabled?: boolean; items: Partial[]; rating?: number; comment?: string; surveyDate?: number; deliveryAddress?: { fullName: string; address: string; phone: string; city: string; province: string; postalCode: string; }; mercadopago?: { dynamicQr?: { qrData: string; inStoreOrderId: string; posId: string; externalReference: string; amount: number; currency: string; expiresAt: number; createdAt: number; }; }; linkedPayments?: Record; channel?: OrderChannel; mercadolibre?: OrderMercadolibre; } type OrderChannel = 'meli'; /** * Who initiated a cancellation. `customer` is the storefront self-service * path; `operator` is reserved for a future back-office cancellation, * still distinct from `disabled`. */ type OrderCancellationSource = 'customer' | 'operator'; /** * Machine-readable reason an order is locked against a mutation — the * payload of `409 ORDER_LOCKED` / `409 ORDER_CANCELLATION_LOCKED`, and the * gate a return checks before it starts. Clients map these to copy; never * user-facing strings themselves. * * ⚠️ Every predicate is a `> 0` test, NOT a presence test. `POST /orders` * stamps `readyAt`/`deliveredAt`/`deliveredDate` at `0` on creation, so an * `attribute_exists` check matches every order ever created and silently * inverts the lock. The api's `assessLock` is the reference. * * Evaluated in this order, first match wins: * - `ready` — `readyAt > 0`. * - `delivered` — `deliveredAt > 0` or `deliveredDate > 0`. * - `disabled` — `disabled === true` (soft-delete; NOT cancellation). * - `invoiced` — `invoices[]` holds a voucher that is not `rejected`/`voided`. * A voucher with no `fiscalStatus` at all is legacy and counts as live. * - `payment-linked` — `linkedPayments` is non-empty. The platform never * unlinks or refunds a provider payment on the operator's behalf. * - `cancelled` — `cancelledAt` is stamped. */ type OrderLockReason = 'ready' | 'delivered' | 'disabled' | 'invoiced' | 'payment-linked' | 'cancelled'; /** * FULFILMENT axis of the order state model: where the goods are. * * The frontend has run this axis in production for years with no server * field to lean on, deriving it from the timestamps. This union names what * it already computes, so a consumer can switch from the derivation to the * stored field WITHOUT any bucket count moving. * * ⚠️ **DERIVATION, when the field is absent — `deliveredAt` OUTRANKS * `readyAt`, in that order, and nothing else participates:** * * ``` * deliveredAt > 0 -> 'delivered' * else readyAt > 0 -> 'ready' * else -> 'pending' * ``` * * The order of those two tests is load-bearing, not stylistic. The * lifecycle is monotone, so a delivery stamp outranks a MISSING ready * stamp: an order delivered without ever being marked ready is * `delivered`, never `pending`. Testing `readyAt` first is a bug the app * already had and fixed — it sent delivered orders back to the first * bucket on one screen while another screen counted them under the last. * * ⚠️ The api's `assessLock` tests `readyAt` FIRST. That is correct for a * LOCK reason (both answers lock the order, so nothing observable differs) * and WRONG as a derivation of this axis. Do not reuse it here. * * ⚠️ Every predicate is `> 0`, never a presence test: `POST /orders` stamps * `readyAt`/`deliveredAt`/`deliveredDate` at `0` on creation, so * `attribute_exists` matches every order ever written and inverts the rule. * * ⚠️ `deliveredAt` (Unix ms) is the authoritative delivery input, NOT * `deliveredDate` (`YYYYMMDD`). The two are written and cleared together by * every delivery writer, so they agree today; `deliveredDate` exists for * same-calendar-day reconciliation of the balance movement, and deriving * this axis from it would couple the state model to that accounting rule. * * **`disabled` is NOT a value here and must never become one.** A * soft-deleted order still occupies its fulfilment bucket and is still * rendered; it drops out of the MONEY instead, through the net-total rule. * Folding the flag into this union would silently empty operator bucket * counts on screens that work today. (Be aware of the api's own wrinkle: * its soft-delete writer stamps `readyAt`/`deliveredAt`/`deliveredDate` * with real timestamps as a hiding mechanism, so a disabled order DERIVES * as `delivered` and its re-enable zeroes all three back to `pending`. * Those two writes rewrite the underlying timestamps wholesale — they * RECOMPUTE this field rather than requesting a transition, and are the one * documented exemption from the transition table below.) * * **`cancelled` is NOT a value here either — cancellation is a THIRD * axis**, carried by `cancelledAt`/`cancelledBy`/`cancellationSource`. It * is orthogonal by construction: a cancelled order keeps whatever * fulfilment state it had reached, and the two flags are already tested * separately everywhere (`cancelledAt` is deliberately distinct from * `disabled`, and the customer-cancellation handler short-circuits on it * before any lock assessment runs). Collapsing it into this union would * both destroy that information and move a cancelled order out of the * bucket the app still shows it in. * * `not_delivered` is the one value NOT derivable from today's rows: it * means a delivery ATTEMPT failed or was cancelled by the carrier, which no * timestamp can express and which is currently dropped on the floor by the * MercadoLibre shipment sync. Adding it is therefore additive — no existing * row derives it, so no bucket moves. Its wire spelling matches the * marketplace's own `not_delivered` shipment status. */ type OrderFulfilmentStatus = 'pending' | 'ready' | 'delivered' | 'not_delivered'; /** * FINANCIAL axis of the order state model: what the customer still owes on * this order. * * English values for a verdict the frontend renders in Spanish. The * bucketing must agree with the FIFO allocation the app already runs over * the customer's whole `ACCOUNT` ledger: * * ``` * 'paid' <- 'Pagada' debit - paid <= epsilon * 'partial' <- 'Parcial' still owing, and something has been paid * 'pending' <- 'Pendiente' still owing, and nothing has been paid * ``` * * ⚠️ `epsilon` is half a display unit, derived from the store's * `priceDecimals`: a shortfall the store cannot even render is not a * shortfall. An exact-zero test disagrees with the app on every order whose * FIFO allocation leaves a sub-centavo residual, and puts a live "collect" * button next to a paid chip. * * ⚠️ A zero-debit order is `paid`, not `pending` — `0 - 0 <= epsilon`. An * overpaid order is also `paid`; the surplus is the customer's balance, not * this order's business. * * ⚠️ One order can carry debits in more than one denomination. These three * values describe the PRIMARY (most open) slice only, exactly as the app's * per-document status does — a consumer asking "is this settled?" must * consult the ledger's open-balance answer, not just this field. * * ⚠️ **This axis is REVERSIBLE and not monotone.** A payment can be * unlinked from an order and a provider payment can be refunded, both of * which move `paid` back to `partial` or `pending`; a credit note or return * moves it the other way by shrinking the debit. See * `ORDER_FINANCIAL_TRANSITIONS`, which has no terminal state for exactly * this reason. */ type OrderFinancialStatus = 'pending' | 'partial' | 'paid'; /** * One entry in an order's append-only status history — a discriminated * union over the axis that moved, because `pending` is a member of BOTH * status unions and `status` alone therefore cannot tell you which axis an * entry describes. */ type OrderStatusEntry = OrderFulfilmentStatusEntry | OrderFinancialStatusEntry; interface OrderStatusEntryBase { /** Unix ms when the move was committed. */ timestamp: number; /** * Who moved it. ABSENT when the mover was the platform itself — a * marketplace shipment webhook, a payment provider hook, a scheduled * drain. Those writers have no operator, and stamping a placeholder id * would make the audit trail lie about who acted. */ userId?: string; /** Operator free text. Operator-only — never broadcast to a customer socket. */ notes?: string; } interface OrderFulfilmentStatusEntry extends OrderStatusEntryBase { axis: 'fulfilment'; status: OrderFulfilmentStatus; /** * The state moved FROM. Absent when the row carried no * `fulfilmentStatus` at the time — the whole back-catalogue, and * permanently so under the forward-only rule. */ from?: OrderFulfilmentStatus; } interface OrderFinancialStatusEntry extends OrderStatusEntryBase { axis: 'financial'; status: OrderFinancialStatus; /** The state moved FROM; absent when the row carried no `financialStatus`. */ from?: OrderFinancialStatus; } interface MercadolibreCreditNoteStamp { creditNoteNumber?: number; emittedAt?: number; status?: 'emitted' | 'skipped' | 'failed'; reason?: string; claimId?: string; source: 'manual' | 'auto'; } interface OrderMercadolibre { mlOrderId: string; packId?: string; buyerNickname?: string; shipmentId?: string; logisticType?: string; mlLastUpdated?: number; paid?: boolean; items?: OrderMercadolibreItem[]; fees?: { saleFee?: number; shippingCostSeller?: number; currency: string; currencyValue?: number; currencyValueAt?: number; }; billingInfo?: { docType?: string; docNumber?: string; custType?: 'CO' | 'BU'; taxpayerType?: string; iibbNumber?: string; }; discrepancies?: { priceMismatch?: boolean; oversell?: boolean; missingCuit?: boolean; /** * How many of this order's ML lines carried an `mlItemId` that resolves to * no local product, so the line could not move `Product.stock`. * * ⚠️ Unlike its three siblings this is a COUNT, not a boolean, and `0` is a * meaningful value: it says the order was graded and every line resolved. * Absent means not graded. A truthiness test therefore reads a fully-linked * order and an ungraded one identically — compare against `undefined`. * * It exists because an unlinked line is the one case where the sale is real * and the inventory move is silently impossible; leaving it unrecorded * reproduces the un-decremented counter for exactly the products most likely * to be mis-linked. */ unlinkedLines?: number; }; fiscalDocumentStatus?: 'uploaded' | 'failed' | 'pending'; } interface OrderMercadolibreItem { mlItemId: string; variationId?: string; userProductId?: string; sellerSku?: string; quantity: number; stock?: { mlStoreId?: string; networkNodeId?: string; }[]; } interface LinkedPaymentEntry { source: 'mp' | 'stripe' | 'mp_movement'; total: number; linkedAt: number; } /** * The body of `POST /orders` on the CREATE / UPDATE path — the one carrying no * `mode`. (`mode: 'edit'` is `EditOrderRequest`; `mode: 'return'` is * `CreateReturnRequest`.) * * Order fields ride this body straight onto the stored row, which is why it * extends `Partial`. Everything declared HERE is the opposite: a field * that ROUTES or DIRECTS the request and is deliberately stripped before the * write. None of them is ever readable back off a stored `Order` — do not * reach for `order.cartId` or `order.counterSale` on a row you read. */ interface CreateOrderRequest extends Partial { /** * The cart to build this order from, named explicitly. * * ⚠️ For a WALK-IN ticket this is the only thing that works. A walk-in cart * has no `customerId` at all and the `PK-customerId` index is sparse, so the * customer lookup cannot reach it by any route. Omit `cartId` on a walk-in * conversion and the server resolves the named CUSTOMER's own cart instead — * converting a different row, or none, while the scanned lines sit in a * ticket nobody converted. * * It is also required for correctness on a NON-walk-in POS sale: a customer * can own several carts at once (their own web cart, plus any till's ticket * for them), so resolving by customer makes the server guess which one the * cashier is holding. The till already knows — send it. * * Satisfies the "name something to build from" gate on its own: a request * carrying `cartId` needs neither `orderId` nor `customerId`. * * ⚠️ NOT stored on the order. It routes the request and is stripped before * the write. */ cartId?: string; /** Counter sale — a request directive, stripped before the write, never stored. */ counterSale?: boolean; /** Send the order's SMS notification. Directive only, never stored. */ sendSms?: boolean; /** Persist `deliveryAddress` as the customer's default. Directive only, never stored. */ saveAsDefault?: boolean; /** * Attributes to REMOVE from the stored order, rather than set — clearing a * date needs an explicit removal, since an omitted key means "leave it". */ removeFields?: ('serviceStartDate' | 'serviceEndDate' | 'dueDate')[]; } interface ZebraTag { orderId: string; fullName: string; phone: string; address: string; city: string; quantity: number; comments: string; sender: { razonSocial: string; cuit: string; phone: string; address: string; city: string; postalCode: string; province: string; }; } } /** * Legal moves on the fulfilment axis. * * `pending -> delivered` is legal DIRECTLY and must stay that way: the * marketplace shipment sync writes a delivery onto an order that was never * marked ready, and the derivation rule on {@link OrderFulfilmentStatus} maps * exactly that row to `delivered`. A table that forced delivery through `ready` * would 409 a webhook that describes something that already happened. * * `not_delivered` sits LATERAL to `ready`, reachable from both `pending` and * `ready` and leading back to either `ready` (the carrier re-attempts) or * `delivered` (it succeeds on the retry). It is not terminal — a failed * delivery attempt is a setback, not an ending — and it is not reachable from * `delivered`, because nothing un-delivers an order by failing to deliver it. * * `delivered -> ready` is the operator's explicit un-delivery, which exists and * is same-calendar-day only (it reverses a balance movement and an account row * that are reconciled per day). It lands on `ready` rather than `pending` * because un-delivery does not clear `readyAt`. There is therefore NO terminal * fulfilment state — `ORDER_FULFILMENT_TERMINAL_STATUSES` is empty by * construction, and it is derived rather than hand-listed so it can never * disagree with the table. * * There are no self-edges: re-requesting the current status is a no-op, not a * transition. */ export declare const ORDER_FULFILMENT_TRANSITIONS: Readonly>; /** * Legal moves on the financial axis — every one of them, in both directions. * * This table is fully connected ON PURPOSE, and saying so is more honest than * inventing a restriction. The financial state is a DERIVED, reversible verdict * over the ledger: linking a payment moves it forward, unlinking or refunding * one moves it back, and a credit note or return can settle an order by * shrinking the debit rather than by paying it. No sequence of those is * illegal, so this table can never return an illegal move. * * It exists for the two things it still buys: the `Record` proves every status * has been considered, and the resolver built on it gives the financial axis * the same no-op detection and same compare-and-set precondition shape as the * fulfilment axis, so one writer pattern covers both. */ export declare const ORDER_FINANCIAL_TRANSITIONS: Readonly>; /** Every fulfilment status, in lifecycle order. */ export declare const ORDER_FULFILMENT_STATUSES: [OrderFulfilmentStatus, ...OrderFulfilmentStatus[]]; /** Every financial status, from unpaid to settled. */ export declare const ORDER_FINANCIAL_STATUSES: [OrderFinancialStatus, ...OrderFinancialStatus[]]; export declare const isOrderFulfilmentStatus: (value: unknown) => value is OrderFulfilmentStatus; export declare const isOrderFinancialStatus: (value: unknown) => value is OrderFinancialStatus; /** * The fulfilment statuses nothing transitions out of — DERIVED from the table, * never hand-listed, so it cannot drift from it. * * Empty today, and that is the correct answer rather than an oversight: * operator un-delivery gives `delivered` an outgoing edge. Read it, do not * assume it. */ export declare const ORDER_FULFILMENT_TERMINAL_STATUSES: readonly OrderFulfilmentStatus[]; export declare const isTerminalOrderFulfilmentStatus: (status: OrderFulfilmentStatus) => boolean; export {};