Create AGENTS.md
This commit is contained in:
@@ -0,0 +1,304 @@
|
||||
# Domain — AGENTS.md
|
||||
|
||||
Camada de domínio do projeto `comparador-notas`. Contém as entidades, serviços
|
||||
de negócio e erros tipados. Não possui dependências de infraestrutura — todo
|
||||
acesso a banco, arquivos ou UI é responsabilidade das camadas superiores.
|
||||
|
||||
---
|
||||
|
||||
## Estrutura dos arquivos
|
||||
|
||||
```
|
||||
src/domain/
|
||||
├── mod.rs
|
||||
├── errors.rs # Enums de erro tipados
|
||||
├── entities/
|
||||
│ ├── mod.rs
|
||||
│ ├── chave_serie.rs # Chave composta (serie + documento_tipo)
|
||||
│ ├── layout.rs # Entidades de configuração de layout CSV/XLSX
|
||||
│ ├── nota.rs # Entidade Nota Fiscal
|
||||
│ ├── resultado_analise.rs # Resultados de análise (pré e completo)
|
||||
│ └── serie.rs # Validação de série
|
||||
└── services/
|
||||
├── mod.rs
|
||||
├── detector_duplicidade.rs # Detecção de notas duplicadas
|
||||
├── detector_sequencia.rs # Detecção de notas faltantes na sequência
|
||||
└── parser_monetario.rs # Parsing e formatação de valores monetários
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## errors.rs
|
||||
|
||||
Erros tipados com a crate `thiserror`. Cada domínio tem seu próprio enum.
|
||||
|
||||
### `ErroSerie`
|
||||
|
||||
| Variante | Mensagem |
|
||||
|---|---|
|
||||
| `Invalida(String)` | Série inválida: deve conter de 1 a 3 dígitos numéricos |
|
||||
| `Vazia` | Série vazia |
|
||||
|
||||
### `ErroValor`
|
||||
|
||||
| Variante | Mensagem |
|
||||
|---|---|
|
||||
| `Negativo(String)` | Valor negativo não é permitido |
|
||||
| `NaoNumerico(String)` | Valor não numérico |
|
||||
|
||||
### `ErroLayout`
|
||||
|
||||
| Variante | Mensagem |
|
||||
|---|---|
|
||||
| `CampoObrigatorioAusente(String)` | Campo obrigatório ausente |
|
||||
| `JsonMalformado(String)` | JSON malformado |
|
||||
| `NomeConflitante(String)` | Layout com esse nome já existe |
|
||||
|
||||
### `ErroArquivo`
|
||||
|
||||
| Variante | Mensagem |
|
||||
|---|---|
|
||||
| `TamanhoExcedido(u64)` | Arquivo > 50 MB |
|
||||
| `Corrompido(String)` | Arquivo corrompido ou ilegível |
|
||||
| `ErroLeitura(String)` | Erro genérico de leitura |
|
||||
|
||||
### `ResumoAvisos`
|
||||
|
||||
Estrutura de avisos não-fatais acumulados durante a importação de um arquivo.
|
||||
|
||||
```rust
|
||||
pub struct ResumoAvisos {
|
||||
pub linhas_malformadas: usize,
|
||||
pub numeros_invalidos: usize,
|
||||
pub series_invalidas: usize,
|
||||
pub valores_invalidos: usize,
|
||||
pub detalhes: Vec<String>, // mensagens individuais por linha
|
||||
}
|
||||
```
|
||||
|
||||
- `tem_avisos()` → `true` se qualquer contador > 0
|
||||
- `linhas_para_exibir()` → `Vec<String>` com resumo para exibição em modal
|
||||
|
||||
---
|
||||
|
||||
## entities/
|
||||
|
||||
### `chave_serie.rs` — `ChaveSerie`
|
||||
|
||||
Chave composta que identifica um grupo de notas fiscais. Combina série com
|
||||
tipo de documento (`NFE`, `NFCE`, etc.).
|
||||
|
||||
```rust
|
||||
pub struct ChaveSerie {
|
||||
pub serie: String,
|
||||
pub documento_tipo: Option<String>,
|
||||
}
|
||||
```
|
||||
|
||||
- Deriva `Hash`, `Eq`, `Ord` — usada como chave em `HashMap` e para ordenação.
|
||||
- `new(serie, documento_tipo)` — construtor.
|
||||
- `label()` — formata para exibição:
|
||||
- `Some(tipo)` → `"001 / NFE"`
|
||||
- `None` → `"001"`
|
||||
|
||||
### `nota.rs` — `Nota`
|
||||
|
||||
Entidade central. `(numero, serie, documento_tipo)` é o identificador único.
|
||||
|
||||
```rust
|
||||
pub struct Nota {
|
||||
pub numero: u64,
|
||||
pub serie: String,
|
||||
pub documento_tipo: Option<String>, // None quando não mapeado
|
||||
pub valor: Option<Decimal>,
|
||||
pub data: Option<NaiveDate>, // usado no PDF, não em regras
|
||||
}
|
||||
```
|
||||
|
||||
### `serie.rs` — `validar_serie`
|
||||
|
||||
```rust
|
||||
pub fn validar_serie(s: &str) -> Result<String, ErroSerie>
|
||||
```
|
||||
|
||||
Valida via regex `^[0-9]{1,3}$` (1 a 3 dígitos numéricos). Faz trim antes
|
||||
de validar. Usa `OnceLock` para compilar o regex uma única vez.
|
||||
|
||||
### `resultado_analise.rs`
|
||||
|
||||
Dois tipos de resultado que modelam o fluxo de análise em duas etapas:
|
||||
|
||||
#### `ResultadoPreAnalise`
|
||||
|
||||
Resultado intermediário — calculado sem expandir a lista completa de faltantes.
|
||||
Usado para verificar se algum intervalo excede 10.000 registros (RF04) antes
|
||||
de pedir confirmação ao usuário.
|
||||
|
||||
```rust
|
||||
pub struct ResultadoPreAnalise {
|
||||
pub intervalos_por_serie: HashMap<ChaveSerie, IntervaloSerie>,
|
||||
pub duplicadas_por_serie: HashMap<ChaveSerie, Vec<(u64, usize)>>,
|
||||
pub soma_total: Decimal,
|
||||
pub soma_por_serie: HashMap<ChaveSerie, Decimal>,
|
||||
pub total_por_serie: HashMap<ChaveSerie, usize>,
|
||||
}
|
||||
```
|
||||
|
||||
#### `IntervaloSerie`
|
||||
|
||||
```rust
|
||||
pub struct IntervaloSerie {
|
||||
pub minimo: u64,
|
||||
pub maximo: u64,
|
||||
pub contagem_faltantes: u64,
|
||||
}
|
||||
```
|
||||
|
||||
- `excede_limite(limite)` → `contagem_faltantes > limite`
|
||||
|
||||
#### `ResultadoAnalise`
|
||||
|
||||
Resultado completo com a lista materializada de faltantes.
|
||||
|
||||
```rust
|
||||
pub struct ResultadoAnalise {
|
||||
pub faltantes_por_serie: HashMap<ChaveSerie, Vec<u64>>, // ordenados crescentemente
|
||||
pub duplicadas_por_serie: HashMap<ChaveSerie, Vec<(u64, usize)>>,
|
||||
pub soma_total: Decimal,
|
||||
pub soma_por_serie: HashMap<ChaveSerie, Decimal>,
|
||||
pub total_por_serie: HashMap<ChaveSerie, usize>,
|
||||
}
|
||||
```
|
||||
|
||||
Métodos auxiliares:
|
||||
- `sem_inconsistencias()` → `true` se não há faltantes nem duplicatas
|
||||
- `total_faltantes()` → soma do tamanho de todas as listas de faltantes
|
||||
- `total_duplicatas()` → soma do número de grupos de duplicatas
|
||||
|
||||
### `layout.rs`
|
||||
|
||||
Entidades de configuração de layout de arquivo.
|
||||
|
||||
#### `TipoArquivo`
|
||||
|
||||
```rust
|
||||
pub enum TipoArquivo { Csv, Xlsx }
|
||||
```
|
||||
|
||||
#### `LayoutCsv`
|
||||
|
||||
| Campo | Tipo | Descrição |
|
||||
|---|---|---|
|
||||
| `delimitador` | `char` | `','`, `';'` ou `'\t'` |
|
||||
| `encoding` | `String` | `"utf-8"` ou `"windows-1252"` |
|
||||
| `linha_cabecalho` | `usize` | Linha do cabeçalho (base 1). `0` = sem cabeçalho |
|
||||
| `indice_numero` | `usize` | Índice da coluna Numero (base 0) |
|
||||
| `indice_serie` | `usize` | Índice da coluna Serie (base 0) |
|
||||
| `indice_valor` | `Option<usize>` | Índice da coluna Valor (base 0), opcional |
|
||||
| `indice_data` | `Option<usize>` | Índice da coluna Data (base 0), opcional |
|
||||
| `indice_documento_tipo` | `Option<usize>` | Índice da coluna Tipo Documento (base 0), opcional |
|
||||
|
||||
#### `LayoutXlsx`
|
||||
|
||||
| Campo | Tipo | Descrição |
|
||||
|---|---|---|
|
||||
| `aba` | `String` | Nome da aba a processar |
|
||||
| `pos_numero` | `String` | Posição LetraLinha (ex: `"D3"`) |
|
||||
| `pos_serie` | `String` | Posição LetraLinha (ex: `"B3"`) |
|
||||
| `pos_valor` | `Option<String>` | Posição LetraLinha, opcional |
|
||||
| `pos_data` | `Option<String>` | Posição LetraLinha, opcional |
|
||||
| `pos_documento_tipo` | `Option<String>` | Posição LetraLinha, opcional |
|
||||
|
||||
#### `Layout` (enum)
|
||||
|
||||
```rust
|
||||
pub enum Layout {
|
||||
Csv { id: Option<i64>, nome: String, config: LayoutCsv },
|
||||
Xlsx { id: Option<i64>, nome: String, config: LayoutXlsx },
|
||||
}
|
||||
```
|
||||
|
||||
Métodos: `id()`, `nome()`, `tipo()`.
|
||||
|
||||
#### `LayoutJson` (serialização)
|
||||
|
||||
Representação `serde` com tag `"tipo"` para importação/exportação em JSON.
|
||||
Implementa `TryFrom<LayoutJson> for Layout` (valida campos obrigatórios) e
|
||||
`From<&Layout> for LayoutJson`.
|
||||
|
||||
---
|
||||
|
||||
## services/
|
||||
|
||||
### `detector_duplicidade.rs`
|
||||
|
||||
#### `detectar_duplicidades(notas) -> HashMap<(u64, String, Option<String>), usize>`
|
||||
|
||||
Conta ocorrências de cada `(numero, serie, documento_tipo)`. Retém apenas
|
||||
grupos com mais de uma ocorrência.
|
||||
|
||||
#### `duplicidades_por_serie(notas) -> HashMap<ChaveSerie, Vec<(u64, usize)>>`
|
||||
|
||||
Agrupa o resultado de `detectar_duplicidades` por `ChaveSerie`. Cada vetor
|
||||
é ordenado por `numero` crescente.
|
||||
|
||||
Regras:
|
||||
- O mesmo número em séries diferentes **não** é duplicata.
|
||||
- O mesmo número com `documento_tipo` diferente **não** é duplicata.
|
||||
- O mesmo número com mesma série e mesmo `documento_tipo` **é** duplicata.
|
||||
|
||||
---
|
||||
|
||||
### `detector_sequencia.rs`
|
||||
|
||||
#### Constante
|
||||
|
||||
```rust
|
||||
pub const LIMITE_FALTANTES: u64 = 10_000;
|
||||
```
|
||||
|
||||
#### `calcular_intervalo(notas) -> Option<IntervaloSerie>`
|
||||
|
||||
Calcula `minimo`, `maximo` e `contagem_faltantes` de forma incremental
|
||||
(usando `windows(2)`) **sem materializar** a lista de faltantes. Deduplica
|
||||
números antes do cálculo para não contar duplicatas como faltantes.
|
||||
|
||||
#### `detectar_faltantes(notas) -> Vec<u64>`
|
||||
|
||||
Materializa a lista completa de faltantes em ordem crescente. Deve ser
|
||||
chamado apenas após confirmação do usuário quando `calcular_intervalo`
|
||||
indica que o intervalo excede `LIMITE_FALTANTES`.
|
||||
|
||||
#### `agrupar_contiguos(faltantes) -> Vec<(u64, u64)>`
|
||||
|
||||
Recebe uma lista **já ordenada** de números faltantes e retorna intervalos
|
||||
contíguos como pares `(inicio, fim)`. Números isolados têm `inicio == fim`.
|
||||
|
||||
Exemplo: `[1, 2, 3, 5, 8, 9]` → `[(1, 3), (5, 5), (8, 9)]`
|
||||
|
||||
---
|
||||
|
||||
### `parser_monetario.rs`
|
||||
|
||||
#### `parse_valor(input) -> Result<Decimal, ErroValor>`
|
||||
|
||||
Faz o parsing de uma string monetária suportando formatos brasileiro e americano.
|
||||
Rejeita valores negativos.
|
||||
|
||||
Algoritmo (RF06):
|
||||
|
||||
| Condição | Regra |
|
||||
|---|---|
|
||||
| Contém ponto **e** vírgula | Último separador é o decimal |
|
||||
| Apenas ponto, 3 dígitos após | Separador de milhar (`1.234` → `1234`) |
|
||||
| Apenas ponto, outros casos | Decimal (`1000.00`) |
|
||||
| Apenas vírgula, 3 dígitos após | Separador de milhar (`1,234` → `1234`) |
|
||||
| Apenas vírgula, outros casos | Decimal (`1000,00` → `1000.00`) |
|
||||
| Sem separador | Número inteiro |
|
||||
|
||||
Aceita prefixo `R$` (case-insensitive).
|
||||
|
||||
#### `formatar_valor_br(valor) -> String`
|
||||
|
||||
Formata `Decimal` para exibição brasileira com 2 casas decimais e pontos de
|
||||
milhar. Ex: `1234567.89` → `"1.234.567,89"`.
|
||||
Reference in New Issue
Block a user