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>
129 lines
6.3 KiB
JavaScript
129 lines
6.3 KiB
JavaScript
/**
|
|
* Customer 360 / Client Registration — eden.md §3 §1.1-1.2. Authenticated-
|
|
* only MVP (Master Prompt §6.5): deliberately out of scope for this round
|
|
* — the public unauthenticated token-based self-service form
|
|
* (`GET/POST /public-client-registration/:token`), file attachment
|
|
* upload/storage, CNPJ/CEP external lookup integration, the "official"
|
|
* `cnpj_address_*` Receita Federal address block, and the
|
|
* `*_externally_verified`/`*_verification_*` columns (only the format-
|
|
* check booleans `cpf_format_valid`/`cnpj_format_valid` are kept). Those
|
|
* all land in a later phase.
|
|
*
|
|
* `quote_id` is a plain nullable UUID WITHOUT a foreign key — the `quotes`
|
|
* table was built in a parallel migration; the FK linking the two is
|
|
* added in a follow-up migration once both are confirmed present (mirrors
|
|
* the legacy's own history: client_registration_id was itself added to
|
|
* quotes in a later migration, 1785513008756).
|
|
*/
|
|
|
|
exports.shorthands = undefined;
|
|
|
|
exports.up = (pgm) => {
|
|
pgm.sql(`
|
|
CREATE SEQUENCE client_registrations_client_code_seq START 1;
|
|
|
|
CREATE TABLE client_registrations (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
created_by UUID REFERENCES users(id),
|
|
quote_id UUID,
|
|
reseller_id UUID REFERENCES resellers(id),
|
|
token TEXT NOT NULL UNIQUE,
|
|
token_created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
client_code INTEGER NOT NULL UNIQUE DEFAULT nextval('client_registrations_client_code_seq'),
|
|
is_portability BOOLEAN NOT NULL DEFAULT false,
|
|
person_type TEXT NOT NULL CHECK (person_type IN ('pf','pj')),
|
|
registration_status TEXT NOT NULL DEFAULT 'rascunho' CHECK (registration_status IN ('rascunho','pendente_validacao','ativo','bloqueado','inativo')),
|
|
submitted_at TIMESTAMPTZ,
|
|
activated_at TIMESTAMPTZ,
|
|
internal_notes TEXT,
|
|
address_zip TEXT,
|
|
address_street TEXT,
|
|
address_number TEXT,
|
|
address_complement TEXT,
|
|
address_neighborhood TEXT,
|
|
address_city TEXT,
|
|
address_state TEXT,
|
|
address_country TEXT NOT NULL DEFAULT 'Brasil',
|
|
contact_principal_name TEXT,
|
|
contact_principal_email TEXT,
|
|
contact_principal_phone TEXT,
|
|
contact_financial_name TEXT,
|
|
contact_financial_email TEXT,
|
|
contact_financial_phone TEXT,
|
|
contact_technical_name TEXT,
|
|
contact_technical_email TEXT,
|
|
contact_technical_phone TEXT,
|
|
pf_full_name TEXT,
|
|
pf_cpf TEXT,
|
|
pf_birth_date DATE,
|
|
pf_email TEXT,
|
|
pf_mobile_phone TEXT,
|
|
pf_alt_phone TEXT,
|
|
pf_social_name TEXT,
|
|
pf_id_document TEXT,
|
|
pf_id_issuer TEXT,
|
|
pj_cnpj TEXT,
|
|
pj_company_name TEXT,
|
|
pj_trade_name TEXT,
|
|
pj_state_registration TEXT,
|
|
pj_municipal_registration TEXT,
|
|
pj_opening_date DATE,
|
|
pj_legal_nature TEXT,
|
|
pj_cnpj_situation TEXT,
|
|
pj_main_activity TEXT,
|
|
pj_rep_full_name TEXT,
|
|
pj_rep_cpf TEXT,
|
|
pj_rep_role TEXT,
|
|
pj_rep_email TEXT,
|
|
pj_rep_mobile_phone TEXT,
|
|
pj_rep_authorization_ack BOOLEAN NOT NULL DEFAULT false,
|
|
cpf_format_valid BOOLEAN,
|
|
cnpj_format_valid BOOLEAN,
|
|
portability_numbers JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
portability_ranges JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
ixc_client_id TEXT,
|
|
ixc_contract_number TEXT
|
|
);
|
|
COMMENT ON TABLE client_registrations IS
|
|
'Customer 360 / cadastro de cliente PF/PJ — eden.md §3. MVP autenticado apenas (sem fluxo público por token, sem anexos, sem lookup externo de CNPJ/CEP) — ver comentário no topo desta migration.';
|
|
COMMENT ON COLUMN client_registrations.quote_id IS
|
|
'Referencia quotes(id) sem FK por enquanto — FK a ser adicionada em migration futura.';
|
|
COMMENT ON COLUMN client_registrations.pf_cpf IS
|
|
'Imutável fora do fluxo de submissão original — nunca exposto em rota de edição administrativa (eden.md §1.1.2). Mesma regra para pj_cnpj e pj_rep_cpf.';
|
|
|
|
CREATE TRIGGER trg_client_registrations_updated_at BEFORE UPDATE ON client_registrations
|
|
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
|
|
|
CREATE INDEX idx_client_registrations_status ON client_registrations(registration_status);
|
|
CREATE INDEX idx_client_registrations_reseller ON client_registrations(reseller_id);
|
|
CREATE INDEX idx_client_registrations_created_by ON client_registrations(created_by);
|
|
|
|
CREATE TABLE client_registration_partners (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
client_registration_id UUID NOT NULL REFERENCES client_registrations(id) ON DELETE CASCADE,
|
|
name TEXT NOT NULL,
|
|
document TEXT,
|
|
qualification TEXT,
|
|
source TEXT NOT NULL DEFAULT 'manual',
|
|
cpf TEXT,
|
|
will_sign BOOLEAN NOT NULL DEFAULT false,
|
|
signature_email TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
COMMENT ON TABLE client_registration_partners IS
|
|
'Sócios/QSA — eden.md §1.2, §4. source default "manual" (não "brasilapi" como no legado) — sem integração de lookup de CNPJ nesta fase.';
|
|
|
|
CREATE INDEX idx_client_registration_partners_registration ON client_registration_partners(client_registration_id);
|
|
`);
|
|
};
|
|
|
|
exports.down = (pgm) => {
|
|
pgm.sql(`
|
|
DROP TABLE IF EXISTS client_registration_partners;
|
|
DROP TABLE IF EXISTS client_registrations;
|
|
DROP SEQUENCE IF EXISTS client_registrations_client_code_seq;
|
|
`);
|
|
};
|