Skip to content

13. No-op Proxy in useReservationUpdater for Transfer Orders

Date: 2026-02-27

Status

Accepted — temporary measure, not a full solution.

Context

useReservationUpdater is the central hook for modifying order records in the Relay store. It’s used in 60+ files across the app (edit page, calendar, tables, sidebar, OrderView, check-in/out, etc.).

The hook contains onUpdateActions — a set of rental-specific billing recalculation functions that run automatically on every updateValue call:

  • eachTime() runs on every call and overwrites totalProfit with a rental formula
  • Field-specific handlers (dateFrom, totalPrice, pricePerDay, etc.) recalculate billing using rental formulas
  • status() zeroes totalPrice for Maintenance and recalculates for other statuses

With the introduction of transfer/disposition orders (Limo / NCC), these rental recalculations are destructive — they overwrite limo billing fields (totalPrice, totalProfit) with meaningless rental values. This happens even for non-billing operations (updating a note, changing an address) because eachTime() fires unconditionally.

Decision

Add a no-op Proxy at the top of onUpdateActions that short-circuits all action calls when the order is a transfer or disposition:

const actionsProxy = new Proxy({}, { get: () => () => {} });
const onUpdateActions = ({ form, store, value }) => {
const serviceType = form.getValue('serviceType');
const isTransfer = serviceType === ServiceTypeValues.Transfer
|| serviceType === ServiceTypeValues.Disposition;
if (isTransfer) return actionsProxy;
return { eachTime() { ... }, ... };
};

The proxy returns a no-op function for any property access. Since callers use actions[key]?.() and actions.eachTime(), every call silently does nothing.

What the proxy covers

Any recalculation that goes through onUpdateActions:

  • updateValue(key, value)actions[key]?.() + actions.eachTime() are no-oped
  • updateOrderExtraService / updateLocalOrderExtraServiceactions.orderExtraServices() is no-oped
  • recalculatePriceWithServiceactions.orderExtraServices() is no-oped
  • recalculateExtraServicesPriceactions.orderExtraServices() is no-oped

What the proxy does NOT cover

Several dangerous functions bypass onUpdateActions entirely or have issues beyond what the proxy handles. These are tracked in TOP-4666:

  1. updateStatus — calls actions.status() which returns undefined via proxy. The caller uses this return value to send totalPrice in the mutation. Today this is transient and may not cause issues, but it’s fragile.

  2. updateVehicle — all recalculation is inline (not through onUpdateActions). The proxy has no effect. Would overwrite all billing fields with rental values if called on a transfer.

  3. recalculatePriceWithNewService — inline ± math on totalPrice/totalProfit. Does not use onUpdateActions at all.

  4. recalculatePriceWithService / recalculateExtraServicesPrice — the proxy no-ops the store update, but these functions read totalPrice/totalProfit from initial let = 0 variables, so they return { totalPrice: 0, totalProfit: 0 }. If a caller sends these values to the backend, billing gets zeroed out.

  5. updateValue with date keys — the proxy correctly skips rental recalculation, but nothing triggers limo billing recalculation either. Date changes on a transfer silently have no billing effect.

Why not fix each function individually?

  • 60+ files use this hook. Scattered if (isTransfer) guards in each function are easy to miss and hard to maintain.
  • The proxy provides a single systemic guard for the most common path (updateValueonUpdateActions).
  • The functions not covered by the proxy need their own transfer-aware logic regardless — they are fundamentally different operations for transfers vs rentals.

Consequences

  • Transfer orders are protected from rental billing corruption for all onUpdateActions-based paths.
  • Developers must be aware that the proxy is not a complete solution. Any new function that does inline recalculation (not through onUpdateActions) must handle transfers explicitly.
  • The long-term fix is to progressively move transfer-specific operations into useTransferUpdater and reduce useReservationUpdater’s role for transfers to non-billing operations only.

References

  • TOP-4660 — Render transfer billing on edit page (where the proxy was introduced)
  • TOP-4666 — Full audit of dangerous functions and their call sites