use crate::domain::entities::layout::{Layout, LayoutCsv, LayoutXlsx}; use rusqlite::{params, Connection, Result}; /// Converte o char delimitador para string legível no banco. fn delim_para_str(c: char) -> String { match c { '\t' => "tab".to_string(), c => c.to_string(), } } /// Converte a string armazenada no banco de volta para char delimitador. fn str_para_delim(s: &str) -> char { match s { "tab" => '\t', s => s.chars().next().unwrap_or(';'), } } /// Salva um layout no banco. Retorna o id gerado. pub fn salvar(conn: &Connection, layout: &Layout) -> Result { match layout { Layout::Csv { nome, config, .. } => { conn.execute( "INSERT INTO layouts (nome, tipo, delimitador, encoding, linha_cabecalho, indice_numero, indice_serie, indice_valor, indice_data, indice_documento_tipo) VALUES (?1, 'csv', ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)", params![ nome, delim_para_str(config.delimitador), config.encoding, config.linha_cabecalho as i64, config.indice_numero as i64, config.indice_serie as i64, config.indice_valor.map(|v| v as i64), config.indice_data.map(|v| v as i64), config.indice_documento_tipo.map(|v| v as i64), ], )?; Ok(conn.last_insert_rowid()) } Layout::Xlsx { nome, config, .. } => { conn.execute( "INSERT INTO layouts (nome, tipo, aba, pos_numero, pos_serie, pos_valor, pos_data, pos_documento_tipo) VALUES (?1, 'xlsx', ?2, ?3, ?4, ?5, ?6, ?7)", params![ nome, config.aba, config.pos_numero, config.pos_serie, config.pos_valor, config.pos_data, config.pos_documento_tipo, ], )?; Ok(conn.last_insert_rowid()) } } } /// Atualiza um layout existente no banco. /// Zera explicitamente os campos do tipo oposto para evitar dados órfãos. pub fn atualizar(conn: &Connection, layout: &Layout) -> Result<()> { let id = layout .id() .ok_or_else(|| rusqlite::Error::InvalidParameterName("id ausente".to_string()))?; match layout { Layout::Csv { nome, config, .. } => { conn.execute( "UPDATE layouts SET nome = ?1, delimitador = ?2, encoding = ?3, linha_cabecalho = ?4, indice_numero = ?5, indice_serie = ?6, indice_valor = ?7, indice_data = ?8, indice_documento_tipo = ?9, aba = NULL, pos_numero = NULL, pos_serie = NULL, pos_valor = NULL, pos_data = NULL, pos_documento_tipo = NULL WHERE id = ?10", params![ nome, delim_para_str(config.delimitador), config.encoding, config.linha_cabecalho as i64, config.indice_numero as i64, config.indice_serie as i64, config.indice_valor.map(|v| v as i64), config.indice_data.map(|v| v as i64), config.indice_documento_tipo.map(|v| v as i64), id, ], )?; } Layout::Xlsx { nome, config, .. } => { conn.execute( "UPDATE layouts SET nome = ?1, aba = ?2, pos_numero = ?3, pos_serie = ?4, pos_valor = ?5, pos_data = ?6, pos_documento_tipo = ?7, delimitador = NULL, encoding = NULL, linha_cabecalho = NULL, indice_numero = NULL, indice_serie = NULL, indice_valor = NULL, indice_data = NULL, indice_documento_tipo = NULL WHERE id = ?8", params![ nome, config.aba, config.pos_numero, config.pos_serie, config.pos_valor, config.pos_data, config.pos_documento_tipo, id, ], )?; } } Ok(()) } /// Lista todos os layouts salvos. pub fn listar(conn: &Connection) -> Result> { let mut stmt = conn.prepare( "SELECT id, nome, tipo, delimitador, encoding, linha_cabecalho, indice_numero, indice_serie, indice_valor, indice_data, aba, pos_numero, pos_serie, pos_valor, pos_data, indice_documento_tipo, pos_documento_tipo FROM layouts ORDER BY nome ASC", )?; let layouts: Result> = stmt .query_map([], |row| { let id: i64 = row.get("id")?; let nome: String = row.get("nome")?; let tipo: String = row.get("tipo")?; if tipo == "csv" { let delim_str: String = row.get("delimitador")?; let delimitador = str_para_delim(&delim_str); Ok(Layout::Csv { id: Some(id), nome, config: LayoutCsv { delimitador, encoding: row.get("encoding")?, linha_cabecalho: row.get::<_, i64>("linha_cabecalho")? as usize, indice_numero: row.get::<_, i64>("indice_numero")? as usize, indice_serie: row.get::<_, i64>("indice_serie")? as usize, indice_valor: row .get::<_, Option>("indice_valor")? .map(|v| v as usize), indice_data: row .get::<_, Option>("indice_data")? .map(|v| v as usize), indice_documento_tipo: row .get::<_, Option>("indice_documento_tipo")? .map(|v| v as usize), }, }) } else { Ok(Layout::Xlsx { id: Some(id), nome, config: LayoutXlsx { aba: row.get("aba")?, pos_numero: row.get("pos_numero")?, pos_serie: row.get("pos_serie")?, pos_valor: row.get("pos_valor")?, pos_data: row.get("pos_data")?, pos_documento_tipo: row.get("pos_documento_tipo")?, }, }) } })? .collect(); layouts } /// Remove um layout pelo id. pub fn excluir(conn: &Connection, id: i64) -> Result<()> { conn.execute("DELETE FROM layouts WHERE id = ?1", [id])?; Ok(()) } /// Verifica se já existe um layout com o nome fornecido. pub fn existe_nome(conn: &Connection, nome: &str) -> Result { let count: i64 = conn.query_row( "SELECT COUNT(*) FROM layouts WHERE nome = ?1", [nome], |row| row.get(0), )?; Ok(count > 0) } /// Verifica se existe um layout com o nome fornecido, excluindo o registro com o id dado. /// Usado para validar conflito de nome ao renomear um layout existente. pub fn existe_nome_excluindo_id(conn: &Connection, nome: &str, id: i64) -> Result { let count: i64 = conn.query_row( "SELECT COUNT(*) FROM layouts WHERE nome = ?1 AND id != ?2", params![nome, id], |row| row.get(0), )?; Ok(count > 0) }