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:
2026-07-23 12:22:02 -07:00
co-authored by Claude Opus 4.8
parent 12692a0af8
commit 7a46c30d9b
22 changed files with 1973 additions and 19 deletions
@@ -0,0 +1,86 @@
import {
Body,
Controller,
Delete,
Get,
Param,
Patch,
Post,
UseGuards,
} from "@nestjs/common";
import { AuthenticatedGuard } from "../auth/authenticated.guard";
import { AbilityGuard } from "../auth/ability.guard";
import { RequireAbility } from "../auth/require-ability.decorator";
import { PoliciesService } from "./policies.service";
import {
AdjusterDto,
PolicyTypeDto,
ProviderDto,
UpdateAdjusterDto,
UpdatePolicyTypeDto,
UpdateProviderDto,
} from "./lookup.dto";
/**
* Insurance reference data: providers, policy types, adjusters. Reading is open
* to any authenticated user (the policy form needs the options); mutating needs
* "lookup:manage" (MANAGER+).
*/
@UseGuards(AuthenticatedGuard, AbilityGuard)
@Controller("lookups")
export class LookupsController {
constructor(private readonly policies: PoliciesService) {}
@Get()
list() {
return this.policies.listLookups();
}
@Post("providers")
@RequireAbility("lookup:manage")
createProvider(@Body() dto: ProviderDto) {
return this.policies.createProvider(dto);
}
@Patch("providers/:id")
@RequireAbility("lookup:manage")
updateProvider(@Param("id") id: string, @Body() dto: UpdateProviderDto) {
return this.policies.updateProvider(id, dto);
}
@Delete("providers/:id")
@RequireAbility("lookup:manage")
removeProvider(@Param("id") id: string) {
return this.policies.removeProvider(id);
}
@Post("policy-types")
@RequireAbility("lookup:manage")
createType(@Body() dto: PolicyTypeDto) {
return this.policies.createPolicyType(dto);
}
@Patch("policy-types/:id")
@RequireAbility("lookup:manage")
updateType(@Param("id") id: string, @Body() dto: UpdatePolicyTypeDto) {
return this.policies.updatePolicyType(id, dto);
}
@Delete("policy-types/:id")
@RequireAbility("lookup:manage")
removeType(@Param("id") id: string) {
return this.policies.removePolicyType(id);
}
@Post("adjusters")
@RequireAbility("lookup:manage")
createAdjuster(@Body() dto: AdjusterDto) {
return this.policies.createAdjuster(dto);
}
@Patch("adjusters/:id")
@RequireAbility("lookup:manage")
updateAdjuster(@Param("id") id: string, @Body() dto: UpdateAdjusterDto) {
return this.policies.updateAdjuster(id, dto);
}
@Delete("adjusters/:id")
@RequireAbility("lookup:manage")
removeAdjuster(@Param("id") id: string) {
return this.policies.removeAdjuster(id);
}
}