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:
@@ -0,0 +1,107 @@
|
||||
import { Type } from "class-transformer";
|
||||
import {
|
||||
IsArray,
|
||||
IsBoolean,
|
||||
IsDateString,
|
||||
IsEmail,
|
||||
IsIn,
|
||||
IsOptional,
|
||||
IsString,
|
||||
ValidateNested,
|
||||
} from "class-validator";
|
||||
|
||||
export const REGISTRATION_STATUSES = [
|
||||
"rascunho",
|
||||
"pendente_validacao",
|
||||
"ativo",
|
||||
"bloqueado",
|
||||
"inativo",
|
||||
] as const;
|
||||
export type RegistrationStatus = (typeof REGISTRATION_STATUSES)[number];
|
||||
|
||||
export class PortabilityRangeDto {
|
||||
@IsString()
|
||||
first!: string;
|
||||
|
||||
@IsString()
|
||||
last!: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Administratively editable fields per eden.md §1.1.2 — and *only* those.
|
||||
* `pfCpf`, `pjCnpj`, and `pjRepCpf` are deliberately not declared here:
|
||||
* with the global ValidationPipe's `forbidNonWhitelisted` (main.ts), any
|
||||
* request that tries to send them is rejected outright rather than
|
||||
* silently ignored. Changing the document itself would need a dedicated
|
||||
* re-verification flow — never a side effect of a generic admin PATCH.
|
||||
*
|
||||
* `registrationStatus` is validated as one of the 5 legal *values* here;
|
||||
* full state-machine transition legality (e.g. blocking `rascunho`→`ativo`
|
||||
* directly) is NOT enforced in this MVP — matches the legacy, which also
|
||||
* has no dedicated transition endpoint (eden.md §1.1.1: "não há endpoint
|
||||
* dedicado de transição", just this same generic PATCH).
|
||||
*/
|
||||
export class UpdateClientRegistrationDto {
|
||||
@IsOptional()
|
||||
@IsIn(REGISTRATION_STATUSES)
|
||||
registrationStatus?: RegistrationStatus;
|
||||
|
||||
@IsOptional() @IsString() internalNotes?: string;
|
||||
@IsOptional() @IsString() ixcClientId?: string;
|
||||
@IsOptional() @IsString() ixcContractNumber?: string;
|
||||
|
||||
@IsOptional() @IsString() addressZip?: string;
|
||||
@IsOptional() @IsString() addressStreet?: string;
|
||||
@IsOptional() @IsString() addressNumber?: string;
|
||||
@IsOptional() @IsString() addressComplement?: string;
|
||||
@IsOptional() @IsString() addressNeighborhood?: string;
|
||||
@IsOptional() @IsString() addressCity?: string;
|
||||
@IsOptional() @IsString() addressState?: string;
|
||||
@IsOptional() @IsString() addressCountry?: string;
|
||||
|
||||
@IsOptional() @IsString() contactPrincipalName?: string;
|
||||
@IsOptional() @IsEmail() contactPrincipalEmail?: string;
|
||||
@IsOptional() @IsString() contactPrincipalPhone?: string;
|
||||
@IsOptional() @IsString() contactFinancialName?: string;
|
||||
@IsOptional() @IsEmail() contactFinancialEmail?: string;
|
||||
@IsOptional() @IsString() contactFinancialPhone?: string;
|
||||
@IsOptional() @IsString() contactTechnicalName?: string;
|
||||
@IsOptional() @IsEmail() contactTechnicalEmail?: string;
|
||||
@IsOptional() @IsString() contactTechnicalPhone?: string;
|
||||
|
||||
// PF — pfCpf intentionally absent, see class comment.
|
||||
@IsOptional() @IsString() pfFullName?: string;
|
||||
@IsOptional() @IsDateString() pfBirthDate?: string;
|
||||
@IsOptional() @IsEmail() pfEmail?: string;
|
||||
@IsOptional() @IsString() pfMobilePhone?: string;
|
||||
@IsOptional() @IsString() pfAltPhone?: string;
|
||||
@IsOptional() @IsString() pfSocialName?: string;
|
||||
@IsOptional() @IsString() pfIdDocument?: string;
|
||||
@IsOptional() @IsString() pfIdIssuer?: string;
|
||||
|
||||
// PJ — pjCnpj and pjRepCpf intentionally absent, see class comment.
|
||||
@IsOptional() @IsString() pjCompanyName?: string;
|
||||
@IsOptional() @IsString() pjTradeName?: string;
|
||||
@IsOptional() @IsString() pjStateRegistration?: string;
|
||||
@IsOptional() @IsString() pjMunicipalRegistration?: string;
|
||||
@IsOptional() @IsDateString() pjOpeningDate?: string;
|
||||
@IsOptional() @IsString() pjLegalNature?: string;
|
||||
@IsOptional() @IsString() pjCnpjSituation?: string;
|
||||
@IsOptional() @IsString() pjMainActivity?: string;
|
||||
@IsOptional() @IsString() pjRepFullName?: string;
|
||||
@IsOptional() @IsString() pjRepRole?: string;
|
||||
@IsOptional() @IsEmail() pjRepEmail?: string;
|
||||
@IsOptional() @IsString() pjRepMobilePhone?: string;
|
||||
@IsOptional() @IsBoolean() pjRepAuthorizationAck?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
portabilityNumbers?: string[];
|
||||
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => PortabilityRangeDto)
|
||||
portabilityRanges?: PortabilityRangeDto[];
|
||||
}
|
||||
Reference in New Issue
Block a user