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

@@ -35,4 +35,15 @@ export class CreateResellerDto {
@IsOptional() @IsString() contactTechnicalName?: string;
@IsOptional() @IsEmail() contactTechnicalEmail?: string;
@IsOptional() @IsString() contactTechnicalPhone?: string;
// Representante legal — usado depois para confecção de contrato com a
// revenda (não bloqueado no PATCH genérico, ao contrário do equivalente
// em client_registrations: ver o cabeçalho da migration
// 1789700100000_resellers-legal-representative.cjs para o porquê.
@IsOptional() @IsString() repFullName?: string;
@IsOptional() @IsString() repCpf?: string;
@IsOptional() @IsString() repRole?: string;
@IsOptional() @IsEmail() repEmail?: string;
@IsOptional() @IsString() repMobilePhone?: string;
@IsOptional() @IsBoolean() repAuthorizationAck?: boolean;
}

View File

@@ -70,6 +70,12 @@ export class ResellersService {
contactTechnicalName: dto.contactTechnicalName,
contactTechnicalEmail: dto.contactTechnicalEmail,
contactTechnicalPhone: dto.contactTechnicalPhone,
repFullName: dto.repFullName,
repCpf: dto.repCpf,
repRole: dto.repRole,
repEmail: dto.repEmail,
repMobilePhone: dto.repMobilePhone,
repAuthorizationAck: dto.repAuthorizationAck,
});
await auditRepo.appendAuditEvent({
@@ -116,6 +122,12 @@ export class ResellersService {
contactTechnicalName: dto.contactTechnicalName,
contactTechnicalEmail: dto.contactTechnicalEmail,
contactTechnicalPhone: dto.contactTechnicalPhone,
repFullName: dto.repFullName,
repCpf: dto.repCpf,
repRole: dto.repRole,
repEmail: dto.repEmail,
repMobilePhone: dto.repMobilePhone,
repAuthorizationAck: dto.repAuthorizationAck,
});
if (!reseller) throw new NotFoundException("Revenda não encontrada");

View File

@@ -211,4 +211,32 @@ describe("resellers — CNPJ lookup autofill", () => {
.send({ willSign: true })
.expect(404);
});
it("sets and updates the legal representative fields, unlocked (unlike client_registrations' pj_rep_cpf)", async () => {
const id = await createReseller("qa-test- Revenda Representante");
const created = await agent(app)
.patch(`/api/v1/resellers/${id}`)
.set(...bearer(admin.accessToken))
.send({
repFullName: "qa-test- Representante Um",
repCpf: "52998224725",
repRole: "Diretor",
repEmail: "representante@example.com",
repMobilePhone: "11999990000",
repAuthorizationAck: true,
})
.expect(200);
expect(created.body.rep_full_name).toBe("qa-test- Representante Um");
expect(created.body.rep_cpf).toBe("52998224725");
expect(created.body.rep_authorization_ack).toBe(true);
const updated = await agent(app)
.patch(`/api/v1/resellers/${id}`)
.set(...bearer(admin.accessToken))
.send({ repCpf: "11144477735" })
.expect(200);
expect(updated.body.rep_cpf).toBe("11144477735");
expect(updated.body.rep_full_name).toBe("qa-test- Representante Um"); // untouched field survives COALESCE
});
});