permits: admin CRUD (route + UI)
A permit is an aggregate (row + credentials + bound plates); create/update treat it as one unit (child sets replaced on update). GET /api/permits (any signed-in role, for lookup); POST/PUT/DELETE + POST /:id/revoke (admin only). Validation: maxConcurrent positive-int-or-null (unbound); a permit must have at least one credential OR one bound plate. Revoke is the soft common case (keeps history, barred at the barrier); DELETE hard-removes — past ledger events that reference it are untouched (append-only audit trail, independent of this row). Web PermitManager in the admin shell: list + add/edit (holder, car-bound toggle, validity, credentials, plates), revoke, delete. Makes permits usable without hand-seeding (companion to the tariff composer). Verified via inject: validation (empty / maxConcurrent=0 -> 400), create -> 201, operator can LIST but not write (403), update replaces child rows, revoke -> revoked, delete -> 204 then 404 with children cleaned.
This commit is contained in:
@@ -240,3 +240,40 @@ export function publishTariffVersion(body: {
|
||||
}): Promise<TariffVersion> {
|
||||
return apiFetch("/api/tariff/versions", { method: "POST", body: JSON.stringify(body) });
|
||||
}
|
||||
|
||||
// --- Permits --------------------------------------------------------------
|
||||
|
||||
export interface PermitCredential {
|
||||
kind: "rf" | "qr";
|
||||
value: string;
|
||||
}
|
||||
export interface Permit {
|
||||
id: string;
|
||||
holderName: string | null;
|
||||
contact: string | null;
|
||||
maxConcurrent: number | null;
|
||||
validFrom: string | null;
|
||||
validTo: string | null;
|
||||
status: "active" | "suspended" | "revoked";
|
||||
credentials: PermitCredential[];
|
||||
plates: string[];
|
||||
}
|
||||
export type PermitInput = Omit<Permit, "id" | "status"> & {
|
||||
status?: Permit["status"];
|
||||
};
|
||||
|
||||
export function fetchPermits(): Promise<{ permits: Permit[] }> {
|
||||
return apiFetch("/api/permits");
|
||||
}
|
||||
export function createPermit(body: PermitInput): Promise<Permit> {
|
||||
return apiFetch("/api/permits", { method: "POST", body: JSON.stringify(body) });
|
||||
}
|
||||
export function updatePermit(id: string, body: PermitInput): Promise<Permit> {
|
||||
return apiFetch(`/api/permits/${id}`, { method: "PUT", body: JSON.stringify(body) });
|
||||
}
|
||||
export function revokePermit(id: string): Promise<Permit> {
|
||||
return apiFetch(`/api/permits/${id}/revoke`, { method: "POST" });
|
||||
}
|
||||
export function deletePermit(id: string): Promise<void> {
|
||||
return apiFetch(`/api/permits/${id}`, { method: "DELETE" });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user