Ponto de partida
Oito decisões
São dois conceitos e um deles já existe. Ship-to é o CNPJ que responde pela operação — é o Establishment, com IE, icmsTaxpayerStatus, Suframa e código IBGE. Deliver-to é o destino: doca, almoxarifado, andar, obra. Só o segundo nasce aqui. establishmentId é obrigatório e imutável — um ponto de entrega não muda de destinatário fiscal, isso seria outro ponto de entrega.
Endereço próprio é opcional; ausente, herda o do estabelecimento. É o que faz um conceito só cobrir os quatro casos reais: doca e setor não têm endereço (herdam), CD alugado e canteiro de obra têm. Sem essa opcionalidade seriam duas entidades — uma de rótulo interno e outra de endereço — e toda consulta teria que olhar nas duas.
Dois fatos, dois campos. Quando outro CNPJ ou CPF recebe a mercadoria no lugar do destinatário, a operação é triangular em qualquer UF, e deliveryOperationType é obrigatório, com os casos que a legislação nomeia. Interestadual é derivado — UF de entrega diferente da do estabelecimento — e nunca passa calado: ou há recebedor distinto com a operação declarada, ou é canteiro de obra cadastrado com centro de custo, ou leva justificativa escrita. Sem nenhum dos três, 422. No endereço eventual o atalho do canteiro não vale: sem recebedor distinto, só a justificativa explica.
CNPJ de recebedor no ponto de entrega só quando outro contribuinte recebe. Industrializador, armazém geral, cliente em venda à ordem. CNPJ nosso é Establishment — cadastrar filial como ponto de entrega é onde IE, Suframa e situação cadastral somem, e é exatamente o erro que a spec de Empresa já nomeia. A única entrada para CNPJ do próprio grupo é a industrialização ou o armazém geral feitos por outra empresa do grupo (RN-DL-06).
Endereço eventual existe, mas nunca vira linha de catálogo. Vive só no snapshot do documento, exige permissão própria e obedece à regra interestadual sem o atalho do canteiro: sem recebedor distinto, a justificativa é obrigatória. E o catálogo se defende sozinho: a tela de administração lista os endereços eventuais repetidos com a contagem de uso, para virarem cadastro por um clique.
Cabeçalho define o padrão, linha sobrescreve — e o destinatário é um só. Todas as linhas de um documento entregam em locais do mesmo establishmentId do documento. Destinatário diferente é outro documento, não outra linha. É a regra da Oracle, e é a que evita nota impossível de emitir.
Obra é nó de centro de custo; aqui mora só o endereço dela. O CostCenter já ganhou vigência própria para obra e projeto. Modelar obra de novo aqui daria duas verdades sobre a mesma coisa. O vínculo é um defaultCostCenterId opcional que sugere o rateio — e nunca o contrário: centro de custo não ganha endereço.
A linha congela endereço, UF, recebedor e operação; a chave estrangeira é só rastro. Regra 1 do mapa de domínio, sem exceção. Renomear a obra, corrigir o CEP ou encerrar o ponto de entrega não pode reescrever o que a nota de seis meses atrás dizia — e a UF congelada é o que torna a conferência fiscal reproduzível.
Fundamento
Por que local de entrega é dimensão fiscal, e não campo de logística
A NF-e separa quem responde de onde chega — e o segundo grupo não transfere jurisdição
O leiaute tem o grupo dest, que identifica o destinatário da operação, e o grupo G · entrega, que identifica o local de entrega quando ele difere. Desde a NT 2018.005 esse grupo carrega CNPJ ou CPF, razão social do recebedor, endereço completo, município, UF, CEP, país, telefone, e-mail e inscrição estadual — e ganhou rejeições próprias para código de país e IE inválidos. É um grupo rico justamente porque a Receita quer o dado, não porque ele seja decorativo.
O que ele não faz é mudar de quem é a operação. Quem decide alíquota é o destinatário jurídico, o CNPJ que consta como dest.
E o critério de operação interna ou interestadual é a circulação física
As Respostas à Consulta da SEFAZ/SP são consistentes: o que define se a operação é interna ou interestadual é o fluxo físico efetivo da mercadoria, não o endereço cadastral do destinatário. A nota deve ser emitida para o adquirente com o endereço onde a entrega vai ocorrer, e o Grupo G preenchido quando o local difere do cadastro.
As duas coisas juntas produzem a única regra que este documento precisa impor
Entregar em outra UF mantendo o destinatário original não produz tratamento interestadual correto — produz divergência entre a alíquota aplicada e o destino real, que é o padrão que o fisco procura. O caminho certo é um dos três: o CNPJ que recebe passa a ser o destinatário; a operação usa uma triangulação que a legislação nomeia — industrialização por encomenda, armazém geral, venda à ordem ou remessa por conta e ordem de terceiros, todas com uma segunda NF-e que está fora do escopo do Nexio; ou a entrega acontece na mesma UF.
A construção civil tem uma dessas triangulações escrita: o RICMS/SP (Anexo XI, art. 4º, §3º) admite a compra com entrega direta no canteiro, sem passar pelo estabelecimento principal. É legislação estadual e varia por UF — o Nexio registra a operação declarada, a justificativa e a UF efetiva; quem responde pela adequação é a assessoria fiscal do cliente.
O que muda de preço quando a UF muda
Alíquota interestadual de ICMS, DIFAL de destino, FCP e os convênios de substituição tributária — todos dependem da UF de destino, e todos entram no preço que o fornecedor cota. Um sistema de compras que trata destino como texto livre não consegue nem comparar duas propostas de UFs diferentes, nem conferir a nota contra o pedido. É por isso que stateCode é campo derivado do código IBGE e congelado na linha, exatamente como já é no Establishment.
O que esta spec deliberadamente não é. Não é motor de cálculo fiscal. O Nexio não apura ICMS, não resolve DIFAL e não decide CFOP. O que ele faz é garantir que a dimensão exista, seja obrigatória onde importa, esteja congelada no documento e chegue íntegra a quem calcula — que é o ERP fiscal. Registrar com precisão o fato é o escopo; tributá-lo não é.
Modelo
Mapa de entidades
Establishment, tem endereço próprio opcional, vigência, contato, janela de recebimento e instruções de acesso.
icmsTaxpayerStatus, Suframa, endereço fiscal e allowsReceiving. Nada é remodelado aqui.
defaultCostCenterId, que sugere o rateio quando a entrega é naquele ponto.
shipToLocationId vira shipToStateCode. Preço e prazo variam por UF, não por doca — e a UF já viaja congelada na linha, então o cadastro de fornecimento não precisa conhecer o ponto. Preço por sítio específico é do Contract.
Convenção de nomes. O documento é escrito em português e todo identificador é em inglês — entidade, atributo, valor de enum, rota, permissão e código de erro. A exceção são os dados de negócio brasileiros, que ficam como são no mundo real: cnpj, stateRegistration, cityIbgeCode, suframaCode.
Uma tabela só: admin.delivery_locations. Índices: code único por (tenantId, companyId) só entre os registros não excluídos — requisito; exemplo de implementação: índice único parcial filtrando isDeleted = false, ou, sem índice parcial, coluna gerada que só é preenchida nos não excluídos, com índice único sobre ela; um em (tenantId, establishmentId, isActive) para o lookup; e um em (tenantId, stateCode), que é o que a cascata de preço do ItemSupplier consulta.
Domínio
Campos · DeliveryLocation
Legenda: obrig sempre · cond conforme a operação · opc opcional · gerado calculado pelo sistema ou pelo banco.
Identificação
| Campo | Tipo | Regra | |
|---|---|---|---|
| id | Guid | gerado | Atribuído pelo agregado, não pelo banco. |
| tenantId | Guid | gerado | Do token, nunca aceito do cliente. |
| establishmentId | Guid | obrig | Quem recebe fiscalmente. Imutável — mudar de destinatário é criar outro ponto de entrega, porque todo documento já emitido congelou este valor. O estabelecimento precisa ter allowsReceiving = true. |
| companyId | Guid | gerado | Derivado do estabelecimento e denormalizado. Existe para o escopo por empresa caber no filtro de linha sem join — mesmo padrão do Establishment. |
| code | string(20) | obrig | Único por empresa, imutável, normalizado em maiúsculas. CD-REC, OBRA-01, DOCA-3. Por empresa e não por estabelecimento porque é assim que a pessoa fala dele. |
| name | string(160) | obrig | Como o lugar é chamado. É o que aparece no seletor, sob o nome do estabelecimento. |
| type | enum | obrig | OwnSite · Warehouse · ConstructionSite · ThirdParty. Não é decorativo: ThirdParty é o único tipo que aceita recebedor — e o exige, com deliveryOperationType; ConstructionSite é o único que explica sozinho a entrega interestadual sem recebedor distinto, com centro de custo. |
| externalCode | string(40) | opc | O código do local no ERP. Único por (tenant, empresa) quando preenchido. |
Endereço · ausente, herda o do estabelecimento
| Campo | Tipo | Regra | |
|---|---|---|---|
| hasOwnAddress | bool | gerado | Verdadeiro quando zipCode está preenchido. Existe como campo derivado para a consulta não testar seis colunas — e para a tela dizer "herda o endereço da filial" em vez de mostrar campos vazios. |
| zipCode | varchar(8) | cond | Sem máscara. Preenchido, obriga o bloco inteiro; vazio, o bloco inteiro tem que estar vazio. Nunca endereço pela metade — 422. |
| street · number · complement · district | string | cond | Logradouro e bairro obrigatórios com o bloco ativo; número aceita S/N, que é o caso normal de canteiro de obra. |
| cityIbgeCode | varchar(7) | cond | Código do município, não o nome. É o que a NF-e carrega no Grupo G e o que resolve homônimo entre estados. O nome é derivado dele, nunca digitado livre. |
| stateCode | char(2) | gerado | Derivado do código IBGE quando há endereço próprio; copiado do estabelecimento quando não há. É a dimensão que muda o ICMS — por isso nunca é digitada em separado e nunca fica nula. |
| countryCode | char(2) | opc | ISO 3166-1, padrão BR. Reservado; entrega fora do Brasil está fora de escopo. |
Fiscal
| Campo | Tipo | Regra | |
|---|---|---|---|
| isInterstate | bool | gerado | Verdadeiro quando stateCode difere do stateCode do estabelecimento. Nunca digitado; recalculado pela RN-DL-19 quando o estabelecimento muda de UF. Ver § Recebedor distinto e entrega interestadual. |
| deliveryOperationType | enum? | cond | Obrigatório com type = ThirdParty, em qualquer UF, e recusado nos outros tipos. ThirdPartyProcessing · GeneralWarehouse · DirectToCustomer · Other (RN-DL-20). |
| deliveryOperationNote | string(400)? | cond | Obrigatória quando a operação é Other, recusada nas outras. |
| interstateNote | string(400)? | cond | Obrigatória quando isInterstate, sem recebedor distinto e fora de canteiro; opcional em canteiro interestadual cadastrado — no endereço eventual, obrigatória também em canteiro (RN-DL-12); recusada sem UF divergente e com recebedor distinto (RN-DL-05). É o campo que a auditoria lê primeiro. |
| recipientCnpj | varchar(14)? | cond | Obrigatório com type = ThirdParty e recusado nos outros tipos. CNPJ com a raiz de uma Company do tenant — cadastrado como Establishment ou não — é recusado, e a mensagem manda usá-lo, ou cadastrá-lo, como destinatário; salvo raiz de outra empresa do tenant em ThirdPartyProcessing ou GeneralWarehouse (RN-DL-06). VARCHAR, nunca numérico: CNPJ alfanumérico está em produção desde 31/07/2026. Normalizado antes de validar e de comparar, no mesmo padrão da Empresa: trim, sem máscara (saem ., / e -), letras em maiúsculas; depois, 14 caracteres — 12 em 0–9A–Z e 2 dígitos verificadores — e DV conferido com ASCII − 48. Formato ou DV inválido é 400 DELIVERY_LOCATION_RECIPIENT_CNPJ_INVALID. A comparação é feita entre os 8 primeiros caracteres do valor normalizado e o cnpjRoot das empresas do tenant. |
| recipientName | string(160)? | cond | Razão social do recebedor. Obrigatória com recipientCnpj. Vai para o xNome do Grupo G da NF-e. |
| recipientStateRegistration | varchar(20)? | opc | IE do recebedor. A NT 2018.005 criou rejeição própria para IE inválida no grupo de entrega — o campo existe para que ela nunca seja disparada. |
Operação · o que faz valer a pena ser entidade
| Campo | Tipo | Regra | |
|---|---|---|---|
| contactName · contactPhone · contactEmail | string | opc | Quem recebe no local. É o attention line da Coupa, e é o nome que o motorista procura no portão. |
| receivingHours | string(120)? | opc | Janela de recebimento como texto exibível — Seg a Sex, 08:00–11:30 e 13:00–16:30. Texto e não agenda: agendamento de doca é outro produto, e uma agenda que ninguém mantém é pior que uma frase certa. |
| deliveryInstructions | text? | opc | Acesso, portaria, restrição de veículo, exigência de EPI. Sai impressa no PDF do pedido — é a razão pela qual o fornecedor acerta a entrega na primeira tentativa. |
| requiresScheduling | bool | opc | Padrão false. Verdadeiro, o pedido sai com aviso de agendamento obrigatório e o recebimento sem agendamento gera divergência, não bloqueio. |
| defaultCostCenterId | Guid? | opc | Sugere o rateio quando a entrega é aqui. Sugestão, nunca imposição — o rateio continua sendo da linha. Ver § Obra e centro de custo. |
Vigência e estado
| Campo | Tipo | Regra | |
|---|---|---|---|
| validFrom | date | obrig | Padrão hoje. Mesmo padrão de CostCenterAssignment. |
| validTo | date? | opc | Obra acaba. Nulo é o caso comum. Vencido, o ponto some do seletor sem que nenhum documento antigo mude de significado. |
| isActive | bool | gerado | Só por /activate e /deactivate; PUT nunca muda. Inativo, o ponto não entra em documento novo; documento em andamento conclui com o que congelou (RN-DL-15). |
| version | int | gerado | Token de concorrência — coluna inteira comum, incrementada a cada gravação. PUT, /activate, /deactivate e DELETE exigem expectedVersion; divergência é 409 DELIVERY_LOCATION_CONCURRENT_CHANGE_CONFLICT. A revalidação da RN-DL-19 também incrementa: quem editava o ponto enquanto a sede mudava relê antes de gravar. |
Invariante fiscal
Recebedor distinto e entrega interestadual
Duas perguntas, feitas nos três lugares em que um endereço de entrega nasce — ao salvar o ponto, ao usá-lo num documento e ao aceitar um endereço eventual. Quem recebe fisicamente é o destinatário? Quando não é, deliveryOperationType é obrigatório, em qualquer UF. A UF de entrega é a do estabelecimento? Quando não é, a entrega é interestadual — isInterstate, derivado e nunca digitado — e precisa estar explicada.
Recebedor distinto do destinatário · deliveryOperationType
| Operação | O caso | O que o Nexio faz |
|---|---|---|
ThirdPartyProcessing | Industrialização por encomenda: a mercadoria vai direto ao industrializador, por conta e ordem do comprador. | Exige type = ThirdParty, CNPJ e razão social do industrializador. Aceita CNPJ de estabelecimento de outra empresa do próprio grupo (RN-DL-06). |
GeneralWarehouse | Armazém geral: a mercadoria entra em depósito de pessoa jurídica que a guarda por conta do comprador. | Idem, inclusive a entrada para CNPJ de outra empresa do grupo. |
DirectToCustomer | Venda à ordem ou remessa por conta e ordem de terceiros: o comprador já vendeu a mercadoria, e o fornecedor entrega direto ao cliente dele (drop ship). | Exige type = ThirdParty e CNPJ no cadastro. É a única operação que aceita recebedor no endereço eventual, com CPF ou CNPJ no snapshot (RN-DL-18). CNPJ do próprio grupo é recusado: se a empresa do grupo é quem compra, ela é a destinatária. |
Other | Recebedor distinto que a lista não nomeia. | deliveryOperationNote obrigatória. CNPJ do próprio grupo é recusado. É o caso que a auditoria lê um por um, e é por isso que ele custa uma frase escrita. |
Entrega interestadual · isInterstate
| Situação | O que explica | O que o Nexio exige |
|---|---|---|
| Com recebedor distinto | A operação declarada em deliveryOperationType. | Nada além dela. interstateNote é recusada: a operação já diz o que acontece. |
| Sem recebedor distinto, em canteiro | type = ConstructionSite. O RICMS/SP (Anexo XI, art. 4º, §3º) admite a compra com entrega direta no canteiro, sem passar pelo estabelecimento principal — e regra equivalente existe na maioria das UFs, com redação própria. | defaultCostCenterId obrigatório no cadastro (RN-DL-07); interstateNote opcional. Só no cadastro: no endereço eventual o tipo é digitado por quem pede e toda linha tem rateio, então canteiro não explica nada — ali a interstateNote é obrigatória (RN-DL-12). |
| Sem recebedor distinto, fora de canteiro | O próprio destinatário recebe em outra UF: depósito sem inscrição própria, feira, funcionário remoto. | interstateNote obrigatória, congelada no documento e marcada para auditoria. A recusa sugere o caminho certo quando ele existe: cadastrar um estabelecimento na UF de destino. |
Por que a checagem é de UF e não de município
Município diferente na mesma UF não muda ICMS e não precisa de justificativa — muda só o Grupo G da nota, que é registro, não escolha. ISS é outra história e está fora desta versão: em serviço, o município de prestação decide a retenção, e a dimensão relevante não é para onde a mercadoria vai. O gancho está declarado no fora de escopo; quando o módulo de serviços existir, a checagem ganha um irmão municipal.
Mudar município, UF ou allowsReceiving do estabelecimento revalida todos os pontos
O stateCode do ponto sem endereço próprio é cópia, não leitura; e o isInterstate de qualquer ponto compara a UF dele com a do estabelecimento. Editar o cityIbgeCode do estabelecimento — mudança de sede ou correção de um código digitado errado — mexe nos dois: o ponto que herda muda de UF, e o ponto com endereço próprio em SP passa a ser interestadual quando a sede vai para MG.
Por isso o PUT do estabelecimento, quando muda município, UF ou allowsReceiving, revalida as RN-DL-05 e RN-DL-07 em todos os pontos pendurados nele — com e sem endereço próprio —, na mesma transação. allowsReceiving passando a falso com ponto ativo pendurado também recusa: o ponto apontaria para estabelecimento que deixou de receber, e a saída é desativar os pontos antes, o que nunca é recusado por documento (RN-DL-15). Recalcula o stateCode só nos que herdam e o isInterstate em todos. A justificativa dos pontos que passam a ser interestaduais vai no mesmo PUT, ponto a ponto — a nota não pode ser gravada antes, porque até a mudança a UF é a mesma e ela seria recusada. Faltando alguma, ou havendo canteiro que fica interestadual sem centro de custo, a edição é recusada com 422 e a lista nomeada. Ponto que deixa de ser interestadual tem a interstateNote limpa. Passando, sai um evento por ponto afetado, com os valores velho e novo de stateCode, isInterstate e interstateNote.
Documento emitido não muda, aqui como em todo lugar: o snapshot congelou a UF que valia, e a revalidação é do cadastro para a frente.
O Nexio avisa, não julga
O sistema não diz que a operação é legal — ele diz que outro contribuinte recebe, ou que a entrega é interestadual, e obriga alguém a declarar a operação ou a justificativa. A adequação é da assessoria fiscal do cliente, e a legislação é estadual. O que o produto garante é que a informação existe, é estruturada e chega ao ERP fiscal junto com o documento, em vez de ser descoberta na conferência da nota.
A saída óbvia continua disponível — e é a certa
Se o grupo tem CNPJ no estado de destino, o caminho correto é esse CNPJ ser o destinatário: cadastra-se o Establishment, e o ponto de entrega pendura nele. Aí stateCode coincide, nenhuma justificativa é exigida e a operação é uma venda interestadual comum entre fornecedor e filial. A mensagem de erro diz isso — não é só uma recusa, é uma sugestão nomeada.
A válvula de escape
Endereço eventual
Toda operação real tem a entrega que não cabe no cadastro: o evento, a feira, a casa do executivo que recebe o notebook, a obra de dois meses que não vale um registro. O SAP tem one-time delivery address exatamente para isso. Negar o caso não o elimina — empurra a informação para o campo de observação, onde ela não é dimensão de nada.
Existe, e vive só no documento
O endereço eventual é preenchido no documento e gravado no snapshot da linha com isOneTimeDelivery = true e deliveryLocationId nulo. Nenhuma linha entra em delivery_locations. Não tem código, não aparece em lookup, não pode ser reutilizado por busca e não polui o catálogo — que é a única coisa que fazia valer a pena proibi-lo.
Não é exceção às regras, é exceção ao cadastro
Continua exigindo establishmentId — alguém tem que ser o destinatário, e endereço eventual não cria CNPJ. Continua derivando stateCode do código IBGE. Declara o tipo do lugar (deliveryLocationType) e continua respondendo às duas perguntas: recebedor distinto só no caso da RN-DL-18, e entrega em outra UF explicada — sem o atalho do canteiro: no eventual interestadual sem recebedor distinto, interstateNote é sempre obrigatória, qualquer que seja o deliveryLocationType. O tipo do lugar é digitado por quem pede e toda linha tem rateio; aceitar "canteiro com o rateio da linha" como explicação deixaria mandar para outra UF sem nota só escolhendo o tipo. O que ele dispensa é o cadastro, não o rigor.
Permissão própria, porque é uma decisão e não uma conveniência
use:delivery-locations-one-time é separada de lookup:delivery-locations. Um cliente que quer disciplina de catálogo simplesmente não concede — e a funcionalidade desaparece da tela, sem flag de configuração e sem código morto. É a mesma mecânica de permissão que o resto do produto já usa para separar quem lê de quem decide.
O catálogo se defende sozinho
O padrão de falha do endereço eventual é conhecido: ele vira o caminho normal, e em um ano o cadastro tem três pontos de entrega e o histórico tem quatrocentos endereços digitados. A defesa é medir. GET /delivery-locations/one-time-usage agrupa os snapshots eventuais dos últimos N dias por CEP e número, com a contagem de uso e os documentos — e a tela oferece "cadastrar como ponto de entrega" num clique, já preenchido. Endereço eventual usado cinco vezes não é eventual; é um cadastro que ninguém fez.
Dado pessoal no eventual: base declarada, guarda do documento e máscara
O eventual é onde o produto encontra dado pessoal: o endereço residencial do funcionário que recebe o notebook, o contato digitado e — só em DirectToCustomer — o CPF e o nome do consumidor final. O tenant é o controlador; o Nexio trata o dado como operador, e só o que a entrega e a nota precisam.
Base legal declarada (LGPD, art. 7º): execução de contrato de que o titular participa — a venda ao consumidor, o vínculo com quem recebe — e cumprimento de obrigação legal, porque o Grupo G da NF-e exige recebedor e endereço. A confirmação é do encarregado do cliente, como toda base legal. Guarda: os campos pessoais do snapshot seguem o prazo de guarda do documento dono; vencido esse prazo, são anonimizados — CPF, nome do recebedor pessoa física, contato, número e complemento —, e ficam CEP, município e UF, que sustentam relatório sem identificar ninguém. Nunca vão para evento, log ou texto de notificação.
Máscara na agregação: /one-time-usage mostra CPF só mascarado (***.456.789-**) e omite nome de pessoa física e contato — quem administra o catálogo precisa saber que o endereço se repete, não quem mora nele; o dado inteiro continua visível dentro do documento, para quem pode lê-lo. Grupo com recebedor pessoa física não oferece "cadastrar como ponto de entrega": o cadastro não guarda CPF, e /from-one-time recusa (RN-DL-21).
O que o eventual quase nunca faz. Não aceita recebedor distinto — entrega a outro contribuinte é decisão de cadastro, com CNPJ conferido, e não texto digitado às pressas na tela do pedido. A exceção é uma só: deliveryOperationType = DirectToCustomer (RN-DL-18). Não aceita defaultCostCenterId, porque o rateio da linha já está lá. E não é editável depois de o documento sair do rascunho: a partir daí é snapshot, e snapshot não se corrige — corrige-se o documento, que gera versão nova.
Consumo
Cabeçalho, linha e o que a linha congela
O cabeçalho define o padrão; a linha sobrescreve
Requisição, pedido e recebimento carregam deliveryLocationId no cabeçalho, e cada linha pode apontar para outro ponto. É consistente com o rateio, que já é por linha desde o Centro de Custo — uma requisição de escritório com três itens para a matriz e um para a obra é o caso comum, não a exceção.
Trocar no cabeçalho pergunta o que fazer com as linhas
A documentação do SAP tem um aviso que vale copiar inteiro: editar só o cabeçalho ou só o item cria descasamento silencioso — "edite tudo ou não edite nada". Aqui a propagação é explícita: mudar o ponto do cabeçalho pergunta "aplicar às N linhas que ainda usam o local anterior?", e as linhas que já foram alteradas à mão nunca são sobrescritas em silêncio.
Um documento, um destinatário
Todas as linhas entregam em pontos do mesmo establishmentId do documento. É a regra da Oracle — o deliver-to só é válido quando o ship-to dele bate com o do documento — e a razão é que uma nota tem um destinatário só. Precisa de dois CNPJs? São dois pedidos. DELIVERY_LOCATION_ESTABLISHMENT_MISMATCH_UNPROCESSABLE, 422, nomeando a linha.
O que a linha congela
Pela Regra 1 do mapa de domínio, sem exceção: a chave estrangeira é rastro, o snapshot é a verdade. Renomear a obra, corrigir o CEP, encerrar o ponto ou desativá-lo não pode reescrever o que o pedido de seis meses atrás dizia.
| Campo na linha | Tipo | Regra |
|---|---|---|
| deliveryLocationId | Guid? | Rastro. Nulo quando a entrega é eventual. Nunca usado para ler o endereço na exibição do documento. |
| deliveryEstablishmentId | Guid | O destinatário fiscal, congelado. Igual ao do cabeçalho, sempre. |
| deliveryAddress | colunas | CEP, logradouro, número, complemento, bairro e cityIbgeCode. Campos próprios, cada um consultável e indexável — não um documento semiestruturado numa coluna só: a UF e o CEP entram em índice e em relatório, e o documento aqui seria conveniência de escrita paga na leitura. É requisito, não recurso de banco (o jsonb do PostgreSQL ou o JSON em texto de outros bancos seriam o exemplo do que não se faz). |
| deliveryStateCode | char(2) | A dimensão fiscal congelada. É por ela que a conferência a três compara pedido, recebimento e nota, e é ela que o ERP fiscal consome. |
| deliveryCountryCode | char(2) | ISO 3166-1, BR na v1. Congelado porque o Grupo G leva o país e a NT 2018.005 rejeita código inválido. |
| deliveryIsInterstate | bool | Derivado na criação — deliveryStateCode contra a UF do estabelecimento — e congelado. A sede que muda de UF depois não transforma em interna a entrega que foi interestadual. |
| deliveryLocationType | enum | O type do ponto; no eventual, o tipo declarado. Congelado porque, no ponto cadastrado, é ele que explica um canteiro interestadual sem justificativa escrita; no eventual, o tipo não dispensa a nota (RN-DL-12). |
| deliveryRecipientTaxId · deliveryRecipientName | varchar(14)? · string(160)? | Grupo G da NF-e, quando quem recebe não é o destinatário. Tax id e não CNPJ porque o drop ship a consumidor final põe um CPF no Grupo G; varchar(14) cobre os dois, e quem valida o dígito olha o comprimento. O campo do cadastro é recipientCnpj e é só CNPJ (RN-DL-06): é no snapshot que a pessoa física cabe, porque ali ela é um fato daquele documento (RN-DL-21). |
| deliveryRecipientStateRegistration | varchar(20)? | IE do recebedor, congelada. A nota leva a IE que valia na emissão, e é contra ela que a rejeição de IE inválida do grupo de entrega é evitada. |
| deliveryOperationType · deliveryOperationNote | enum? · string(400)? | Congelados. É o que marca a linha para o relatório de operação triangular, em qualquer UF; mudar o cadastro depois não reescreve a operação que valeu naquele documento. |
| deliveryInterstateNote | string(400)? | Congelada, pelo mesmo motivo. É a justificativa que a auditoria lê. |
| isOneTimeDelivery | bool | Verdadeiro só no endereço eventual. É o que alimenta /one-time-usage. |
| deliveryLocationName | string(160) | O nome do ponto como estava na emissão. É a primeira linha do bloco de entrega no PDF, e renomear o ponto não pode reescrever o pedido que já saiu. No eventual, o nome digitado. |
| deliveryContactName · Phone · Email | string? | Quem recebe no local, congelado. É quem o motorista procura no portão — e quem o fornecedor liga quando não acha o portão. No eventual, dado pessoal com a guarda da RN-DL-21. |
| deliveryReceivingHours | string(120)? | A janela de recebimento impressa no pedido. Mudar o horário do cadastro não altera o compromisso que o pedido de agosto comunicou. |
| deliveryInstructions | text? | Acesso, portaria, restrição de veículo, EPI — o texto que sai impresso. |
| deliveryRequiresScheduling | bool | É contra este valor que o recebimento apura a divergência de agendamento, nunca contra o cadastro de hoje: entrega sem agendar só é divergência se o pedido tinha pedido agendamento. Ligar a exigência no cadastro em outubro não pode transformar em divergência a entrega combinada em agosto. |
Fronteira interna
Obra e centro de custo — o caso que obriga o desenho
Obra já existe, e existe do outro lado
O CostCenter ganhou vigência própria com a justificativa escrita: "obra e projeto". Obra é nó da árvore de quem paga, com responsável, alçada, orçamento e data de encerramento. Modelar obra aqui de novo daria duas verdades sobre a mesma coisa — e a segunda sempre fica desatualizada, porque é a que ninguém abre.
São dimensões diferentes de propósito, e a assimetria é o desenho
Uma obra pode receber material em dois pontos (o canteiro e o depósito de apoio) e um ponto de entrega pode servir a várias obras (o CD que abastece três frentes). Não é um para um, e por isso não é um campo. O vínculo é DeliveryLocation.defaultCostCenterId, opcional e unidirecional: o ponto de entrega sugere o centro de custo, e o centro de custo nunca ganha endereço.
Sugerir é preencher o rateio, não travá-lo
Ao escolher um ponto de entrega com defaultCostCenterId, a linha nasce com aquele centro de custo em 100% — e o requisitante pode trocar ou ratear. É a mesma mecânica da conta contábil sugerida por Item.defaultGlAccountId e depois por CategoryNode: o sistema propõe, a pessoa responde, o documento congela.
As duas vigências são independentes, e a divergência é avisada
A obra encerra no centro de custo e o ponto de entrega fica ativo, ou o contrário. Nenhum dos dois cascateia para o outro — cascata entre agregados diferentes é como se cria exclusão que ninguém pediu. O que existe é aviso: desativar um nó de centro de custo que é defaultCostCenterId de pontos ativos lista quais são, e segue.
Canteiro em outra UF junta as duas regras. type = ConstructionSite com stateCode diferente é interestadual explicado pelo próprio tipo, e exige defaultCostCenterId preenchido. A segunda exigência não é fiscal, é de conferência: entrega em canteiro é o caso em que material some entre a nota e a obra, e sem o nó de custo amarrado desde o cadastro ninguém reconstrói para onde foi.
Transversal
Escopo e visibilidade
O escopo vem de graça, e é essa a vantagem de establishmentId obrigatório
companyId é derivado do estabelecimento e denormalizado na linha. O conjunto de empresas resolvido a cada requisição por UserCompanyScope é o filtro — não há segunda regra de visibilidade, não há tabela nova e não há nada para lembrar de validar. Ponto de entrega de empresa fora do escopo do usuário responde 404, nunca 403, pela mesma decisão já tomada em centro de custo e em empresa.
O seletor mostra o que dá para usar, e some quando não há escolha
GET /delivery-locations/lookup?establishmentId=… devolve os pontos vigentes e ativos daquele estabelecimento, agrupados. Um item na resposta e nenhuma permissão de eventual: o campo não aparece na tela, e o único ponto é aplicado em silêncio — mesma decisão do seletor de estabelecimento. Zero itens e nenhuma permissão de eventual é erro de cadastro, e a tela diz isso com o link para o cadastro em vez de mostrar um combo vazio.
CD compartilhado entre empresas do grupo são N registros, e está certo
O mesmo galpão que recebe para três empresas do grupo vira três DeliveryLocation, um por CNPJ destinatário. Parece duplicação e não é: fiscalmente são três operações diferentes, com três notas, três destinatários e possivelmente três tratamentos de ICMS. Um registro com N donos precisaria escolher um destinatário na hora de emitir — que é a decisão que este campo existe para não deixar implícita.
Integração
Relação com os demais domínios
| Domínio | Mudança | O quê |
|---|---|---|
| Empresa e Estabelecimento | nenhuma | É a dependência, e ela já está pronta. allowsReceiving e allowsPurchasing já nasceram como eixos separados, e stateCode já é derivado do IBGE. Nenhum campo novo. |
| Centro de Custo | nenhuma | A vigência de obra já existe no Centro de Custo. O vínculo é de mão única e mora aqui, em defaultCostCenterId. O nó não ganha endereço. |
| Cadastro de Item | acrescenta | O gancho shipToLocationId vira shipToStateCode e entra na chave do ItemSupplier. O que muda preço é frete e tributação, que são por UF — dois pontos na mesma cidade não têm preço diferente, e chavear pelo ponto faria o catálogo crescer sem teto. A chave de busca é o deliveryStateCode já congelado na linha, então resolver preço não consulta o cadastro do ponto. Cascata: UF exata > linha sem destino. Preço por sítio específico — canteiro remoto, ilha — é negociado, e por isso é do Contract, que já é o topo da precedência. |
| Fluxo de Aprovação | acrescenta | Ganha o critério UF de entrega em lista, irmão dos critérios de empresa e estabelecimento. Motivo real: entrega interestadual muda o custo e várias empresas querem que ela passe por quem responde pelo fiscal. Nenhuma entidade nova. |
| Orçamento | nenhuma | Verba é por empresa, centro de custo, conta e período. Local de entrega não é dimensão orçamentária e não deve virar uma — a linha já leva o centro de custo que o ponto sugeriu. |
| Moeda e Câmbio | nenhuma | Sem interseção. A moeda é do documento e do fornecedor. |
| Fornecedor | nenhuma | SupplierAddress continua sendo o endereço dele, com as flags de propósito. Este documento modela o endereço nosso. A simetria termina aí de propósito: o fornecedor tem um endereço com vários papéis; nós temos vários pontos com um papel só. |
| Requisição · Pedido · Recebimento · Nota | a especificar | Nascem já com as vinte e seis colunas de snapshot, deliveryLocationId no cabeçalho e por linha, e a validação de destinatário único. Documento gerado de outro — pedido da requisição, recebimento do pedido — herda o snapshot da origem (RN-DL-15), e o dono do documento anonimiza os campos pessoais do eventual ao fim da guarda (RN-DL-21). É o motivo de esta spec vir antes delas. |
Interface
Telas
Pontos de entrega agrupados por empresa e estabelecimento, com UF, tipo, vigência e estado. Filtro por UF é o primeiro da barra, não o último — é a coluna que a pessoa do fiscal procura. Ponto interestadual leva marca visual com a justificativa no tooltip; ponto de terceiro mostra a operação ao lado do tipo.
Estabelecimento primeiro, tudo depois — a UF dele e o tipo do ponto decidem se aparecem o bloco de recebedor e operação e o campo de justificativa interestadual. O endereço começa recolhido com "herda o endereço da filial" escrito e um botão para abrir; o CEP preenche logradouro, bairro e município, e o município traz a UF, sempre bloqueada.
A tela que impede o catálogo de apodrecer. Lista os snapshots eventuais dos últimos 90 dias agrupados por CEP e número, ordenados por contagem de uso, com os documentos ao lado e o botão "cadastrar como ponto de entrega" já preenchido. CPF aparece mascarado, sem nome de pessoa física nem contato, e grupo com recebedor pessoa física não tem o botão.
Um campo no cabeçalho, agrupado por estabelecimento, com o padrão do usuário pré-selecionado. Um item só e sem permissão de eventual: o campo não aparece. Ponto interestadual mostra a explicação que o cadastro tem. No eventual, o tipo do lugar vem primeiro e decide o que a tela pede: recebedor e operação, ou justificativa quando a UF diverge.
Coluna de entrega visível só quando a linha diverge do cabeçalho — caso contrário é ruído em cima do caso comum. Trocar o cabeçalho pergunta antes de propagar, e nomeia quantas linhas serão afetadas e quantas ficaram como estão.
O PDF traz o bloco de entrega inteiro: nome, endereço, contato, janela de recebimento e as instruções de acesso. É a única tela desta spec que o fornecedor vê, e é onde ela paga o próprio custo.
API
Endpoints
| Rota | Permissão | Devolve | Erros |
|---|---|---|---|
| GET /delivery-locations | read:delivery-locations | PagedResult<DeliveryLocationDto>, filtrável por empresa, estabelecimento, tipo, UF e vigência | 400 |
| GET /delivery-locations/{id} | read:delivery-locations | DeliveryLocationDto com o endereço efetivo — o próprio ou o herdado, resolvido no servidor | 404 |
| GET /delivery-locations/lookup | lookup:delivery-locations | O seletor. Só vigentes e ativos do estabelecimento pedido, agrupados, com stateCode e isInterstate de cada ponto | 400 |
| POST /delivery-locations | create:delivery-locations | DeliveryLocationDto | 400 · 404 · 409 · 422 |
| PUT /delivery-locations/{id} | update:delivery-locations | DeliveryLocationDto. establishmentId e code são recusados quando divergem. recipientCnpj fora do formato ou com DV inválido é 400 DELIVERY_LOCATION_RECIPIENT_CNPJ_INVALID; CEP ou código IBGE inválido, 400 (RN-DL-06) | 400 · 404 · 409 · 422 |
| PATCH /delivery-locations/{id}/activate | update:delivery-locations | Estado só por rota própria, nunca por PUT. Exige expectedVersion | 404 · 409 · 422 |
| PATCH /delivery-locations/{id}/deactivate | delete:delivery-locations | Exige expectedVersion. Nunca é recusada por documento em andamento (RN-DL-15) | 404 · 409 |
| GET /delivery-locations/{id}/open-documents | read:delivery-locations | Documentos em andamento que vão concluir com o ponto e rascunhos que vão precisar trocar de ponto antes de submeter, por tipo e com contagem. A tela mostra antes de desativar | 404 |
| DELETE /delivery-locations/{id} | delete:delivery-locations | Existe só para cadastro errado no mesmo dia. Com documento, é desativação | 404 · 409 |
| GET /delivery-locations/one-time-usage | read:delivery-locations | Endereços eventuais agrupados por CEP e número, com contagem e documentos. Janela padrão de 90 dias. CPF mascarado, sem nome de pessoa física nem contato (RN-DL-21) | 400 |
| POST /delivery-locations/from-one-time | create:delivery-locations | Cria o cadastro a partir de um snapshot eventual. Não reescreve os documentos passados — eles continuam com o que congelaram. Recusa snapshot com recebedor pessoa física | 400 · 409 · 422 |
| POST /delivery-locations:import | create:delivery-locations | Lote, tudo ou nada, com ?dryRun=true obrigatório antes — a mesma mecânica do centro de custo. Cliente com quarenta filiais e canteiros não digita um por um | 400 · 422 |
Contrato
Contrato de erros
O status HTTP é derivado do sufixo do código, nunca da mensagem: o mapeador do Core reconhece dez sufixos — _NOT_FOUND → 404 · _ALREADY_EXISTS, _ALREADY_ASSIGNED e _CONFLICT → 409 · _FORBIDDEN, _ACCESS_DENIED e _BLOCKED → 403 · _UNAUTHORIZED → 401 · _UNPROCESSABLE → 422 · _UNAVAILABLE → 503 · sem sufixo → 400. Código canônico explícito vence o sufixo. Nenhum código desta página usa _UNAVAILABLE.
| Código | HTTP | Quando |
|---|---|---|
| DELIVERY_LOCATION_NOT_FOUND | 404 | Não existe neste tenant — ou não está no escopo do usuário. Os dois casos respondem igual, de propósito |
| DELIVERY_LOCATION_CODE_ALREADY_EXISTS | 409 | Código repetido dentro da mesma empresa. A mensagem carrega o código |
| DELIVERY_LOCATION_EXTERNAL_CODE_ALREADY_EXISTS | 409 | Código do ERP repetido na mesma empresa |
| DELIVERY_LOCATION_IN_USE_CONFLICT | 409 | DELETE de ponto que algum documento já usou. A mensagem traz a contagem por tipo de documento e manda desativar — desativação não passa por aqui (RN-DL-15) |
| DELIVERY_LOCATION_CONCURRENT_CHANGE_CONFLICT | 409 | expectedVersion diferente da versão gravada, em PUT, /activate, /deactivate ou DELETE. A tela relê e mostra o que mudou |
| DELIVERY_LOCATION_ESTABLISHMENT_IMMUTABLE_CONFLICT | 409 | Tentativa de trocar o destinatário fiscal. A mensagem manda criar outro ponto |
| DELIVERY_LOCATION_ESTABLISHMENT_NOT_RECEIVING_UNPROCESSABLE | 422 | Estabelecimento com allowsReceiving = false |
| DELIVERY_LOCATION_ADDRESS_INCOMPLETE_UNPROCESSABLE | 422 | Bloco de endereço pela metade. Ou tudo, ou nada |
| DELIVERY_LOCATION_INTERSTATE_NOTE_REQUIRED_UNPROCESSABLE | 422 | Entrega interestadual sem recebedor distinto e sem interstateNote — no ponto cadastrado, fora de canteiro; no endereço eventual, em qualquer tipo de lugar (RN-DL-12). A mensagem sugere o caminho alternativo: cadastrar um estabelecimento na UF de destino |
| DELIVERY_LOCATION_INTERSTATE_NOTE_NOT_APPLICABLE_UNPROCESSABLE | 422 | interstateNote com UF igual à do estabelecimento, ou com recebedor distinto — a operação já explica |
| DELIVERY_LOCATION_OPERATION_TYPE_REQUIRED_UNPROCESSABLE | 422 | Recebedor distinto — type = ThirdParty no cadastro, recebedor no eventual — sem deliveryOperationType |
| DELIVERY_LOCATION_OPERATION_TYPE_NOT_APPLICABLE_UNPROCESSABLE | 422 | deliveryOperationType sem recebedor distinto |
| DELIVERY_LOCATION_OPERATION_NOTE_REQUIRED_UNPROCESSABLE | 422 | Operação Other sem deliveryOperationNote |
| DELIVERY_LOCATION_OPERATION_NOTE_NOT_APPLICABLE_UNPROCESSABLE | 422 | deliveryOperationNote em operação diferente de Other |
| DELIVERY_LOCATION_RECIPIENT_REQUIRED_UNPROCESSABLE | 422 | type = ThirdParty sem recipientCnpj ou sem recipientName |
| DELIVERY_LOCATION_RECIPIENT_NOT_ALLOWED_UNPROCESSABLE | 422 | recipientCnpj com type diferente de ThirdParty |
| DELIVERY_LOCATION_ONE_TIME_RECIPIENT_NOT_ALLOWED_UNPROCESSABLE | 422 | Recebedor no endereço eventual com deliveryOperationType diferente de DirectToCustomer. A mensagem diz qual é a única operação que o aceita (RN-DL-18) |
| DELIVERY_LOCATION_ONE_TIME_PERSONAL_RECIPIENT_UNPROCESSABLE | 422 | /from-one-time a partir de snapshot com recebedor pessoa física. O cadastro não guarda CPF (RN-DL-21) |
| DELIVERY_LOCATION_RECIPIENT_IS_OWN_ESTABLISHMENT_UNPROCESSABLE | 422 | O CNPJ do recebedor — recipientCnpj, ou deliveryRecipientTaxId de 14 caracteres no eventual — tem a raiz de uma Company do tenant fora da exceção da RN-DL-06: a raiz da empresa destinatária, ou a de outra empresa em operação que não é ThirdPartyProcessing nem GeneralWarehouse. Vale para estabelecimento cadastrado ou não. A mensagem nomeia a empresa e, quando cadastrado, o estabelecimento, e manda usá-lo como destinatário |
| ESTABLISHMENT_ADDRESS_CHANGE_BREAKS_DELIVERY_LOCATIONS_UNPROCESSABLE | 422 | PUT /establishments/{id} que muda município, UF ou allowsReceiving e deixaria algum ponto de entrega pendurado no estabelecimento — com ou sem endereço próprio — interestadual sem explicação (sem nota em deliveryLocationInterstateNotes[] no mesmo PUT, fora de canteiro e sem recebedor distinto; ou canteiro sem centro de custo), ou ativo e apontando para estabelecimento que deixou de receber. A mensagem lista os pontos pelo código e pelo nome (RN-DL-19, RN-EMP-17) |
| DELIVERY_LOCATION_CONSTRUCTION_COST_CENTER_REQUIRED_UNPROCESSABLE | 422 | Canteiro interestadual sem defaultCostCenterId |
| DELIVERY_LOCATION_COST_CENTER_UNPROCESSABLE | 422 | defaultCostCenterId de outra empresa, fora de vigência, ou nó que não aceita lançamento |
| DELIVERY_LOCATION_ESTABLISHMENT_MISMATCH_UNPROCESSABLE | 422 | Linha de documento entregando em ponto de outro estabelecimento. Nomeia a linha |
| DELIVERY_LOCATION_NOT_EFFECTIVE_UNPROCESSABLE | 422 | Ponto inativo ou fora da vigência na data do documento — na criação ou submissão do documento, ou ao incluir ou trocar o ponto de uma linha. Nomeia a linha. Documento já submetido não é revalidado (RN-DL-15) |
| DELIVERY_LOCATION_ONE_TIME_FORBIDDEN | 403 | Endereço eventual sem a permissão use:delivery-locations-one-time |
| DELIVERY_LOCATION_CODE_REQUIRED · _TOO_LONG · _NOT_ALPHANUMERIC | 400 | Validações de código, herdadas do padrão do centro de custo |
| DELIVERY_LOCATION_NAME_REQUIRED · _TOO_LONG | 400 | Nome em branco ou acima de 160 |
| DELIVERY_LOCATION_ZIP_CODE_INVALID · _CITY_IBGE_CODE_INVALID | 400 | CEP fora de oito dígitos; código IBGE que não existe na tabela de municípios |
| DELIVERY_LOCATION_RECIPIENT_CNPJ_INVALID | 400 | recipientCnpj que, normalizado, não tem o formato do CNPJ ou tem DV inválido |
| DELIVERY_LOCATION_RECIPIENT_TAX_ID_INVALID | 400 | deliveryRecipientTaxId do eventual que, normalizado, não é CNPJ de 14 nem CPF de 11 válido |
Autorização
Permissões
| Permissão | Alcance | Observação |
|---|---|---|
| read:delivery-locations | Listar e ler; abre a tela de cadastro e o relatório de endereços eventuais | Recortada pelo escopo do usuário. Não dá o seletor do documento: read e lookup não se implicam |
| lookup:delivery-locations | Alimentar o seletor de ponto de entrega do documento (GET /delivery-locations/lookup) | Todo papel que emite documento precisa dela |
| create:delivery-locations | Criar, inclusive em lote por importação e a partir de um endereço eventual | Administração de cadastro. Separada de read pelo mesmo motivo de sempre |
| update:delivery-locations | Editar e ativar | Estado só por rota própria; a ativação é update |
| delete:delivery-locations | Desativar e excluir o cadastro errado do mesmo dia | Desativar é exclusão lógica e usa delete, mesmo sendo PATCH |
| use:delivery-locations-one-time | Digitar endereço eventual no documento | Separada de propósito. Não conceder é a forma de exigir disciplina de catálogo — sem parâmetro de configuração e sem código morto |
Invariantes
Regras de negócio
establishmentId é obrigatório e o estabelecimento precisa ter allowsReceiving = true. Não existe ponto de entrega solto no tenant: alguém sempre responde pela nota.
establishmentId e code não mudam depois de criados. Todo documento emitido congelou o primeiro; o segundo é a identidade pela qual as pessoas falam do lugar. Mudança é registro novo.
CEP preenchido obriga logradouro, bairro e município. CEP vazio obriga o bloco inteiro vazio. Endereço pela metade não herda nem sobrescreve — só produz nota rejeitada.
stateCode nunca é digitado e nunca é nuloDerivado do cityIbgeCode quando há endereço próprio, copiado do estabelecimento quando não há. É a dimensão que muda o imposto — não pode divergir do município e não pode faltar.
isInterstate é verdadeiro quando stateCode difere do estabelecimento, e nunca é digitado. Interestadual exige uma de três explicações: recebedor distinto com deliveryOperationType (RN-DL-20); type = ConstructionSite com centro de custo, só no ponto cadastrado (RN-DL-07); ou interstateNote, a única explicação do endereço eventual sem recebedor distinto (RN-DL-12). Sem UF divergente, interstateNote é recusada; com recebedor distinto, também. Verificada no cadastro, no uso em documento e no endereço eventual — os três caminhos, sempre.
recipientCnpj e recipientName são obrigatórios com type = ThirdParty e recusados nos outros tipos. CNPJ do próprio grupo é recusado — filial usada como endereço perde IE, Suframa e situação cadastral —, e o grupo é reconhecido pela raiz, não pelo cadastro: os 8 primeiros caracteres do recipientCnpj normalizado são comparados com o cnpjRoot de toda Company do tenant, esteja o estabelecimento cadastrado ou não. Raiz igual à da empresa destinatária — a do establishmentId — é sempre 422 DELIVERY_LOCATION_RECIPIENT_IS_OWN_ESTABLISHMENT_UNPROCESSABLE: a saída é cadastrar esse CNPJ como Establishment, se ainda não for, e usá-lo como destinatário. Raiz de outra Company do tenant é aceita só quando deliveryOperationType é ThirdPartyProcessing ou GeneralWarehouse — industrializador ou armazém do grupo é outro contribuinte, e é o CNPJ dele que vai no Grupo G —; em DirectToCustomer e Other, o mesmo 422. Sem a comparação por raiz, filial não cadastrada da própria empresa passaria como terceiro. recipientCnpj é normalizado antes de validar e de comparar — trim, sem máscara, maiúsculas — e formato ou DV inválido é 400 DELIVERY_LOCATION_RECIPIENT_CNPJ_INVALID; o deliveryRecipientTaxId do eventual segue a mesma normalização, como CNPJ de 14 ou CPF de 11, com 400 DELIVERY_LOCATION_RECIPIENT_TAX_ID_INVALID; com 14 caracteres, passa pela mesma comparação por raiz, e como o recebedor do eventual só existe em DirectToCustomer (RN-DL-18), raiz de qualquer Company do tenant é recusada com o mesmo 422.
type = ConstructionSite com UF divergente exige defaultCostCenterId. Não é regra fiscal, é de conferência: é o caso em que material some entre a nota e a obra.
Todas as linhas entregam em pontos do mesmo establishmentId do documento. Dois CNPJs destinatários são dois documentos, porque são duas notas.
Endereço, país, UF, isInterstate, tipo do lugar, destinatário, recebedor com CPF ou CNPJ e IE, operação e justificativa vão para colunas da linha na criação — e também tudo o que o pedido impresso e o recebimento leem: nome do ponto, contato, janela de recebimento, instruções de acesso e a exigência de agendamento. A exibição do documento nunca lê o cadastro. A divergência de agendamento é apurada contra o snapshot, não contra o cadastro de hoje. Regra 1 do mapa de domínio, sem exceção.
Trocar o ponto do cabeçalho pergunta antes, informa quantas linhas serão afetadas e nunca sobrescreve linha alterada à mão.
Vive só no snapshot, com isOneTimeDelivery = true e deliveryLocationId nulo. Não tem código, não entra em lookup e não é reutilizável por busca. Virar cadastro é ato explícito, por /from-one-time.
Exige estabelecimento, declara o tipo do lugar, deriva stateCode do IBGE e cumpre a RN-DL-05 — sem o atalho do canteiro: interestadual sem recebedor distinto exige interstateNote qualquer que seja o deliveryLocationType declarado, senão 422 DELIVERY_LOCATION_INTERSTATE_NOTE_REQUIRED_UNPROCESSABLE. O tipo é digitado por quem pede e toda linha tem rateio, então "canteiro" não explica nada no eventual. O que ele dispensa é o cadastro, não o rigor. E não aceita recebedor distinto, salvo o caso único da RN-DL-18.
/from-one-time cria o registro daqui para a frente. Os documentos que usaram o endereço continuam com deliveryLocationId nulo e o snapshot que congelaram — porque naquele momento não havia cadastro, e o histórico registra o que aconteceu.
Encerrar o nó de centro de custo não encerra o ponto de entrega, e vice-versa. O que existe é aviso com a lista do que ficou pendurado. Cascata entre agregados diferentes é como se cria exclusão que ninguém pediu.
Com isActive = false, o ponto de entrega não é oferecido nem aceito em documento novo — criar ou submeter documento que o aponte, ou incluir ou trocar o ponto de uma linha, é recusado com 422 DELIVERY_LOCATION_NOT_EFFECTIVE_UNPROCESSABLE, nomeando a linha; submeter rascunho é documento novo, e o rascunho troca de ponto antes. Todo documento já submetido que o aponta segue até o fim: aprovação, pedido, recebimento, nota, devolução e estorno, com o snapshot congelado, que o documento gerado herda da origem sem reler o cadastro. A desativação não é recusada por haver documento em aberto; /open-documents mostra antes o que vai concluir com o ponto e quais rascunhos vão precisar trocar. É a mesma regra da empresa e do estabelecimento. Ponto fora de vigência segue a mesma regra. Documento fechado e histórico continuam legíveis com o que congelaram, com marca visual de ponto inativo.
defaultCostCenterId sugere, nunca impõeA linha nasce com aquele centro de custo em 100% e o requisitante pode trocar ou ratear. O sistema propõe, a pessoa responde, o documento congela — a mesma mecânica da conta contábil sugerida pelo item.
Nenhum cliente reimplementa a herança. A API sempre devolve o endereço final e diz de onde ele veio, do mesmo jeito que a política de orçamento devolve de qual nó cada valor foi resolvido.
O snapshot eventual aceita deliveryRecipientTaxId e deliveryRecipientName se e somente se deliveryOperationType = DirectToCustomer, com deliveryLocationType = ThirdParty; em qualquer outra operação é 422. Entrega direta ao cliente final é, por natureza, eventual — o comprador não volta a entregar naquele endereço. A exceção para aqui porque o consumidor final costuma ser pessoa física, e CPF em cadastro permanente é dado pessoal que o produto guardaria sem precisar: no snapshot ele é um fato daquele documento, que a nota vai carregar no Grupo G de qualquer forma, com a guarda e a máscara da RN-DL-21. O cadastro não muda: ponto de entrega de terceiro continua exigindo type = ThirdParty e CNPJ (RN-DL-06). E o /one-time-usage continua contando: drop ship repetido para o mesmo recebedor pessoa jurídica é revenda com carteira, e aí vira cadastro.
allowsReceiving do estabelecimento revalida todos os pontosO PUT do estabelecimento que altera cityIbgeCode, UF ou allowsReceiving revalida, na mesma transação, todos os pontos pendurados nele — com e sem endereço próprio. Recalcula stateCode só nos que herdam e isInterstate em todos, e confere neles as RN-DL-05 e RN-DL-07. A interstateNote de cada ponto que passa a ser interestadual vai no mesmo PUT, em deliveryLocationInterstateNotes[] — deliveryLocationId + interstateNote —; nota para ponto que não passa a interestadual é ignorada. A edição é recusada com 422 ESTABLISHMENT_ADDRESS_CHANGE_BREAKS_DELIVERY_LOCATIONS_UNPROCESSABLE e a lista nomeada quando falta nota, quando há canteiro sem centro de custo, ou quando allowsReceiving passa a falso com ponto ativo pendurado — que ficaria apontando para estabelecimento que deixou de receber; desative os pontos antes, o que nunca é recusado por documento (RN-DL-15). É o mesmo gatilho, o mesmo campo e o mesmo código que a Empresa declara na rota do estabelecimento (RN-EMP-17). Ponto que deixa de ser interestadual perde a interstateNote. Passando, a version de cada ponto afetado sobe e sai um evento por ponto. Documento emitido não muda.
Sempre que quem recebe fisicamente não é o destinatário — type = ThirdParty no cadastro, recebedor preenchido no eventual —, deliveryOperationType é obrigatório, independente da UF; sem recebedor distinto, é recusado. Other exige deliveryOperationNote. O enum nomeia o fato e os casos reconhecidos — industrialização, armazém geral, venda à ordem ou remessa por conta e ordem —; a segunda NF-e e o CFOP são do ERP fiscal. É o que marca a linha para o relatório de operação triangular, inclusive dentro da mesma UF.
CPF, nome de pessoa física, contato e endereço residencial do snapshot eventual são tratados com base legal declarada (execução de contrato e obrigação legal da NF-e), seguem o prazo de guarda do documento dono e, vencido esse prazo, são anonimizados, restando CEP, município e UF. Nunca vão para evento, log ou notificação. /one-time-usage mostra CPF mascarado e omite nome de pessoa física e contato; /from-one-time a partir de snapshot com recebedor pessoa física é 422 DELIVERY_LOCATION_ONE_TIME_PERSONAL_RECIPIENT_UNPROCESSABLE.
Aferição
Referência de mercado
| Nexio | SAP | Coupa | Oracle Fusion | Nota |
|---|---|---|---|---|
| Separação ship-to × deliver-to | Werk / Lagerort × endereço de entrega no item | — | Ship-to Location × Deliver-to Location | A Oracle é a mais explícita, e a frase deles resume: "end users care about Deliver-To; suppliers care about Ship-To" |
establishmentId obrigatório no ponto | endereço herda do plant | — | o deliver-to site carrega o ship-to associado | A Oracle recusa a combinação quando o ship-to do local não bate com o do documento. É a nossa RN-DL-08 |
| Propósito do endereço | partner roles | campo purposes | flags billing / shipping / deliver-to site | Três produtos, três formas da mesma ideia. Aqui não precisamos: o registro só existe para receber |
| Endereço eventual | one-time delivery address | — | — | Só o SAP tem. Endereço divergente no pedido gera até uma one-time location na integração com o TM |
| Código e contato no local | ADRC | location-code, attention, active | Location | O attention line da Coupa é o nosso contactName, e existe pelo mesmo motivo prático |
| Grupo fiscal de entrega | localização brasileira | — | — | Nenhum produto internacional modela o Grupo G da NF-e — é onde a localização brasileira sempre entra depois, como adaptação |
| Operação triangular e entrega interestadual explicadas | — | — | — | Não existe em nenhum deles. É consequência de uma regra que só o Brasil tem, e é a única invenção desta spec |
Três notas. A primeira: a separação em dois níveis aparece nos três produtos internacionais, com nomes diferentes — é a forma certa, e a nossa vantagem é que o nível fiscal já existia como Establishment antes de esta spec começar. A segunda: o endereço eventual é um caso em que o SAP está sozinho e certo — o caso é real, e negá-lo empurra a informação para o campo de observação. A terceira, e a que importa: a exigência de operação declarada e de explicação para entrega interestadual não tem equivalente em nenhum dos quatro, porque nenhum deles nasceu num país onde a UF de destino muda a alíquota. É o mesmo tipo de vantagem estrutural que a spec de Empresa identificou nos dois níveis de CNPJ.
Limites
Fora de escopo
| O que | Por quê |
|---|---|
| Cálculo fiscal — ICMS, DIFAL, ST, FCP, CFOP | O Nexio registra a dimensão e a entrega íntegra a quem calcula. Apurar imposto é do ERP fiscal, e entrar nisso multiplica a superfície de manutenção por 27 legislações estaduais |
| Segunda NF-e da operação triangular | Remessa simbólica, retorno de industrialização, remessa por conta e ordem e depósito em armazém geral têm nota própria, emitida pelo fornecedor, pelo comprador ou pelo ERP fiscal. O Nexio registra a operação e o recebedor (RN-DL-20); não emite nem confere essa nota |
| Agendamento de doca com capacidade | receivingHours é texto exibível e requiresScheduling é um aviso. Agenda com janelas, capacidade e reserva é produto próprio — e agenda que ninguém mantém é pior que uma frase certa |
| Roteirização, frete e transportadora | Transportadora é dado do pedido, não do ponto de entrega. Roteirização é TMS |
| Depósito e endereçamento de estoque | O storage location do SAP. Ponto de entrega responde onde chega; depósito responde onde fica. Estoque segue fora de escopo, com ItemType.controlsStock sempre false |
| Endereço fora do Brasil | countryCode existe e é sempre BR. Importação tem local de desembaraço, regime aduaneiro e moeda — é outro documento inteiro |
| ISS e local de prestação de serviço | Gancho declarado. Em serviço o município decide a retenção, e a dimensão não é para onde a mercadoria vai. Quando o módulo de serviços existir, a checagem de UF ganha um irmão municipal |
| Endereço de cobrança | Não existe bill-to separado do lado comprador: a nota vem para o estabelecimento, e o endereço dele já está lá |
| Geolocalização | Coordenadas servem a roteirização e a prova de entrega — as duas fora. Voltam com o módulo que as justificar |
| Ponto de entrega compartilhado entre empresas | Por decisão, não por limitação: o mesmo galpão vira N registros, um por CNPJ destinatário, porque fiscalmente são N operações. Ver § Escopo e visibilidade |
Registro
Histórico de revisões
| Versão | Data | O que mudou |
|---|---|---|
| v1.9 | 30/10/2026 | Permissões padronizadas em read/lookup/create/update/delete (decisão de 30/10/2026). manage:delivery-locations vira, por ação: POST, POST /from-one-time e POST :import → create; PUT e PATCH /activate → update; PATCH /deactivate e DELETE → delete; GET /one-time-usage → read. O seletor GET /lookup passa de read a lookup:delivery-locations, e a tabela de permissões vai de três a seis linhas. |
| v1.8 | 11/09/2026 | Correção da validação R3. CNPJ do próprio grupo reconhecido pela raiz (cnpjRoot), cadastrado ou não: raiz da empresa destinatária sempre recusada; raiz de outra empresa só em industrialização e armazém geral; o deliveryRecipientTaxId de 14 caracteres do eventual passa pela mesma comparação (RN-DL-06). Unicidade do code escrita como requisito, com o índice parcial só como exemplo. |
| v1.7 | 11/09/2026 | Correção da validação R2. Endereço eventual interestadual sem recebedor distinto exige sempre interstateNote — canteiro declarado no eventual não explica (RN-DL-05, RN-DL-12). RN-DL-19 dispara também em allowsReceiving, e o texto do ESTABLISHMENT_ADDRESS_CHANGE_BREAKS_DELIVERY_LOCATIONS_UNPROCESSABLE é o mesmo da Empresa. deliveryAddress como requisito, sem recurso de banco; PUT /delivery-locations/{id} com o 400 |
| v1.6 | 11/09/2026 | Correção pós-validação. recipientCnpj e deliveryRecipientTaxId normalizados antes de validar, com DELIVERY_LOCATION_RECIPIENT_CNPJ_INVALID e DELIVERY_LOCATION_RECIPIENT_TAX_ID_INVALID (400). DELIVERY_LOCATION_VERSION_CONFLICT passa a DELIVERY_LOCATION_CONCURRENT_CHANGE_CONFLICT; ONE_TIME_DELIVERY_NOT_PERMITTED_UNPROCESSABLE (422) passa a DELIVERY_LOCATION_ONE_TIME_FORBIDDEN (403). A nota dos pontos que passam a interestaduais na edição do estabelecimento ganha nome de campo comum com a Empresa, deliveryLocationInterstateNotes[], e código de recusa. O lookup deixa de falar em "motivo" |
| v1.5 | 10/09/2026 | Validação de 10/09. Sai IntraGroupTransfer e o interstateReason: nasce deliveryOperationType (recebedor distinto do destinatário, em qualquer UF) com deliveryOperationNote; interestadual passa a ser derivado (isInterstate) e explicado por operação, canteiro ou interstateNote (RN-DL-05, RN-DL-20). CNPJ de outra empresa do grupo aceito só em industrialização e armazém geral (RN-DL-06). Revalidação por mudança do estabelecimento cobre todos os pontos, com a justificativa no mesmo PUT (RN-DL-19). Desativar não trava documento em andamento (RN-DL-15) e /blocking-documents vira /open-documents. Snapshot de 21 para 26 colunas (país, IE do recebedor, tipo, isInterstate, justificativa). LGPD do endereço eventual (RN-DL-21). Token version. Erros do eventual duplicados unificados; dez sufixos. |
| v1.4 | 09/09/2026 | Ajustes de convenção do fechamento do ciclo de 08/09. |
| v1.3 | 09/09/2026 | Motivo IntraGroupTransfer (RN-DL-20); snapshot de 7 para 21 colunas; revalidação dos pontos que herdam ao mudar o município do estabelecimento (RN-DL-19); regra de destinatário no eventual renumerada de RN-DL-13 para RN-DL-18. |
| v1.2 | 08/09/2026 | Destinatário no endereço eventual para drop ship; deliveryRecipientCnpj renomeado para deliveryRecipientTaxId. |
| v1.0 | 02/09/2026 | Criação: DeliveryLocation pendurado no estabelecimento, regra interestadual, endereço eventual e snapshot na linha; preço por destino chaveado por UF no ItemSupplier. |