shoc-frontend-new/src/domain/vendors/api/vendor-company-roster-api.ts
Alexandre Brandizzi ca7829036f
Some checks failed
CI / ci (push) Has been cancelled
CI / governance (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
feat(vendors): add company roster workflows (SH-198) (#64)
* feat(vendors): support company roster workflows

* fix(vendors): address roster review feedback

* test(vendors): preserve draft on roster load failure

* fix(vendors): preserve current roster selection
2026-08-04 09:53:48 -03:00

100 lines
3.3 KiB
TypeScript

import { isHTTPError } from "ky";
import { API_PATHS } from "@/api/api-paths";
import { apiGet, apiPost, apiPut } from "@/api/api";
import { handleApiResponse } from "@/api/handle-api-response";
import {
mapRosterConflict,
mapVendorCompanyRoster,
mapVendorRosterToBackend,
} from "@/domain/vendors/mappers/vendor-roster-mapper";
import { VendorRosterConflictError } from "@/domain/vendors/lib/vendor-roster-conflict";
import type { VendorCompanyRoster } from "@/domain/vendors/types/vendor";
const STALE_MESSAGE =
"This company was changed by another session. Reload the latest version and try again.";
const OPEN_WORK_ORDER_MESSAGE =
"This company cannot be saved because it still has open work orders.";
async function readConflictBody(error: unknown): Promise<unknown> {
if (!isHTTPError(error)) return undefined;
try {
return await error.response.json();
} catch {
try {
const text = await error.response.text();
return text ? JSON.parse(text) : undefined;
} catch {
return undefined;
}
}
}
export async function throwRosterConflict(error: unknown): Promise<never> {
const status = isHTTPError(error) ? error.response.status : 0;
const body = await readConflictBody(error);
const message = status === 409 ? STALE_MESSAGE : "Failed to save vendor company";
const conflict = mapRosterConflict(body, message);
if (conflict.kind === "open-work-orders" && conflict.blockedWorkOrders.length === 0) {
conflict.message = OPEN_WORK_ORDER_MESSAGE;
}
throw new VendorRosterConflictError(conflict, status || 409);
}
export interface RosterQuery {
vendorId?: string | number;
companyId?: string | number;
}
function buildRosterSearchParams(query: RosterQuery): Record<string, string> {
const params: Record<string, string> = {};
if (query.vendorId !== undefined && query.vendorId !== "") {
params.vendorId = String(query.vendorId);
}
if (query.companyId !== undefined && query.companyId !== "") {
params.companyId = String(query.companyId);
}
return params;
}
export const vendorCompanyRosterApi = {
get: async (query: RosterQuery): Promise<VendorCompanyRoster> => {
const data = await apiGet<unknown>(API_PATHS.vendorCompanyRoster.base, {
searchParams: buildRosterSearchParams(query),
});
return mapVendorCompanyRoster(handleApiResponse(data));
},
create: async (payload: unknown): Promise<VendorCompanyRoster> => {
try {
const data = await apiPost<unknown>(
API_PATHS.vendorCompanyRoster.base,
mapVendorRosterToBackend(payload),
);
return mapVendorCompanyRoster(handleApiResponse(data));
} catch (error) {
if (isHTTPError(error) && error.response.status === 409) {
await throwRosterConflict(error);
}
throw error;
}
},
update: async (
companyId: string | number,
payload: unknown,
rowVersion: string,
): Promise<VendorCompanyRoster> => {
try {
const data = await apiPut<unknown>(
API_PATHS.vendorCompanyRoster.byCompany(companyId),
mapVendorRosterToBackend({ ...(payload as Record<string, unknown>), rowVersion }),
);
return mapVendorCompanyRoster(handleApiResponse(data));
} catch (error) {
if (isHTTPError(error) && error.response.status === 409) {
await throwRosterConflict(error);
}
throw error;
}
},
};