feat(resellers): campos de representante legal (preparação p/ contrato)

Usuário: o cadastro de revenda precisa ficar completo porque essa
mesma base vai alimentar a criação de contrato com a revenda dentro
do ERP. resellers ganha rep_full_name/rep_cpf/rep_role/rep_email/
rep_mobile_phone/rep_authorization_ack, mesmo shape de
client_registrations.pj_rep_* — um contrato precisa de um signatário
nomeado e autorizado, não só a lista geral de sócios.

Divergência deliberada: ao contrário de pj_rep_cpf (bloqueado do PATCH
genérico em client_registrations, esperando um fluxo de submissão
pública que o EDEN ainda não construiu — hoje uma lacuna conhecida,
nunca preenchido de fato), rep_cpf aqui fica livremente editável —
revenda não tem esse fluxo de submissão pública pra travar atrás.

Nova tela: card "Representante legal" entre "Dados da revenda" e
"Sócios". Suíte: 107/107 (106→107). pnpm turbo run typecheck build:
28/28. Testado ao vivo em navegador real.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-18 15:41:16 -03:00
parent ec13dccee6
commit d81553fcfe
8 changed files with 240 additions and 4 deletions

View File

@@ -0,0 +1,49 @@
/**
* Representante legal da revenda — mirrors `client_registrations.pj_rep_*`
* (packages/database/migrations/1788447739229_client-registrations.cjs).
* Motivation (user, 2026-09-18): reseller registration data will later
* feed reseller contract creation inside the ERP, same as
* client_registrations already does for client contracts — a contract
* needs a named, authorized signer for the legal entity, not just the
* general Sócios list (`reseller_partners`, added in
* 1789700000000_resellers-expansion.cjs).
*
* Deliberate deviation from `client_registrations.pj_rep_cpf`: there it's
* structurally excluded from the generic admin PATCH (`UpdateClientRegistrationDto`)
* because in the legacy it's only ever set during the public self-service
* submission flow, which EDEN hasn't built yet — so it stays permanently
* unset there today, a known pre-existing gap, not something to replicate.
* Resellers have no such public submission flow (always backoffice-created)
* and no "original submission" moment to lock behind, so `rep_cpf` here is
* a plain, freely-PATCH-able column like every other reseller field — see
* `resellers.service.ts`, no new DTO exclusion added for it.
*/
exports.shorthands = undefined;
exports.up = (pgm) => {
pgm.sql(`
ALTER TABLE resellers
ADD COLUMN rep_full_name TEXT,
ADD COLUMN rep_cpf TEXT,
ADD COLUMN rep_role TEXT,
ADD COLUMN rep_email TEXT,
ADD COLUMN rep_mobile_phone TEXT,
ADD COLUMN rep_authorization_ack BOOLEAN NOT NULL DEFAULT false;
COMMENT ON COLUMN resellers.rep_authorization_ack IS
'Confirmação de que o representante está autorizado a agir pela revenda — mesmo campo de intenção que client_registrations.pj_rep_authorization_ack, sem o mesmo bloqueio estrutural de edição (ver cabeçalho da migration).';
`);
};
exports.down = (pgm) => {
pgm.sql(`
ALTER TABLE resellers
DROP COLUMN IF EXISTS rep_full_name,
DROP COLUMN IF EXISTS rep_cpf,
DROP COLUMN IF EXISTS rep_role,
DROP COLUMN IF EXISTS rep_email,
DROP COLUMN IF EXISTS rep_mobile_phone,
DROP COLUMN IF EXISTS rep_authorization_ack;
`);
};

View File

@@ -33,6 +33,12 @@ export interface Reseller {
contact_technical_name: string | null;
contact_technical_email: string | null;
contact_technical_phone: string | null;
rep_full_name: string | null;
rep_cpf: string | null;
rep_role: string | null;
rep_email: string | null;
rep_mobile_phone: string | null;
rep_authorization_ack: boolean;
}
export async function listResellers(includeInactive = false): Promise<Reseller[]> {
@@ -79,6 +85,12 @@ export interface CreateResellerInput {
contactTechnicalName?: string | null;
contactTechnicalEmail?: string | null;
contactTechnicalPhone?: string | null;
repFullName?: string | null;
repCpf?: string | null;
repRole?: string | null;
repEmail?: string | null;
repMobilePhone?: string | null;
repAuthorizationAck?: boolean;
}
export async function createReseller(input: CreateResellerInput): Promise<Reseller> {
@@ -91,7 +103,8 @@ export async function createReseller(input: CreateResellerInput): Promise<Resell
address_neighborhood, address_city, address_state, address_country,
contact_principal_name, contact_principal_email, contact_principal_phone,
contact_financial_name, contact_financial_email, contact_financial_phone,
contact_technical_name, contact_technical_email, contact_technical_phone
contact_technical_name, contact_technical_email, contact_technical_phone,
rep_full_name, rep_cpf, rep_role, rep_email, rep_mobile_phone, rep_authorization_ack
)
VALUES (
$1, $2, $3, COALESCE($4, true), $5,
@@ -101,7 +114,8 @@ export async function createReseller(input: CreateResellerInput): Promise<Resell
$17, $18, $19, COALESCE($20, 'Brasil'),
$21, $22, $23,
$24, $25, $26,
$27, $28, $29
$27, $28, $29,
$30, $31, $32, $33, $34, COALESCE($35, false)
)
RETURNING *`,
[
@@ -134,6 +148,12 @@ export async function createReseller(input: CreateResellerInput): Promise<Resell
input.contactTechnicalName ?? null,
input.contactTechnicalEmail ?? null,
input.contactTechnicalPhone ?? null,
input.repFullName ?? null,
input.repCpf ?? null,
input.repRole ?? null,
input.repEmail ?? null,
input.repMobilePhone ?? null,
input.repAuthorizationAck ?? null,
],
);
const reseller = rows[0];
@@ -176,7 +196,13 @@ export async function updateReseller(
contact_financial_phone = COALESCE($26, contact_financial_phone),
contact_technical_name = COALESCE($27, contact_technical_name),
contact_technical_email = COALESCE($28, contact_technical_email),
contact_technical_phone = COALESCE($29, contact_technical_phone)
contact_technical_phone = COALESCE($29, contact_technical_phone),
rep_full_name = COALESCE($30, rep_full_name),
rep_cpf = COALESCE($31, rep_cpf),
rep_role = COALESCE($32, rep_role),
rep_email = COALESCE($33, rep_email),
rep_mobile_phone = COALESCE($34, rep_mobile_phone),
rep_authorization_ack = COALESCE($35, rep_authorization_ack)
WHERE id = $1
RETURNING *`,
[
@@ -209,6 +235,12 @@ export async function updateReseller(
input.contactTechnicalName ?? null,
input.contactTechnicalEmail ?? null,
input.contactTechnicalPhone ?? null,
input.repFullName ?? null,
input.repCpf ?? null,
input.repRole ?? null,
input.repEmail ?? null,
input.repMobilePhone ?? null,
input.repAuthorizationAck ?? null,
],
);
return rows[0] ?? null;