Add roteiro para corrigir warnings.
This commit is contained in:
+8
-12
@@ -26,17 +26,13 @@ Se não for muito custoso para implementar, pode ser uma boa ideia adicionar uma
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### F-02 — Validação de Tamanho de Arquivo (PRD RF01.3) (Implementar)
|
### F-02 — Validação de Tamanho de Arquivo (PRD RF01.3) (Implementado)
|
||||||
|
|
||||||
**Problema:** O PRD especifica limite de 50 MB, mas não há validação implementada. Arquivos muito grandes podem travar a UI durante a leitura.
|
**Status:** Já implementado. Ambos `csv_reader.rs` e `xlsx_reader.rs` verificam o tamanho do arquivo
|
||||||
|
antes de qualquer leitura via constante `LIMITE_BYTES = 50 MB`, retornando `ErroArquivo::TamanhoExcedido`
|
||||||
|
se excedido — exibido como modal de erro pela UI.
|
||||||
|
|
||||||
**Solução:** Verificar `fs::metadata(caminho)?.len()` antes de iniciar importação. Exibir modal de erro se exceder o limite.
|
**Esforço estimado:** Baixo (30min) — *concluído*
|
||||||
|
|
||||||
**Escopo técnico:**
|
|
||||||
- Adicionar checagem em `importar_arquivo.rs` antes de chamar CSV/XLSX reader
|
|
||||||
- Constante `LIMITE_ARQUIVO_BYTES: u64 = 50 * 1024 * 1024`
|
|
||||||
|
|
||||||
**Esforço estimado:** Baixo (30min)
|
|
||||||
**Impacto:** Médio — evita travamentos inesperados
|
**Impacto:** Médio — evita travamentos inesperados
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -154,8 +150,8 @@ Verificar se já existe no codigo, pois na tela de configuração do Layout ele
|
|||||||
|
|
||||||
## Resumo
|
## Resumo
|
||||||
|
|
||||||
| ID | Feature | Esforço | Impacto | Prioridade |
|
| ID | Feature | Esforço | Impacto | Prioridade | Status |
|
||||||
|---|---|---|---|---|
|
|---|---|---|---|---|---|
|
||||||
| F-01 | Exportar resultado em CSV | Médio | Alto | Alta |
|
| F-01 | Exportar resultado em CSV | Médio | Alto | Alta |
|
||||||
| F-02 | Validação de tamanho de arquivo | Baixo | Médio | Alta |
|
| F-02 | Validação de tamanho de arquivo | Baixo | Médio | Alta |
|
||||||
| F-03 | Preview de colunas antes da análise | Alto | Alto | Média |
|
| F-03 | Preview de colunas antes da análise | Alto | Alto | Média |
|
||||||
@@ -163,4 +159,4 @@ Verificar se já existe no codigo, pois na tela de configuração do Layout ele
|
|||||||
| F-06 | Recarregar arquivo sem reconfigurar | Baixo | Médio | Média |
|
| F-06 | Recarregar arquivo sem reconfigurar | Baixo | Médio | Média |
|
||||||
| F-07 | Auto-detecção de delimitador CSV | Médio | Baixo-Médio | Média |
|
| F-07 | Auto-detecção de delimitador CSV | Médio | Baixo-Médio | Média |
|
||||||
| F-08 | Auto-detecção de encoding CSV | Médio | Baixo | Baixa |
|
| F-08 | Auto-detecção de encoding CSV | Médio | Baixo | Baixa |
|
||||||
| F-09 | Agrupamento de faltantes no PDF | Baixo | Baixo-Médio | Baixa |
|
| F-09 | Agrupamento de faltantes no PDF | Baixo | Baixo-Médio | Baixa | ✅ Implementado |
|
||||||
|
|||||||
+217
@@ -0,0 +1,217 @@
|
|||||||
|
# Roteiro de Desenvolvimento — Comparador de Notas
|
||||||
|
|
||||||
|
**Gerado em:** 04/03/2026
|
||||||
|
**Baseado em:** PRD v1.7 + análise estática do código atual
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Warnings do Compilador
|
||||||
|
|
||||||
|
São 7 warnings ativos (`cargo build`). Nenhum é crítico, mas todos devem ser eliminados para manter o código limpo.
|
||||||
|
|
||||||
|
### W-01 — `Nota.data` nunca lida
|
||||||
|
|
||||||
|
**Arquivo:** `src/domain/entities/nota.rs:17`
|
||||||
|
**Causa:** O campo `data: Option<NaiveDate>` é armazenado na struct `Nota`, mas nenhum código de negócio ou de UI o consome atualmente.
|
||||||
|
**Contexto PRD:** O campo Data é definido no modelo de dados (seção 5) como opcional. O PRD diz que é "exibida como informação adicional no relatório PDF. Não participa de nenhuma regra de validação ou cálculo."
|
||||||
|
**Solução:** Usar o campo `data` na geração do PDF (`pdf_generator.rs`), exibindo a data da nota nas seções de faltantes ou duplicatas quando disponível. Isso resolve o warning e implementa o requisito do PRD.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### W-02 — `IntervaloSerie.minimo` e `IntervaloSerie.maximo` nunca lidos
|
||||||
|
|
||||||
|
**Arquivo:** `src/domain/entities/resultado_analise.rs:23-24`
|
||||||
|
**Causa:** Os campos `minimo: u64` e `maximo: u64` existem na struct `IntervaloSerie`, mas nenhum código os consome após o cálculo.
|
||||||
|
**Contexto PRD:** RF04 define que o intervalo de faltantes é avaliado entre o menor e o maior número encontrado. O aviso de confirmação (RF04 — Proteção contra intervalos anormalmente grandes) exibe o intervalo calculado ao usuário.
|
||||||
|
**Solução:** Exibir `minimo` e `maximo` na mensagem de confirmação em `app.rs` quando o intervalo exceder 10.000. Exemplo: `"Série 001 / NFE: intervalo de 999.996 faltantes detectado (de 1 a 1.000.000)"`. O PRD especifica exatamente esse formato de aviso.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### W-03 — `ResultadoAnalise::sem_inconsistencias` nunca usada
|
||||||
|
|
||||||
|
**Arquivo:** `src/domain/entities/resultado_analise.rs:51`
|
||||||
|
**Causa:** O método `pub fn sem_inconsistencias()` está definido mas nunca chamado.
|
||||||
|
**Contexto PRD:** Nenhuma funcionalidade específica é mapeada diretamente para este método.
|
||||||
|
**Solução (opção A — remover):** Remover o método se não houver uso planejado próximo. É código morto.
|
||||||
|
**Solução (opção B — usar):** Usar o método na tela de resultado (`resultado.rs`) para exibir um badge "Sem inconsistências" ou mensagem de sucesso no topo quando `sem_inconsistencias()` for `true`. Melhora a UX e elimina o warning.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### W-04 — `EstadoModal::Informacao` nunca construída
|
||||||
|
|
||||||
|
**Arquivo:** `src/ui/app.rs:39`
|
||||||
|
**Causa:** A variante `Informacao { titulo, mensagem }` existe no enum `EstadoModal` mas nenhum código a instancia. Existe `Aviso` e `Erro` para as demais situações.
|
||||||
|
**Solução:** Remover a variante `Informacao` do enum se não houver distinção visual planejada entre "Informação" e "Aviso". Alternativamente, usá-la onde hoje se usa `Aviso` para casos puramente informativos (sem cor amarela de alerta). A variante mais simples é a remoção.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### W-05 — `Message::CancelarExpansao` nunca construída
|
||||||
|
|
||||||
|
**Arquivo:** `src/ui/message.rs:76`
|
||||||
|
**Causa:** A variante `CancelarExpansao` existe no enum `Message` e é tratada em `update()` (muda o estado para `ConfigurandoColunas`), mas nenhuma tela emite essa mensagem. O cancelamento do intervalo excessivo é feito via `Message::ModalCancelado`.
|
||||||
|
**Solução:** Remover `Message::CancelarExpansao` do enum. O comportamento de cancelar a expansão já está implementado em `Message::ModalCancelado` (`app.rs:629-636`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### W-06 — `theme::cabecalho_tabela` nunca usada
|
||||||
|
|
||||||
|
**Arquivo:** `src/ui/theme.rs:140`
|
||||||
|
**Causa:** A função de estilo `cabecalho_tabela` está definida mas nenhuma tela a usa.
|
||||||
|
**Contexto:** O componente `tabela_preview.rs` usa estilo inline para o cabeçalho em vez desta função.
|
||||||
|
**Solução:** Aplicar `t::cabecalho_tabela` no cabeçalho da tabela de preview em `tabela_preview.rs`, substituindo o estilo inline atual. Isso centraliza o estilo e elimina o warning.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### W-07 — `theme::badge_sucesso` nunca usada
|
||||||
|
|
||||||
|
**Arquivo:** `src/ui/theme.rs:149`
|
||||||
|
**Causa:** A função de estilo `badge_sucesso` está definida mas nenhuma tela a usa. Os badges de sucesso são aplicados via `t::badge_aviso` (amarelo) ou texto colorido com `t::SUCCESS`.
|
||||||
|
**Contexto PRD:** A tela de resultado exibe indicador de completude por série. Quando 100% completo (sem faltantes), poderia exibir um badge verde.
|
||||||
|
**Solução (opção A — usar):** Exibir badge `t::badge_sucesso` na tela de resultado quando uma série não tem faltantes, em vez de apenas texto verde. Melhora a distinção visual.
|
||||||
|
**Solução (opção B — remover):** Remover se não houver uso planejado.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Funcionalidades Faltantes (vs. PRD v1.7)
|
||||||
|
|
||||||
|
### F-01 — Campo `data` no PDF (RF07.1)
|
||||||
|
|
||||||
|
**Status:** Não implementado
|
||||||
|
**PRD:** Seção 5 e RF07.1 — "Data de emissão da nota. Exibida como informação adicional no relatório PDF."
|
||||||
|
**Situação atual:** O campo `data` é lido do arquivo (`csv_reader.rs`, `xlsx_reader.rs`) e armazenado em `Nota.data`, mas `pdf_generator.rs` não o utiliza em nenhuma seção.
|
||||||
|
**O que falta:** Exibir a data de cada nota nas seções de faltantes e/ou duplicatas do PDF quando disponível. Por exemplo, no detalhamento de duplicatas: `"NF 42 / Série 001 — 3 ocorrências (última: 15/01/2025)"`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### F-02 — Mensagem de confirmação com intervalo exato (RF04)
|
||||||
|
|
||||||
|
**Status:** Parcialmente implementado
|
||||||
|
**PRD:** RF04 — "Exibir aviso informando o intervalo calculado (ex: 'Série 001 / NFE: intervalo de 999.996 faltantes detectado')"
|
||||||
|
**Situação atual:** O aviso de confirmação em `app.rs:930-948` exibe apenas `"Série X: intervalo de N faltantes detectado"`, mas `minimo` e `maximo` de `IntervaloSerie` não são incluídos na mensagem. Os campos existem mas não são usados (W-02 acima).
|
||||||
|
**O que falta:** Incluir `minimo` e `maximo` na mensagem de confirmação para que o usuário veja o intervalo completo.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### F-03 — Limite de tamanho de arquivo 50 MB (RF01.3)
|
||||||
|
|
||||||
|
**Status:** Não implementado
|
||||||
|
**PRD:** RF01.3 — "O sistema deve recusar arquivos maiores que 50 MB e exibir mensagem de erro ao usuário."
|
||||||
|
**Situação atual:** Nenhuma verificação de tamanho de arquivo existe nos readers (`csv_reader.rs`, `xlsx_reader.rs`) nem em `processar_arquivo_selecionado` (`app.rs`).
|
||||||
|
**O que falta:** Verificar `std::fs::metadata(caminho)?.len()` antes de processar. Se > 50 MB, retornar `ErroArquivo::TamanhoExcedido` (o tipo já está definido em `domain/errors.rs`) e exibir modal de erro.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### F-04 — Arquivo XLSX corrompido: limpar estado (RF01.2)
|
||||||
|
|
||||||
|
**Status:** Parcialmente implementado
|
||||||
|
**PRD:** RF01.2 — "Se o arquivo não puder ser lido, exibir mensagem de erro em modal e limpar o arquivo carregado; o estado anterior é descartado."
|
||||||
|
**Situação atual:** `Message::XlsxErroAoCarregar` exibe o erro em modal, mas não limpa `self.caminho_arquivo` nem `self.nome_arquivo` nem `self.preview_arquivo`. O estado anterior do arquivo permanece em memória.
|
||||||
|
**O que falta:** No handler de `Message::XlsxErroAoCarregar`, zerar `self.caminho_arquivo = None`, `self.nome_arquivo = String::new()`, `self.preview_arquivo = None` e `self.notas_importadas.clear()` antes de exibir o erro.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### F-05 — Relatório de linhas malformadas ao usuário (RF01.1 / RF01.2)
|
||||||
|
|
||||||
|
**Status:** Parcialmente implementado
|
||||||
|
**PRD:** RF01.1/RF01.2 — "o sistema deve reportar ao usuário quais linhas foram descartadas, sem interromper a importação"
|
||||||
|
**Situação atual:** `ResumoAvisos` agrega contagens, e o modal de avisos exibe resumos do tipo "32 linhas descartadas por malformação". Porém, **os números de linha específicos** (ex: "linhas 15, 42, 103 descartadas") não são rastreados nem exibidos.
|
||||||
|
**O que falta:** Avaliar se o PRD exige listagem de números de linha individuais (o texto diz "quais linhas foram descartadas"). A interpretação atual (contagens por categoria) pode ser suficiente, mas merece revisão explícita com o product owner. Se linhas individuais forem necessárias, `ResumoAvisos` precisa armazenar `Vec<usize>` por categoria.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### F-06 — Validação de campos duplicados no mapeamento (RF02.3)
|
||||||
|
|
||||||
|
**Status:** Não implementado
|
||||||
|
**PRD:** RF02 — Tratamento de Erros de Configuração: "Dois campos mapeados para o mesmo índice/posição → Bloquear e exibir erro de validação imediatamente, antes de executar a análise"
|
||||||
|
**Situação atual:** `executar_importacao_sync` delega a validação ao reader, mas não há verificação explícita de índices/posições duplicados antes do disparo da análise. Se o usuário mapear, por exemplo, Numero e Serie para o mesmo índice CSV, os dados serão importados incorretamente sem aviso.
|
||||||
|
**O que falta:** Adicionar validação em `disparar_importacao` (ou no use case `importar_arquivo`) que verifique se algum índice (CSV) ou posição (XLSX) aparece em mais de um campo mapeado e retorne erro descritivo antes de iniciar o processamento.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### F-07 — Índice/posição inválido: identificar qual campo (RF02)
|
||||||
|
|
||||||
|
**Status:** Parcialmente implementado
|
||||||
|
**PRD:** RF02 — "Índice/posição configurado não existe no arquivo importado → Exibir erro ao usuário identificando qual campo está inválido"
|
||||||
|
**Situação atual:** Os readers retornam erros quando um índice não existe, mas a mensagem de erro pode não identificar claramente qual campo (Numero, Serie, Valor etc.) causou o problema.
|
||||||
|
**O que falta:** Garantir que as mensagens de erro de importação identifiquem o campo problemático pelo nome lógico (ex: "Campo 'Numero': índice 5 não existe — o arquivo tem 4 colunas").
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### F-08 — Spinner/indicador visual durante análise (RNF03 / RNF04)
|
||||||
|
|
||||||
|
**Status:** Minimamente implementado
|
||||||
|
**PRD:** RNF03 — "A análise é executada em uma thread separada para não bloquear a interface gráfica"
|
||||||
|
**Situação atual:** O estado `EstadoApp::Analisando` exibe apenas dois textos estáticos ("Analisando..." e "Aguarde...") sem nenhum indicador de progresso animado.
|
||||||
|
**O que falta:** Implementar um spinner ou progress bar indeterminado na tela de análise para dar feedback visual ao usuário de que o processamento está ativo. O iced suporta animações via `Subscription` com `time::every`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### F-09 — Paginação independente por série (RF07)
|
||||||
|
|
||||||
|
**Status:** Não implementado (limitação funcional)
|
||||||
|
**PRD:** RF07 — paginação para listas longas
|
||||||
|
**Situação atual:** `app.pagina_faltantes` e `app.pagina_duplicatas` são contadores globais únicos, compartilhados entre todas as séries. Ao navegar para a página 2 de faltantes da Série 001, a paginação também afeta a Série 002 se ambas aparecerem na mesma tela.
|
||||||
|
**O que falta:** Tornar a paginação independente por `ChaveSerie`, usando um `HashMap<ChaveSerie, usize>` no estado da aplicação em vez de um único `usize` global. Isso requer mudanças em `App`, em `Message` e na tela `resultado.rs`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### F-10 — Exportação do resultado em CSV (seção 4.2 / seção 14)
|
||||||
|
|
||||||
|
**Status:** Não incluído no MVP (seção 4.2)
|
||||||
|
**PRD:** Listado como "Não Incluído (MVP)" na seção 4.2 e como evolução futura na seção 14.
|
||||||
|
**Nota:** Não é um requisito do MVP. Documentado aqui apenas para rastreabilidade.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Resumo por Prioridade
|
||||||
|
|
||||||
|
| ID | Tipo | Arquivo(s) afetado(s) | Esforço estimado |
|
||||||
|
|----|------|-----------------------|-----------------|
|
||||||
|
| W-01 | Warning → usar campo `data` | `pdf_generator.rs` | Baixo |
|
||||||
|
| W-02 | Warning → usar `minimo`/`maximo` | `app.rs` | Baixo |
|
||||||
|
| W-03 | Warning → remover ou usar | `resultado_analise.rs`, `resultado.rs` | Baixo |
|
||||||
|
| W-04 | Warning → remover variante | `app.rs` | Baixo |
|
||||||
|
| W-05 | Warning → remover variante | `message.rs`, `app.rs` | Baixo |
|
||||||
|
| W-06 | Warning → aplicar estilo | `tabela_preview.rs` | Baixo |
|
||||||
|
| W-07 | Warning → usar ou remover | `resultado.rs` ou `theme.rs` | Baixo |
|
||||||
|
| F-01 | Funcionalidade faltante | `pdf_generator.rs` | Baixo |
|
||||||
|
| F-02 | Funcionalidade incompleta | `app.rs` | Baixo |
|
||||||
|
| F-03 | Funcionalidade faltante | `csv_reader.rs`, `xlsx_reader.rs`, `app.rs` | Médio |
|
||||||
|
| F-04 | Funcionalidade incompleta | `app.rs` | Baixo |
|
||||||
|
| F-05 | Revisão de requisito | `domain/errors.rs`, readers | Médio |
|
||||||
|
| F-06 | Funcionalidade faltante | `app.rs` ou `importar_arquivo.rs` | Médio |
|
||||||
|
| F-07 | Funcionalidade incompleta | `csv_reader.rs`, `xlsx_reader.rs` | Médio |
|
||||||
|
| F-08 | UX faltante | `app.rs` | Médio |
|
||||||
|
| F-09 | Limitação funcional | `app.rs`, `message.rs`, `resultado.rs` | Alto |
|
||||||
|
| F-10 | Fora do MVP | — | — |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Ordem de Execução Sugerida
|
||||||
|
|
||||||
|
### Fase 1 — Warnings (todos baixo esforço, fazem parte da limpeza)
|
||||||
|
|
||||||
|
1. W-05: Remover `Message::CancelarExpansao`
|
||||||
|
2. W-04: Remover `EstadoModal::Informacao`
|
||||||
|
3. W-03: Decidir entre remover `sem_inconsistencias` ou usá-la em `resultado.rs`
|
||||||
|
4. W-07: Decidir entre remover `badge_sucesso` ou usá-la em `resultado.rs`
|
||||||
|
5. W-06: Aplicar `t::cabecalho_tabela` em `tabela_preview.rs`
|
||||||
|
6. W-02 + F-02: Usar `minimo`/`maximo` na mensagem de confirmação de intervalo
|
||||||
|
7. W-01 + F-01: Usar `data` no PDF
|
||||||
|
|
||||||
|
### Fase 2 — Bugs e requisitos críticos
|
||||||
|
|
||||||
|
8. F-04: Limpar estado ao falhar leitura XLSX
|
||||||
|
9. F-03: Validar limite de 50 MB
|
||||||
|
10. F-06: Validar campos duplicados no mapeamento
|
||||||
|
11. F-07: Mensagens de erro com nome do campo
|
||||||
|
|
||||||
|
### Fase 3 — Melhorias de UX
|
||||||
|
|
||||||
|
12. F-08: Spinner animado na tela de análise
|
||||||
|
13. F-09: Paginação independente por série
|
||||||
|
14. F-05: Revisar requisito de rastreamento de linhas individuais
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Fim do roteiro.*
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
use crate::domain::entities::chave_serie::ChaveSerie;
|
use crate::domain::entities::chave_serie::ChaveSerie;
|
||||||
use crate::domain::entities::resultado_analise::ResultadoAnalise;
|
use crate::domain::entities::resultado_analise::ResultadoAnalise;
|
||||||
|
use crate::domain::services::detector_sequencia::agrupar_contiguos;
|
||||||
use crate::domain::services::parser_monetario::formatar_valor_br;
|
use crate::domain::services::parser_monetario::formatar_valor_br;
|
||||||
use chrono::{DateTime, Local};
|
use chrono::{DateTime, Local};
|
||||||
use genpdf::{
|
use genpdf::{
|
||||||
@@ -125,8 +126,19 @@ impl PdfGenerator for GenpdfGenerator {
|
|||||||
chave.label(),
|
chave.label(),
|
||||||
faltantes.len()
|
faltantes.len()
|
||||||
)));
|
)));
|
||||||
let numeros: Vec<String> = faltantes.iter().map(|n| n.to_string()).collect();
|
// Agrupar faltantes contíguos em intervalos (ex: 100–104 em vez de 100, 101, 102, 103, 104)
|
||||||
doc.push(Paragraph::new(format!(" {}", numeros.join(", "))));
|
let grupos = agrupar_contiguos(faltantes);
|
||||||
|
let descricao: Vec<String> = grupos
|
||||||
|
.iter()
|
||||||
|
.map(|(ini, fim)| {
|
||||||
|
if ini == fim {
|
||||||
|
ini.to_string()
|
||||||
|
} else {
|
||||||
|
format!("{ini}–{fim} ({} notas)", fim - ini + 1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
doc.push(Paragraph::new(format!(" {}", descricao.join(", "))));
|
||||||
}
|
}
|
||||||
|
|
||||||
doc.push(Break::new(1));
|
doc.push(Break::new(1));
|
||||||
|
|||||||
@@ -44,8 +44,6 @@ pub fn aplicar_migrations(conn: &Connection) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Migration v2: adicionar índice único em layouts.nome.
|
|
||||||
|
|
||||||
/// Migration v3: adicionar colunas de tipo de documento nos layouts.
|
/// Migration v3: adicionar colunas de tipo de documento nos layouts.
|
||||||
fn migration_v3(conn: &Connection) -> Result<()> {
|
fn migration_v3(conn: &Connection) -> Result<()> {
|
||||||
conn.execute_batch(
|
conn.execute_batch(
|
||||||
@@ -55,6 +53,8 @@ fn migration_v3(conn: &Connection) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Migration v2: adicionar índice único em layouts.nome.
|
||||||
|
///
|
||||||
/// Renomeia duplicatas (sufixo com id) antes de criar o índice para não falhar
|
/// Renomeia duplicatas (sufixo com id) antes de criar o índice para não falhar
|
||||||
/// em bancos que já possuem nomes repetidos.
|
/// em bancos que já possuem nomes repetidos.
|
||||||
fn migration_v2(conn: &Connection) -> Result<()> {
|
fn migration_v2(conn: &Connection) -> Result<()> {
|
||||||
|
|||||||
@@ -62,29 +62,7 @@ pub fn ler_xlsx(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let campos: Vec<String> = row
|
let campos: Vec<String> = row.iter().map(celula_para_string).collect();
|
||||||
.iter()
|
|
||||||
.map(|cell| {
|
|
||||||
use calamine::Data;
|
|
||||||
match cell {
|
|
||||||
Data::Empty => String::new(),
|
|
||||||
Data::String(s) => s.clone(),
|
|
||||||
Data::Float(f) => {
|
|
||||||
if f.fract() == 0.0 {
|
|
||||||
format!("{}", *f as i64)
|
|
||||||
} else {
|
|
||||||
format!("{}", f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Data::Int(i) => i.to_string(),
|
|
||||||
Data::Bool(b) => b.to_string(),
|
|
||||||
Data::DateTime(dt) => dt.to_string(),
|
|
||||||
Data::DateTimeIso(s) => s.clone(),
|
|
||||||
Data::DurationIso(s) => s.clone(),
|
|
||||||
Data::Error(_) => String::new(),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// Ignorar linhas completamente em branco
|
// Ignorar linhas completamente em branco
|
||||||
if campos.iter().all(|s: &String| s.trim().is_empty()) {
|
if campos.iter().all(|s: &String| s.trim().is_empty()) {
|
||||||
@@ -113,28 +91,7 @@ pub fn preview_xlsx(caminho: &Path, nome_aba: &str) -> Result<Vec<Vec<String>>,
|
|||||||
.rows()
|
.rows()
|
||||||
.take(5)
|
.take(5)
|
||||||
.map(|row| {
|
.map(|row| {
|
||||||
row.iter()
|
row.iter().map(celula_para_string).collect()
|
||||||
.map(|cell| {
|
|
||||||
use calamine::Data;
|
|
||||||
match cell {
|
|
||||||
Data::Empty => String::new(),
|
|
||||||
Data::String(s) => s.clone(),
|
|
||||||
Data::Float(f) => {
|
|
||||||
if f.fract() == 0.0 {
|
|
||||||
format!("{}", *f as i64)
|
|
||||||
} else {
|
|
||||||
format!("{}", f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Data::Int(i) => i.to_string(),
|
|
||||||
Data::Bool(b) => b.to_string(),
|
|
||||||
Data::DateTime(dt) => dt.to_string(),
|
|
||||||
Data::DateTimeIso(s) => s.clone(),
|
|
||||||
Data::DurationIso(s) => s.clone(),
|
|
||||||
Data::Error(_) => String::new(),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@@ -178,6 +135,30 @@ pub fn parsear_letra_linha(s: &str) -> Option<Coordenada> {
|
|||||||
Some(Coordenada { coluna, linha })
|
Some(Coordenada { coluna, linha })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Converte uma célula calamine em String, usando representação legível.
|
||||||
|
/// Inteiros sem parte decimal são formatados sem ".0"; erros e células vazias
|
||||||
|
/// retornam string vazia.
|
||||||
|
fn celula_para_string(cell: &calamine::Data) -> String {
|
||||||
|
use calamine::Data;
|
||||||
|
match cell {
|
||||||
|
Data::Empty => String::new(),
|
||||||
|
Data::String(s) => s.clone(),
|
||||||
|
Data::Float(f) => {
|
||||||
|
if f.fract() == 0.0 {
|
||||||
|
format!("{}", *f as i64)
|
||||||
|
} else {
|
||||||
|
format!("{}", f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Data::Int(i) => i.to_string(),
|
||||||
|
Data::Bool(b) => b.to_string(),
|
||||||
|
Data::DateTime(dt) => dt.to_string(),
|
||||||
|
Data::DateTimeIso(s) => s.clone(),
|
||||||
|
Data::DurationIso(s) => s.clone(),
|
||||||
|
Data::Error(_) => String::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn verificar_tamanho(caminho: &Path) -> Result<(), ErroArquivo> {
|
fn verificar_tamanho(caminho: &Path) -> Result<(), ErroArquivo> {
|
||||||
let metadata =
|
let metadata =
|
||||||
std::fs::metadata(caminho).map_err(|e| ErroArquivo::ErroLeitura(e.to_string()))?;
|
std::fs::metadata(caminho).map_err(|e| ErroArquivo::ErroLeitura(e.to_string()))?;
|
||||||
|
|||||||
+7
-13
@@ -210,7 +210,7 @@ impl App {
|
|||||||
},
|
},
|
||||||
|result| match result {
|
|result| match result {
|
||||||
Ok(layouts) => Message::LayoutsRecarregados(layouts),
|
Ok(layouts) => Message::LayoutsRecarregados(layouts),
|
||||||
Err(e) => Message::ModalTextoAlterado(format!("Erro ao recarregar layouts: {}", e)),
|
Err(e) => { eprintln!("Erro ao recarregar layouts: {e}"); Message::Noop },
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1122,10 +1122,10 @@ impl App {
|
|||||||
let (conteudo, nome_sugerido) = match exportar_layout_json(&layout) {
|
let (conteudo, nome_sugerido) = match exportar_layout_json(&layout) {
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
// Não temos &mut self aqui; retornamos um command que exibe erro
|
// Não temos &mut self aqui — reutilizamos PdfExportado(Err) para
|
||||||
return Task::perform(async move { e.to_string() }, |e| {
|
// exibir o modal de erro (o handler chama self.exibir_erro).
|
||||||
Message::PdfExportado(Err(e))
|
let msg = format!("Erro ao exportar layout JSON: {}", e);
|
||||||
});
|
return Task::perform(async move { msg }, |e| Message::PdfExportado(Err(e)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1150,7 +1150,8 @@ impl App {
|
|||||||
|result| match result {
|
|result| match result {
|
||||||
Ok(_) => Message::Noop,
|
Ok(_) => Message::Noop,
|
||||||
Err(e) if e.is_empty() => Message::Noop,
|
Err(e) if e.is_empty() => Message::Noop,
|
||||||
Err(e) => Message::PdfExportado(Err(format!("Erro ao salvar JSON: {}", e))),
|
// Reutiliza PdfExportado(Err) para exibir modal de erro.
|
||||||
|
Err(e) => Message::PdfExportado(Err(format!("Erro ao salvar layout JSON: {}", e))),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1212,13 +1213,6 @@ impl App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Mensagens extras para o fluxo XLSX ─────────────────────────────────────
|
|
||||||
// (precisamos adicionar estas variantes ao enum Message)
|
|
||||||
|
|
||||||
// Nota: as variantes AbaxlsxCarregadas e XlsxErroAoCarregar serão tratadas
|
|
||||||
// no match do update() principal via um segundo match arm de Message.
|
|
||||||
// Para isso, precisamos adicionar estas variantes ao message.rs também.
|
|
||||||
|
|
||||||
// ─── Função auxiliar síncrona para executar importação em spawn_blocking ─────
|
// ─── Função auxiliar síncrona para executar importação em spawn_blocking ─────
|
||||||
|
|
||||||
fn executar_importacao_sync(
|
fn executar_importacao_sync(
|
||||||
|
|||||||
Reference in New Issue
Block a user