feat(policies): full CRUD + child editors + insurance lookups (plan phase 3)
Policy header, all five child collections, and the insurance reference catalogs become create/edit/delete-able on the RBAC foundation. API: - Policy gains archivedAt (soft-delete); list/browser default to archivedAt=null with ?includeArchived opt-in. - PoliciesService: header create/update/archive/restore (customer FK validated for a clean 404); add/update/remove for installments, vehicles, drivers, beneficiaries, claims — each scoped to its policy so one policy's id can't touch another's rows; lookups CRUD for providers, policy types, adjusters. - PoliciesController write routes: header create/update need STAFF+ (policy:create/update), archive/restore need MANAGER+ (policy:delete), every child route needs policy:update. New LookupsController at /lookups (read open; mutate needs lookup:manage / MANAGER+). Mutations audited. - DTOs (policy header, children, lookups); dates coerced; shared coerce.ts. Web: - Generic ChildCollection editor (config-driven add/edit/remove table), reused by both the policy detail child editors and the catalogs screen. - PolicyForm (header) with type/provider selects and a debounced CustomerPicker; /polizas/nuevo (accepts ?customerId prefill) and /polizas/[id]/editar. Policy detail: gated action bar (Editar/Archivar) + "Administrar detalles" child editors for all five collections. - /catalogos admin screen (aseguradoras/tipos/ajustadores), nav-gated on lookup:manage. "Nueva póliza" buttons on the list and on the customer detail (prefilled). api.ts + types for all of the above. Verified against dev: policy create (dates coerced, archivedAt null), installment/vehicle add, VIEWER child-add 403, cross-policy child guard 404, lookups CRUD with VIEWER 403 / MANAGER 201, archive drops from the default list and includeArchived surfaces it. Both apps compile clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -26,10 +26,12 @@ import type {
|
||||
MovementSort,
|
||||
PolicyDetail,
|
||||
PolicyFacets,
|
||||
PolicyInput,
|
||||
PolicyListResponse,
|
||||
PolicySort,
|
||||
PolicyStats,
|
||||
PolicyStatus,
|
||||
LookupsResponse,
|
||||
PropertyDetail,
|
||||
PropertyFacets,
|
||||
PropertyListResponse,
|
||||
@@ -208,6 +210,83 @@ export function getPolicy(
|
||||
return apiFetch<PolicyDetail>(`/policies/${id}?days=${days}`);
|
||||
}
|
||||
|
||||
export function createPolicy(input: PolicyInput): Promise<PolicyDetail> {
|
||||
return apiFetch<PolicyDetail>("/policies", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
}
|
||||
export function updatePolicy(
|
||||
id: string,
|
||||
input: Partial<PolicyInput>,
|
||||
): Promise<PolicyDetail> {
|
||||
return apiFetch<PolicyDetail>(`/policies/${id}`, {
|
||||
method: "PATCH",
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
}
|
||||
export function archivePolicy(id: string): Promise<PolicyDetail> {
|
||||
return apiFetch<PolicyDetail>(`/policies/${id}`, { method: "DELETE" });
|
||||
}
|
||||
export function restorePolicy(id: string): Promise<PolicyDetail> {
|
||||
return apiFetch<PolicyDetail>(`/policies/${id}/restore`, { method: "POST" });
|
||||
}
|
||||
|
||||
// Generic policy-child CRUD. `kind` is the URL segment
|
||||
// (installments|vehicles|drivers|beneficiaries|claims).
|
||||
export function addPolicyChild<T>(
|
||||
policyId: string,
|
||||
kind: string,
|
||||
input: T,
|
||||
): Promise<unknown> {
|
||||
return apiFetch(`/policies/${policyId}/${kind}`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
}
|
||||
export function updatePolicyChild<T>(
|
||||
policyId: string,
|
||||
kind: string,
|
||||
childId: string,
|
||||
input: T,
|
||||
): Promise<unknown> {
|
||||
return apiFetch(`/policies/${policyId}/${kind}/${childId}`, {
|
||||
method: "PATCH",
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
}
|
||||
export function removePolicyChild(
|
||||
policyId: string,
|
||||
kind: string,
|
||||
childId: string,
|
||||
): Promise<unknown> {
|
||||
return apiFetch(`/policies/${policyId}/${kind}/${childId}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
}
|
||||
|
||||
/* ------------------------------------------------- Lookups (insurance ref) */
|
||||
|
||||
export function getLookups(): Promise<LookupsResponse> {
|
||||
return apiFetch<LookupsResponse>("/lookups");
|
||||
}
|
||||
export function createLookup(kind: string, input: unknown): Promise<unknown> {
|
||||
return apiFetch(`/lookups/${kind}`, { method: "POST", body: JSON.stringify(input) });
|
||||
}
|
||||
export function updateLookup(
|
||||
kind: string,
|
||||
id: string,
|
||||
input: unknown,
|
||||
): Promise<unknown> {
|
||||
return apiFetch(`/lookups/${kind}/${id}`, {
|
||||
method: "PATCH",
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
}
|
||||
export function removeLookup(kind: string, id: string): Promise<unknown> {
|
||||
return apiFetch(`/lookups/${kind}/${id}`, { method: "DELETE" });
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------- Utilities module */
|
||||
|
||||
export interface PropertyQuery {
|
||||
|
||||
+112
-1
@@ -155,17 +155,23 @@ export interface Vehicle {
|
||||
id: string;
|
||||
make: string | null;
|
||||
model: string | null;
|
||||
modelYear: number | null;
|
||||
modelYear: number | string | null;
|
||||
licensePlate: string | null;
|
||||
bodyType: string | null;
|
||||
engineNumber?: string | null;
|
||||
vinNumber?: string | null;
|
||||
stateCode?: string | null;
|
||||
notes?: string | null;
|
||||
}
|
||||
|
||||
export interface InsuredDriver {
|
||||
id: string;
|
||||
fullName: string | null;
|
||||
licenseNumber: string | null;
|
||||
birthDate?: string | null;
|
||||
sex?: string | null;
|
||||
occupation?: string | null;
|
||||
licenseState?: string | null;
|
||||
}
|
||||
|
||||
export interface Beneficiary {
|
||||
@@ -237,6 +243,106 @@ export interface PolicyListItem {
|
||||
vehicleCount: number;
|
||||
installmentCount: number;
|
||||
documentCount: number;
|
||||
archived: boolean;
|
||||
}
|
||||
|
||||
/** Editable policy-header fields — shared by the create/edit form and API. */
|
||||
export interface PolicyInput {
|
||||
policyNumber: string;
|
||||
customerId: string;
|
||||
policyTypeId?: string;
|
||||
insuranceProviderId?: string;
|
||||
agentName?: string;
|
||||
policyDate?: string;
|
||||
policyFrom?: string;
|
||||
policyTo?: string;
|
||||
coveragePeriodDays?: number;
|
||||
netPremium?: number;
|
||||
policyFee?: number;
|
||||
brokerFee?: number;
|
||||
commission?: number;
|
||||
total?: number;
|
||||
currency?: Currency;
|
||||
observations?: string;
|
||||
notes?: string;
|
||||
endorsement?: boolean;
|
||||
liquidated?: boolean;
|
||||
liquidationNumber?: string;
|
||||
liquidationDate?: string;
|
||||
}
|
||||
|
||||
export interface InstallmentInput {
|
||||
sequence: number;
|
||||
amount?: number;
|
||||
currency?: Currency;
|
||||
dueDate?: string;
|
||||
paidDate?: string;
|
||||
checkNumber?: string;
|
||||
isCash?: boolean;
|
||||
}
|
||||
export interface VehicleInput {
|
||||
make?: string;
|
||||
model?: string;
|
||||
modelYear?: string;
|
||||
bodyType?: string;
|
||||
engineNumber?: string;
|
||||
licensePlate?: string;
|
||||
vinNumber?: string;
|
||||
stateCode?: string;
|
||||
notes?: string;
|
||||
}
|
||||
export interface DriverInput {
|
||||
fullName?: string;
|
||||
birthDate?: string;
|
||||
sex?: string;
|
||||
occupation?: string;
|
||||
licenseNumber?: string;
|
||||
licenseState?: string;
|
||||
}
|
||||
export interface BeneficiaryInput {
|
||||
name?: string;
|
||||
address?: string;
|
||||
phone?: string;
|
||||
email?: string;
|
||||
}
|
||||
export interface ClaimInput {
|
||||
claimType?: string;
|
||||
incidentDate?: string;
|
||||
reportedDate?: string;
|
||||
description?: string;
|
||||
adjusterId?: string;
|
||||
claimedAmount?: number;
|
||||
settledAmount?: number;
|
||||
settlementDate?: string;
|
||||
checkNumber?: string;
|
||||
resolved?: boolean;
|
||||
resolutionNotes?: string;
|
||||
}
|
||||
|
||||
/* Lookups (insurance reference data) */
|
||||
export interface ProviderRow {
|
||||
id: string;
|
||||
name: string;
|
||||
_count?: { policies: number };
|
||||
}
|
||||
export interface PolicyTypeRow {
|
||||
id: string;
|
||||
name: string;
|
||||
shortDescription: string | null;
|
||||
_count?: { policies: number };
|
||||
}
|
||||
export interface AdjusterRow {
|
||||
id: string;
|
||||
company: string | null;
|
||||
city: string | null;
|
||||
name: string | null;
|
||||
phone: string | null;
|
||||
beeper: string | null;
|
||||
}
|
||||
export interface LookupsResponse {
|
||||
providers: ProviderRow[];
|
||||
types: PolicyTypeRow[];
|
||||
adjusters: AdjusterRow[];
|
||||
}
|
||||
|
||||
export interface PolicyListResponse {
|
||||
@@ -295,6 +401,10 @@ export interface Claim {
|
||||
settlementDate: string | null;
|
||||
status?: string | null;
|
||||
adjuster: Adjuster | null;
|
||||
adjusterId?: string | null;
|
||||
checkNumber?: string | null;
|
||||
resolved?: boolean;
|
||||
resolutionNotes?: string | null;
|
||||
}
|
||||
|
||||
export interface PolicyCustomerRef {
|
||||
@@ -333,6 +443,7 @@ export interface PolicyDetail {
|
||||
legacySourceDb: string | null;
|
||||
legacySourceTable: string | null;
|
||||
legacyId: string | null;
|
||||
archivedAt: string | null;
|
||||
status: PolicyStatus;
|
||||
daysToExpiry: number | null;
|
||||
customer: PolicyCustomerRef;
|
||||
|
||||
Reference in New Issue
Block a user