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>
This commit is contained in:
88
apps/api/src/quotes/quotes.controller.ts
Normal file
88
apps/api/src/quotes/quotes.controller.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { Body, Controller, Get, Param, Patch, Post, Query, UseGuards } from "@nestjs/common";
|
||||
import { ApiBearerAuth, ApiTags } from "@nestjs/swagger";
|
||||
import { QuotesService } from "./quotes.service.js";
|
||||
import { CreateQuoteDto } from "./dto/create-quote.dto.js";
|
||||
import { UpdateQuoteDto } from "./dto/update-quote.dto.js";
|
||||
import { DecideApprovalDto } from "./dto/decide-approval.dto.js";
|
||||
import { CloseDealDto } from "./dto/close-deal.dto.js";
|
||||
import { JwtAuthGuard } from "../auth/jwt-auth.guard.js";
|
||||
import { PermissionGuard } from "../auth/permission.guard.js";
|
||||
import { SuperAdminGuard } from "../auth/super-admin.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";
|
||||
|
||||
/**
|
||||
* Ofertas comerciais (eden.md módulo 2). Scope resolution (own/reseller/all)
|
||||
* happens in the service, driven by the actor's granted permission scope —
|
||||
* see QuotesService.list/get. Every write recomputes pricing server-side
|
||||
* (Master Prompt §6.4) — the client never supplies a trusted total.
|
||||
*/
|
||||
@ApiTags("quotes")
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard, PermissionGuard)
|
||||
@Controller("v1/quotes")
|
||||
export class QuotesController {
|
||||
constructor(private readonly quotes: QuotesService) {}
|
||||
|
||||
@Get()
|
||||
@RequirePermission("quotes", "view")
|
||||
list(@CurrentUser() user: EdenAuthenticatedUser, @Query("dealStatus") dealStatus?: string) {
|
||||
return this.quotes.list(user, dealStatus);
|
||||
}
|
||||
|
||||
@Get(":id")
|
||||
@RequirePermission("quotes", "view")
|
||||
get(@Param("id") id: string, @CurrentUser() user: EdenAuthenticatedUser) {
|
||||
return this.quotes.get(id, user);
|
||||
}
|
||||
|
||||
@Post()
|
||||
@RequirePermission("quotes", "edit")
|
||||
create(@Body() dto: CreateQuoteDto, @CurrentUser() user: EdenAuthenticatedUser) {
|
||||
return this.quotes.create(dto, user);
|
||||
}
|
||||
|
||||
@Patch(":id")
|
||||
@RequirePermission("quotes", "edit")
|
||||
update(
|
||||
@Param("id") id: string,
|
||||
@Body() dto: UpdateQuoteDto,
|
||||
@CurrentUser() user: EdenAuthenticatedUser,
|
||||
) {
|
||||
return this.quotes.update(id, dto, user);
|
||||
}
|
||||
|
||||
/** Approve/reject a pending discount or reduced-fidelity exception — eden.md §3.3. Gated inside the service by canApproveDiscount(), not by a generic permission (mirrors the legacy's per-user flag, not a role-based permission). */
|
||||
@Patch(":id/approval")
|
||||
@RequirePermission("quotes", "approve")
|
||||
decideApproval(
|
||||
@Param("id") id: string,
|
||||
@Body() dto: DecideApprovalDto,
|
||||
@CurrentUser() user: EdenAuthenticatedUser,
|
||||
) {
|
||||
return this.quotes.decideApproval(id, dto, user);
|
||||
}
|
||||
|
||||
/** Closes the deal, locking the quote — eden.md §5.1/§6. */
|
||||
@Post(":id/close")
|
||||
@RequirePermission("quotes", "edit")
|
||||
closeDeal(
|
||||
@Param("id") id: string,
|
||||
@Body() dto: CloseDealDto,
|
||||
@CurrentUser() user: EdenAuthenticatedUser,
|
||||
) {
|
||||
return this.quotes.closeDeal(id, dto, user);
|
||||
}
|
||||
|
||||
/** super_admin-only shortcut, works even on a locked quote — eden.md §5.2. Route-level guard for defense in depth, on top of the service-layer check. */
|
||||
@Patch(":id/deal-status")
|
||||
@UseGuards(SuperAdminGuard)
|
||||
dealStatus(
|
||||
@Param("id") id: string,
|
||||
@Body("dealStatus") dealStatus: "orcamento" | "fechado" | "perdido",
|
||||
@CurrentUser() user: EdenAuthenticatedUser,
|
||||
) {
|
||||
return this.quotes.changeDealStatus(id, dealStatus, user);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user