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>
75 lines
2.6 KiB
TypeScript
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);
|
|
}
|
|
}
|