Files
eden/apps/api/src/contracts/contracts.controller.ts
Matheus (Handix) 18838a9031 feat(api): Quotes, Client Registration e esqueleto de Contratos
Fecha o núcleo comercial da Fase 2/3: motor de precificação de Ofertas
(feito diretamente, dado o risco de erro de arredondamento/regra de
negócio), Cadastro de Cliente e esqueleto de Contratos (via 2 subagentes
em paralelo, salvos manualmente após ambos baterem no limite de gasto
mensal da conta — trabalho não commitado nos worktrees foi revisado
arquivo a arquivo e copiado para main, com as peças faltantes
completadas diretamente).

- Quotes: pricing.ts porta as fórmulas do legado (eden.md §7/§8) —
  needsApproval, faixas de condição de pagamento, efeito legal da
  fidelidade condicionado a approval_status==='approved'. Fluxo
  criar→decidir aprovação→fechar negócio (vinculado a um cadastro de
  cliente, com guarda contra fechamento duplo).
- Client Registration: cadastro direto (eden.md §3), MVP autenticado-only
  (sem token público de autoatendimento, upload ou lookup CNPJ/CEP nesta
  fase — documentado em comentário).
- Contracts: máquina de estados do ADR-0006 imposta via SELECT...FOR
  UPDATE + tabela de transições, com signed_at/activated_at e histórico
  de status; 4 das 11 tabelas do ADR-0006 (as demais adiadas).

2 bugs próprios corrigidos antes do commit: UpdateQuoteInput usava
COALESCE para campos financeiros (undefined do JS apagaria valor em
update parcial) — campos tornados obrigatórios; User.reseller_id/
can_approve_discount faltavam em types.ts apesar das colunas já
existirem no banco desde migrations anteriores.

Verificado: pnpm turbo run typecheck build (28/28), pnpm test (15/15),
e todo o fluxo testado ao vivo via curl contra a stack Docker real,
incluindo cenários de erro (transição de status inválida, FK de produto
inexistente, fechamento duplo, 401 sem token) e trilha de auditoria
conferida no Postgres.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 12:25:49 -03:00

75 lines
2.6 KiB
TypeScript

import { Body, Controller, Get, Param, Patch, Post, UseGuards } from "@nestjs/common";
import { ApiBearerAuth, ApiTags } from "@nestjs/swagger";
import { ContractsService } from "./contracts.service.js";
import { CreateContractDto } from "./dto/create-contract.dto.js";
import { UpdateContractStatusDto } from "./dto/update-contract-status.dto.js";
import { ContractItemInputDto } from "./dto/contract-item-input.dto.js";
import { ContractPartyInputDto } from "./dto/contract-party-input.dto.js";
import { JwtAuthGuard } from "../auth/jwt-auth.guard.js";
import { PermissionGuard } from "../auth/permission.guard.js";
import { RequirePermission } from "../auth/decorators/require-permission.decorator.js";
import { CurrentUser } from "../auth/decorators/current-user.decorator.js";
import type { EdenAuthenticatedUser } from "../auth/jwt-auth.guard.js";
/**
* Contracts as a first-class aggregate — docs/adr/0006-contract-first-class.md.
* Every write recalculates monthly_value/contract_total server-side from
* the items array (Master Prompt §6.6/§24) — a client-supplied total is
* never trusted.
*/
@ApiTags("contracts")
@ApiBearerAuth()
@UseGuards(JwtAuthGuard, PermissionGuard)
@Controller("v1/contracts")
export class ContractsController {
constructor(private readonly contracts: ContractsService) {}
@Get()
@RequirePermission("contracts", "view")
list(@CurrentUser() user: EdenAuthenticatedUser) {
return this.contracts.list(user);
}
@Get(":id")
@RequirePermission("contracts", "view")
get(@Param("id") id: string, @CurrentUser() user: EdenAuthenticatedUser) {
return this.contracts.get(id, user);
}
@Post()
@RequirePermission("contracts", "edit")
create(@Body() dto: CreateContractDto, @CurrentUser() user: EdenAuthenticatedUser) {
return this.contracts.create(dto, user);
}
@Patch(":id/status")
@RequirePermission("contracts", "edit")
updateStatus(
@Param("id") id: string,
@Body() dto: UpdateContractStatusDto,
@CurrentUser() user: EdenAuthenticatedUser,
) {
return this.contracts.updateStatus(id, dto, user);
}
@Post(":id/items")
@RequirePermission("contracts", "edit")
addItem(
@Param("id") id: string,
@Body() dto: ContractItemInputDto,
@CurrentUser() user: EdenAuthenticatedUser,
) {
return this.contracts.addItem(id, dto, user);
}
@Post(":id/parties")
@RequirePermission("contracts", "edit")
addParty(
@Param("id") id: string,
@Body() dto: ContractPartyInputDto,
@CurrentUser() user: EdenAuthenticatedUser,
) {
return this.contracts.addParty(id, dto, user);
}
}