fix(tariff): reject stepped base combined with time/seasonal tiers
A stepped ("up-to") default card prices the whole stay as one total, so the V2
engine short-circuits to steppedFee and NEVER consults windowed cards — any
time/seasonal tiers would silently never fire. Found live: an active tariff had a
stepped base plus weekday-night + weekend tiers, and every 3h stay priced 600 ALL
regardless of hour/day because the tiers were dead.
- validateTariffV2 now rejects a stepped defaultCard combined with windowedCards,
with an actionable message (switch the base to ladder/flat, or remove the tiers).
- Composer shows an inline red warning the moment base mode is stepped and tiers
exist; publishing is blocked server-side regardless.
- ApiError now carries the server's problems[], so the publish error surfaces the
SPECIFIC reason instead of a generic "invalid tariff structure".
- 2 new validation tests (55 pass).
Wiki: tariff, log.
Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
+4
-2
@@ -29,7 +29,7 @@ export async function apiFetch<T>(path: string, init: RequestInit = {}): Promise
|
||||
}
|
||||
const res = await fetch(path, { ...init, headers, credentials: "include" });
|
||||
if (!res.ok) {
|
||||
const msg = (await res.json().catch(() => ({}))) as { error?: string };
|
||||
const msg = (await res.json().catch(() => ({}))) as { error?: string; problems?: string[] };
|
||||
const error = msg.error ?? `${path}: ${res.status}`;
|
||||
// Ship the failed request to the backend log store (best-effort, loop-safe — the
|
||||
// logger itself never logs the /api/logs call). 401s are normal pre-login churn,
|
||||
@@ -37,7 +37,7 @@ export async function apiFetch<T>(path: string, init: RequestInit = {}): Promise
|
||||
if (res.status !== 401) {
|
||||
logFailedRequest({ path, method, status: res.status, error });
|
||||
}
|
||||
throw new ApiError(error, res.status);
|
||||
throw new ApiError(error, res.status, msg.problems);
|
||||
}
|
||||
if (res.status === 204) return undefined as T;
|
||||
return res.json() as Promise<T>;
|
||||
@@ -47,6 +47,8 @@ export class ApiError extends Error {
|
||||
constructor(
|
||||
message: string,
|
||||
readonly status: number,
|
||||
/** Field-level problems from a validation error (e.g. tariff publish), if any. */
|
||||
readonly problems?: string[],
|
||||
) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user