# ENG-1975 — View para dados do relatório de embalagem do cliente

## Objetivo

Criar uma view SQL nova, específica para disponibilizar ao ERP os dados que o WMS usa para montar o relatório de embalagem de expedição do cliente. O ERP será responsável por gerar o relatório; esta entrega não altera front-end, PDF ou layout existente.

## Nome

Sugestão de view:

```sql
vw_relatorio_embalagem_cliente
```

O nome e os campos não devem mencionar Brametal. A view representa o lado do WMS e deve usar nomes que descrevam os conceitos do WMS, sem copiar nomes de colunas do formulário do cliente quando não forem conceitos existentes aqui.

## Granularidade e agrupamento

A view será plana, com uma linha por produto/item efetivamente vinculado à embalagem. Os dados de agrupamento serão repetidos em cada linha:

```text
embalagem + pré-fatura + item/produto
```

O ERP agrupará as linhas pelos identificadores retornados. Uma embalagem pode conter várias pré-faturas; cada item continuará em sua própria linha e ficará associado à pré-fatura correspondente.

A view não deve depender da ordenação física das linhas. Deve retornar identificadores e sequências que permitam ao ERP ordenar os itens.

## Origem e comportamento

A consulta deve reproduzir a origem e o comportamento do relatório atual:

- partir de `shipping_packagings`;
- relacionar `shipping_packaging_products` aos `picked_items`;
- relacionar o item à saída/pré-fatura e ao produto;
- usar os dados persistidos nas tabelas e nos registros de `integrations.parameters`;
- considerar apenas os produtos que o relatório atual considera, excluindo produtos cancelados;
- não filtrar inicialmente por status, concluído, faturado ou data;
- não chamar o ERP durante a consulta;
- não tentar obter nota fiscal, fornecedor ou qualquer dado que exista somente no ERP;
- preservar valores nulos/ausentes conforme estão salvos, sem inventar fallback de apresentação.

## Campos de identificação e agrupamento

Retornar os identificadores técnicos e os valores de negócio disponíveis no WMS, incluindo, quando aplicável:

```text
embalagem_id
embalagem_tipo
embalagem_status
embalagem_volume
embalagem_observacao
embalagem_criada_em
embalagem_concluida_em
embalagem_descricao
prefatura_id
prefatura_numane
prefatura_numpfa
prefatura_identificacao
produto_id
item_id
item_sequencia
```

`prefatura_identificacao` pode ser uma composição equivalente à impressão (`NUMANE/NUMPFA`), mas as colunas originais devem permanecer disponíveis.

## Dados da pré-fatura/saída

Expor as informações persistidas usadas pelo relatório, com nomes do domínio do WMS:

```text
cliente_id
cliente_codigo
cliente_nome
cidade_cliente
uf_cliente
representante
data_faturamento
codigo_forma_pagamento
descricao_forma_pagamento
codigo_condicao_pagamento
descricao_condicao_pagamento
valor_prefatura
peso_bruto_prefatura
tipo_frete
transportadora
observacao_prefatura
```

Somente devem ser incluídos os campos que possuem origem persistida no WMS. `nota_fiscal` não faz parte da view, pois existe somente no ERP.

Quando transportadora ou observações forem persistidas nos parâmetros de integração, devem ser lidas de lá. A view não deve executar a chamada `SeniorPreOrder` que o relatório atual executa.

## Dados do produto/item

Expor as colunas originais persistidas e, se útil para a integração, versões compostas/formata­das:

```text
produto_codigo
produto_derivacao
produto_descricao
unidade_medida
quantidade_embalada
quantidade_separada
quantidade_total
peso_unitario_produto
peso_item
peso_total_item_embalagem
sequencia_prefatura
numero_pedido
pedido_cliente
produto_cliente
codigo_e_c
OP
observacao_item
```

Os nomes acima são conceitos do WMS. Não criar campos chamados `codigo_brametal`, `codigo_rex`, `ordem_compra` ou `op_lote` apenas para reproduzir o formulário do cliente.

A coluna `OP` deve vir do campo persistido correspondente no WMS. A observação deve ser retornada separadamente, como está salva. Se `OP` também aparecer dentro da observação, isso não deve ser deduplicado ou extraído pela view; a decisão de integração fica com o ERP.

As colunas originais usadas como origem devem continuar disponíveis, inclusive quando houver alias ou concatenação para facilitar o consumo.

## Pesos

Retornar todos os pesos que possuem origem no WMS, sem misturar conceitos:

```text
peso_bruto_prefatura
peso_unitario_produto
peso_item
peso_total_itens_embalagem
peso_tarefa
```

O peso do item deve seguir a regra do relatório atual: quantidade do item na embalagem multiplicada pelo peso bruto cadastrado do produto. O peso total dos itens da embalagem deve representar a soma dos pesos dos itens considerados pelo relatório.

Não criar peso aproximado da caixa nem peso de tara se não houver esses dados persistidos no WMS.

## Cancelamentos

A view deve manter o comportamento do relatório atual: produtos cujo `stock_io_product.status` seja cancelado não devem ser retornados.

## Filtros

A view não terá filtro fixo de pré-fatura, item, status ou embalagem. O ERP será responsável por aplicar os filtros necessários. Por isso, a view deve expor separadamente os códigos e identificadores que o ERP possa usar, especialmente:

```text
prefatura_numane
prefatura_numpfa
produto_codigo
produto_id
sequencia_prefatura
numero_pedido
pedido_cliente
produto_cliente
embalagem_id
```

## Fora do escopo

- Alterar o relatório PDF atual.
- Alterar o front-end.
- Implementar o relatório no ERP.
- Criar integração/API para o ERP.
- Buscar dados em tempo real no ERP.
- Retornar nota fiscal, fornecedor ou peso da caixa se esses dados não estiverem salvos no WMS.
- Capturar a anotação manuscrita `776646` da imagem.

## Contrato implementado

A view foi implementada como `vw_relatorio_embalagem_cliente`. Ela expõe:

- identificação, tipo, status, volume, observação, datas, time, responsável e tipo da embalagem;
- descrições textuais de `embalagem_tipo` e `embalagem_status`, preservando também os códigos numéricos originais;
- identificação e parâmetros originais da pré-fatura;
- cliente persistido no WMS, com fallback do nome salvo na integração;
- dados de pagamento, frete, transportadora e detalhes do pedido que estejam persistidos;
- identificação, códigos, descrição e unidade do produto;
- sequência, pedidos e códigos do cliente persistidos no item da pré-fatura;
- `OP` persistida na integração do item e observação do item em campos separados;
- quantidades embalada, separada e total em campos separados;
- pesos bruto e líquido da pré-fatura, peso bruto do item da pré-fatura, peso unitário, peso do item embalado, total da embalagem e peso da tarefa;
- tarefa diretamente vinculada ao item separado e a coleção `tarefas_separacao` em JSON, com identificação, posição e peso de todas as tarefas ligadas ao item da pré-fatura;
- os JSONs originais `cliente_parametros`, `prefatura_parametros`, `item_prefatura_parametros`, `produto_parametros` e `item_parametros`.

Valores vindos de `integrations.parameters` são preservados como texto. A view não converte esses valores de apresentação para evitar perda de zeros ou falha diante de formatos legados. Apenas pesos calculados a partir de colunas numéricas do WMS são retornados como números calculados.

## Exemplos de consulta para o ERP

Filtrar uma pré-fatura e os produtos desejados:

```sql
SELECT *
FROM vw_relatorio_embalagem_cliente
WHERE prefatura_numane = '1928172'
  AND prefatura_numpfa = '48'
  AND produto_codigo IN ('73252', '73253')
ORDER BY embalagem_id, sequencia_prefatura, embalagem_item_id;
```

Filtrar diretamente pela embalagem:

```sql
SELECT *
FROM vw_relatorio_embalagem_cliente
WHERE embalagem_id = 156758
ORDER BY prefatura_numane, prefatura_numpfa, sequencia_prefatura, embalagem_item_id;
```

## Validação esperada

- Migration cria a view e sua reversão remove somente essa view.
- Teste verifica a existência da definição e suas colunas principais.
- Teste funcional com uma embalagem e várias pré-faturas valida a granularidade das linhas.
- Teste funcional valida exclusão de produto cancelado.
- Teste funcional valida valores originais, observações, quantidades e pesos.
- A migration é aplicada em PostgreSQL real para validar a sintaxe e o contrato consultável.

## Ajuste para consumo via database link Oracle (ENG-1975, follow-up)

O ERP consulta a view por um database link Oracle (`@PG_DBLINK`, Oracle Database
Gateway). O gateway mapeia toda coluna PostgreSQL **sem tamanho declarado** para
o tipo `LONG`: colunas `json`/`jsonb` e todo `text` produzido por `->>`, `#>>` e
`CASE`. Um `SELECT` da view pelo link — mesmo `SELECT * ... WHERE 1=2` — falha
na fase de *describe* com `ORA-00600 [HO define: Long fetch]`, porque há mais de
um `LONG` no retorno.

Primeira correção, na migration
`2026_09_03_120000_fix_customer_packaging_report_view_dblink_types`:

- as colunas de JSON cru saíram da view: `cliente_parametros`,
  `prefatura_parametros`, `item_prefatura_parametros`, `produto_parametros`,
  `item_parametros` e `tarefas_separacao`. Os campos escalares seguem expostos
  individualmente; `tarefa_separacao_id` e `peso_tarefa` (tarefa do próprio item)
  permanecem. O detalhamento multi-tarefa que estava em `tarefas_separacao`
  (posição `x/y` e peso de cada tarefa da pré-fatura) pode ser reexposto como
  `varchar` delimitado se o ERP precisar;
- toda expressão textual passou a ser `varchar(4000)` — **o que não bastou**,
  ver abaixo.

### Tamanho das colunas: `varchar(4000)` não serve

O `VARCHAR2` do Oracle tem limite de 4000 **bytes**, e o gateway dimensiona pelo
pior caso do UTF-8 (4 bytes por caractere). Um `varchar(4000)` do PostgreSQL é
lido como 16000 bytes, estoura o limite e a coluna é degradada de novo para
`LONG`/CLOB. O sintoma muda de `ORA-00600` para:

```
ORA-00997: uso inválido do tipo de dados LONG
```

que aparece inclusive quando a query seleciona só colunas numéricas, porque
basta a coluna `LONG` ser usada no `WHERE`.

Correção na migration `2026_09_03_160000_align_customer_packaging_report_view_with_erp_types`:
cada coluna de texto recebe o tamanho medido nos dados, com **teto de `varchar(500)`**
(2000 bytes no pior caso do UTF-8, folgado abaixo dos 4000). As colunas nativas
`varchar(1000)` (`observation` de `shipping_packagings` e de `stock_ios`) também
foram reduzidas, porque 1000 × 4 = 4000 bytes fica exatamente no limite.

### Tipos equivalentes aos do Senior

Os campos que o layout `E135PFA` define como numéricos ou data deixam de ser texto. Além de
alinhar o contrato com o ERP, isso os tira de vez do risco de `LONG`: só texto passa pela
regra de bytes do `VARCHAR2`.

| Coluna da view | Campo Senior | Máscara | Tipo na view |
| --- | --- | --- | --- |
| `prefatura_numane` | NumAne | `ZZZ.ZZZ.ZZZ.ZZ9` | `bigint` |
| `prefatura_numpfa` | NumPfa | `ZZZ.ZZZ.ZZ9` | `integer` |
| `cliente_codigo` | CodCli | `ZZZ.ZZZ.ZZ9` | `bigint` |
| `transportadora_codigo` | CodTra | `ZZZ.ZZZ.ZZ9` | `bigint` |
| `codigo_forma_pagamento` | CodFpg | `Z9` | `smallint` |
| `valor_prefatura` | VlrPfa | `Z.ZZZ.ZZZ.ZZZ.ZZ9,99` | `numeric(15,2)` |
| `peso_bruto_prefatura` | PesBru | `ZZZ.ZZZ.ZZ9,9[5]` | `numeric(14,5)` |
| `peso_liquido_prefatura` | PesLiq | `ZZZ.ZZZ.ZZ9,9[5]` | `numeric(14,5)` |
| `peso_bruto_item_prefatura` | PesBru (item) | `ZZZ.ZZZ.ZZ9,9[5]` | `numeric(14,5)` |
| `data_faturamento` | PRVFAT | — | `date` |
| `sequencia_prefatura` | SeqPes | — | `integer` |
| `numero_pedido` | NumPed | — | `bigint` |
| `OP` | production_order | — | `integer` |

Continuam texto **por decisão**, porque o dado impede a conversão:

| Coluna | Máscara | Motivo medido na base |
| --- | --- | --- |
| `codigo_condicao_pagamento` | `U[6]` | 57.093 com zero à esquerda (`016`) e 139 com hífen (`027-1`) |
| `pedido_cliente` | `U[20]` | 659.003 alfanuméricos |
| `produto_cliente` | — | 599.596 alfanuméricos |
| `produto_codigo` | — | 59.384 alfanuméricos; é o filtro principal do ERP |
| `produto_derivacao` | — | 30.655 com zero à esquerda — `01` é diferente de `1` |
| `tipo_frete` | `U` | indicador C/F |
| `codigo_e_c` | `U` | indicador S/N |

Todo cast numérico é protegido por regex (`CASE WHEN ... ~ '^-?[0-9]+$' THEN ...`): um valor fora
do formato vira `NULL` em vez de derrubar a view inteira no `SELECT`. Hoje não há nenhum caso
assim, mas `PRVFAT` já chega em dois formatos (`2025-05-30T00:00:00` e `2025-04-22`), então a
origem não é confiável.

Faltam os layouts das tabelas relacionadas (cadastro de cliente, representante, transportadora,
condição e forma de pagamento) para dimensionar `cliente_nome`, `cidade_cliente`, `uf_cliente`,
`representante`, `transportadora_nome`, as descrições de pagamento e o bloco `ORDER_DETAILS`.
Esses seguem dimensionados pelo dado observado.

### Permissão da role do database link

O `PG_DBLINK` conecta no PostgreSQL como a role **`rexlink`**. `DROP VIEW`
derruba o `GRANT SELECT` dela e o ERP passa a receber
`ERROR: permission denied for view vw_relatorio_embalagem_cliente`. Toda
migration que recria a view com `DROP VIEW` precisa reaplicar:

```sql
GRANT SELECT ON vw_relatorio_embalagem_cliente TO rexlink;
```

guardado por `EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'rexlink')`, porque
a role não existe em dev/CI.

### Regras para manutenção da view

1. Nenhuma coluna pode sair como `json`/`jsonb`/`text` nem `varchar` sem tamanho.
2. Nenhuma coluna pode passar de `varchar(500)`.
2. Campo que o Senior define como numérico ou data sai tipado, com cast protegido por regex.
   Campo com zero à esquerda ou conteúdo alfanumérico continua texto.
3. Recriação com `DROP VIEW` obriga a reaplicar o `GRANT` para `rexlink`.
   Quando a mudança não altera colunas, preferir `CREATE OR REPLACE VIEW`, que
   preserva o grant.
