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 overwritestotalProfitwith 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-opedupdateOrderExtraService/updateLocalOrderExtraService—actions.orderExtraServices()is no-opedrecalculatePriceWithService—actions.orderExtraServices()is no-opedrecalculateExtraServicesPrice—actions.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:
-
updateStatus— callsactions.status()which returnsundefinedvia proxy. The caller uses this return value to sendtotalPricein the mutation. Today this is transient and may not cause issues, but it’s fragile. -
updateVehicle— all recalculation is inline (not throughonUpdateActions). The proxy has no effect. Would overwrite all billing fields with rental values if called on a transfer. -
recalculatePriceWithNewService— inline ± math on totalPrice/totalProfit. Does not useonUpdateActionsat all. -
recalculatePriceWithService/recalculateExtraServicesPrice— the proxy no-ops the store update, but these functions readtotalPrice/totalProfitfrom initiallet = 0variables, so they return{ totalPrice: 0, totalProfit: 0 }. If a caller sends these values to the backend, billing gets zeroed out. -
updateValuewith 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 (
updateValue→onUpdateActions). - 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
useTransferUpdaterand reduceuseReservationUpdater’s role for transfers to non-billing operations only.