Contexto
O cliente tem fornecedores pessoa física, pagos por RPA. Hoje o cadastro de Fornecedor só aceita CNPJ, então esse fornecedor não entra no sistema.
O cálculo do RPA já existe: o Contas a Pagar ativa o motor fiscal para documento de RPA (campos de imposto abertos, bruto/líquido, obrigações tributárias). Esta issue é só o cadastro.
Colaborador e Fornecedor seguem separados: têm função e significado diferentes na operação do cliente. O mesmo CPF existir nos dois cadastros é aceito.
Estado atual (origin/dev)
domain/supplier/types.ts: cnpj: Cnpj (VO do kernel, parse exige 14 caracteres).
par_suppliers.cnpj: cnpjKey = varchar(14), UNIQUE par_suppliers_cnpj_idx. corporate_name e fantasy_name são NOT NULL.
supplier-schemas.ts:131: cnpj: z.string().length(14) (create e update).
supplier.mapper.ts:84: reidrata com Cnpj.parse, então uma linha com CPF quebraria na leitura.
Cpf já existe no kernel (shared/kernel/cpf.ts).
- O CNAB já está pronto para CPF.
financial/domain/payout/inscription.ts:83 deriva o tipo de inscrição do tamanho (11 → 1, senão 2), e o read-model financeiro já usa document (varchar(20)).
Desenho
1. Domínio: SupplierDocument (no módulo partners, não no kernel)
type SupplierDocument =
| { kind: 'cpf'; value: Cpf } // PF
| { kind: 'cnpj'; value: Cnpj } // PJ
parse: qualquer letra ou 14 caracteres usa Cnpj.parse; 11 dígitos usa Cpf.parse; outro tamanho é recusado com invalid-supplier-document.
- O
Cnpj do kernel não muda: Financiador e Acordo continuam usando ele.
- O tipo (PF/PJ) não é gravado. Ele é derivado do documento, porque um campo separado permitiria gravar PF com um CNPJ.
2. Invariante PF × PJ
|
PF (CPF) |
PJ (CNPJ) |
name |
nome completo, obrigatório |
como hoje |
corporateName |
não existe (null) |
obrigatório |
fantasyName |
não existe (null) |
obrigatório |
- PF com
corporateName/fantasyName preenchido é recusado (422), não ignorado: supplier-corporate-name-not-allowed-for-pf e supplier-fantasy-name-not-allowed-for-pf.
- Trocar PF ↔ PJ na edição é mudança do campo sensível e exige a mesma permissão de hoje (
supplier:edit-sensitive). Ao virar PJ, corporateName e fantasyName passam a ser obrigatórios.
3. Migração 0020
- Renomear
cnpj para document (continua varchar(14)) e o índice para par_suppliers_document_idx. CPF (11) e CNPJ (14) nunca colidem no índice.
corporate_name e fantasy_name passam a aceitar NULL, com CHECK amarrado ao tamanho do documento:
CHECK ((CHAR_LENGTH(document) = 11) = (corporate_name IS NULL)), e o mesmo para fantasy_name.
- Sem backfill: toda linha atual tem CNPJ de 14 caracteres.
- O mapper passa a reidratar com
SupplierDocument.parse. A detecção de duplicata (supplier-repository.drizzle.ts:34-43) passa a olhar o novo nome do índice.
4. HTTP
- Entrada (POST e PUT):
document com 11 ou 14 caracteres. cnpj continua aceito como alias deprecated por um ciclo, para o backend subir antes do front sem quebrar a tela atual.
- Resposta (detalhe e lista):
document e personType: 'PF' | 'PJ' (derivado, não gravado). cnpj continua na resposta como alias deprecated por um ciclo.
- Códigos de erro:
invalid-cnpj vira invalid-supplier-document.
register-supplier-cnpj-duplicate, supplier-cnpj-duplicate e edit-supplier-cnpj-duplicate viram *-document-duplicate.
- Entram os dois códigos de PF da seção 2.
- Busca da lista: passa a encontrar também por CPF.
- Batch (
suppliers:batch): já usa taxId, sem mudança.
5. O que depende do fornecedor
- Evento
SupplierRegistered: o campo cnpj vira document. Consumidores aceitam as duas formas enquanto houver eventos antigos no outbox.
- Preenchimento pela leitura da nota (
contractor-read.drizzle.ts:136, find-supplier-by-cnpj.ts): a busca por documento tenta CPF também.
- CSV (
supplier-csv.ts): cabeçalho "CPF/CNPJ".
- CNAB/VAN: sem mudança de código. Entram testes com favorecido CPF em transferência, boleto (J-52) e Pix.
- Documentação: ADR novo com
supersedes parcial do ADR-0031 (o campo sensível do Supplier deixa de ser cnpj).
6. Legado
Verificar se a ETL de fornecedores recusou registros por não serem CNPJ. Se recusou, recarregar esses fornecedores depois desta entrega.
Critérios de aceite
Front
Spec web-app specs/117-fornecedor-pessoa-fisica. Na tela, uma chave segmentada Pessoa Jurídica | Pessoa Física controla o rótulo, a máscara e os campos. O front consome personType em Contratos e no Lançar Documento, e só vai para produção depois desta issue.
Contexto
O cliente tem fornecedores pessoa física, pagos por RPA. Hoje o cadastro de Fornecedor só aceita CNPJ, então esse fornecedor não entra no sistema.
O cálculo do RPA já existe: o Contas a Pagar ativa o motor fiscal para documento de RPA (campos de imposto abertos, bruto/líquido, obrigações tributárias). Esta issue é só o cadastro.
Colaborador e Fornecedor seguem separados: têm função e significado diferentes na operação do cliente. O mesmo CPF existir nos dois cadastros é aceito.
Estado atual (
origin/dev)domain/supplier/types.ts:cnpj: Cnpj(VO do kernel,parseexige 14 caracteres).par_suppliers.cnpj:cnpjKey=varchar(14),UNIQUE par_suppliers_cnpj_idx.corporate_nameefantasy_namesãoNOT NULL.supplier-schemas.ts:131:cnpj: z.string().length(14)(create e update).supplier.mapper.ts:84: reidrata comCnpj.parse, então uma linha com CPF quebraria na leitura.Cpfjá existe no kernel (shared/kernel/cpf.ts).financial/domain/payout/inscription.ts:83deriva o tipo de inscrição do tamanho (11 →1, senão2), e o read-model financeiro já usadocument(varchar(20)).Desenho
1. Domínio:
SupplierDocument(no módulopartners, não no kernel)parse: qualquer letra ou 14 caracteres usaCnpj.parse; 11 dígitos usaCpf.parse; outro tamanho é recusado cominvalid-supplier-document.Cnpjdo kernel não muda: Financiador e Acordo continuam usando ele.2. Invariante PF × PJ
namecorporateNamenull)fantasyNamenull)corporateName/fantasyNamepreenchido é recusado (422), não ignorado:supplier-corporate-name-not-allowed-for-pfesupplier-fantasy-name-not-allowed-for-pf.supplier:edit-sensitive). Ao virar PJ,corporateNameefantasyNamepassam a ser obrigatórios.3. Migração
0020cnpjparadocument(continuavarchar(14)) e o índice parapar_suppliers_document_idx. CPF (11) e CNPJ (14) nunca colidem no índice.corporate_nameefantasy_namepassam a aceitarNULL, com CHECK amarrado ao tamanho do documento:CHECK ((CHAR_LENGTH(document) = 11) = (corporate_name IS NULL)), e o mesmo parafantasy_name.SupplierDocument.parse. A detecção de duplicata (supplier-repository.drizzle.ts:34-43) passa a olhar o novo nome do índice.4. HTTP
documentcom 11 ou 14 caracteres.cnpjcontinua aceito como alias deprecated por um ciclo, para o backend subir antes do front sem quebrar a tela atual.documentepersonType: 'PF' | 'PJ'(derivado, não gravado).cnpjcontinua na resposta como alias deprecated por um ciclo.invalid-cnpjvirainvalid-supplier-document.register-supplier-cnpj-duplicate,supplier-cnpj-duplicateeedit-supplier-cnpj-duplicateviram*-document-duplicate.suppliers:batch): já usataxId, sem mudança.5. O que depende do fornecedor
SupplierRegistered: o campocnpjviradocument. Consumidores aceitam as duas formas enquanto houver eventos antigos no outbox.contractor-read.drizzle.ts:136,find-supplier-by-cnpj.ts): a busca por documento tenta CPF também.supplier-csv.ts): cabeçalho "CPF/CNPJ".supersedesparcial do ADR-0031 (o campo sensível do Supplier deixa de sercnpj).6. Legado
Verificar se a ETL de fornecedores recusou registros por não serem CNPJ. Se recusou, recarregar esses fornecedores depois desta entrega.
Critérios de aceite
corporateName/fantasyNamecria o fornecedor (personType: 'PF').invalid-supplier-document.corporateName→ 422supplier-corporate-name-not-allowed-for-pf.corporateName→ 422, como hoje.cnpj(alias) continua funcionando.register-supplier-document-duplicate.documentepersonType.supplier:edit-sensitive→ recusado.1.Front
Spec
web-appspecs/117-fornecedor-pessoa-fisica. Na tela, uma chave segmentada Pessoa Jurídica | Pessoa Física controla o rótulo, a máscara e os campos. O front consomepersonTypeem Contratos e no Lançar Documento, e só vai para produção depois desta issue.