- Implementadas ações de Editar e Excluir na página de Gestão de FOT - Adicionado filtro de busca para FOTs - Corrigido desalinhamento de colunas na tabela de Gestão de FOT - Atualizado FotForm para suportar a edição de registros existentes - Corrigido erro de renderização do React no Dashboard mapeando corretamente os objetos de atribuição - Removidos dados de mock (INITIAL_EVENTS) e corrigido erro de referência nula no DataContext - Adicionados métodos de atualização/exclusão ao apiService
231 lines
6 KiB
Go
231 lines
6 KiB
Go
package main
|
||
|
||
import (
|
||
"context"
|
||
"database/sql"
|
||
"encoding/csv"
|
||
"fmt"
|
||
"io"
|
||
"log"
|
||
"os"
|
||
"strconv"
|
||
"strings"
|
||
|
||
_ "github.com/jackc/pgx/v5/stdlib"
|
||
"github.com/joho/godotenv"
|
||
"golang.org/x/text/encoding/charmap"
|
||
"golang.org/x/text/transform"
|
||
)
|
||
|
||
func main() {
|
||
// Load .env
|
||
// Try loading from current directory first, typical when running 'go run cmd/importer/main.go' from root
|
||
if err := godotenv.Load(".env"); err != nil {
|
||
// Fallback for when running from cmd/importer
|
||
if err := godotenv.Load("../../.env"); err != nil {
|
||
log.Println("Warning: .env file not found in .env or ../../.env")
|
||
}
|
||
}
|
||
|
||
dbURL := os.Getenv("DB_DSN")
|
||
if dbURL == "" {
|
||
log.Fatal("DB_DSN is not set")
|
||
}
|
||
|
||
db, err := sql.Open("pgx", dbURL)
|
||
if err != nil {
|
||
log.Fatalf("Failed to connect to database: %v", err)
|
||
}
|
||
defer db.Close()
|
||
|
||
if err := db.Ping(); err != nil {
|
||
log.Fatalf("Failed to ping database: %v", err)
|
||
}
|
||
|
||
// Open CSV File
|
||
filename := "importacao_fots.csv"
|
||
f, err := os.Open(filename)
|
||
if err != nil {
|
||
log.Fatalf("Failed to open CSV file: %v", err)
|
||
}
|
||
defer f.Close()
|
||
|
||
// Use Windows-1252 Decoder
|
||
r := csv.NewReader(transform.NewReader(f, charmap.Windows1252.NewDecoder()))
|
||
r.Comma = ';'
|
||
r.LazyQuotes = true // Allow messy quotes
|
||
|
||
// Read Header
|
||
header, err := r.Read()
|
||
if err != nil {
|
||
log.Fatalf("Failed to read header: %v", err)
|
||
}
|
||
fmt.Printf("Header: %v\n", header)
|
||
|
||
ctx := context.Background()
|
||
rowsProcessed := 0
|
||
rowsInserted := 0
|
||
rowsFailed := 0
|
||
|
||
for {
|
||
record, err := r.Read()
|
||
if err == io.EOF {
|
||
break
|
||
}
|
||
if err != nil {
|
||
log.Printf("Error reading row %d: %v. Skipping.", rowsProcessed+1, err)
|
||
rowsFailed++
|
||
continue
|
||
}
|
||
rowsProcessed++
|
||
|
||
// Map Columns (Indices based on "FOT;Empresa;EF I / EF II;Observa‡äes;Institui‡Æo;Ano Formatura;Cidade;Estado;Gastos Capta‡Æo;Pr‚ Venda")
|
||
// 0: FOT
|
||
// 1: Empresa
|
||
// 2: Cursos (was EF I / EF II)
|
||
// 3: Observacoes
|
||
// 4: Instituicao
|
||
// 5: Ano Formatura
|
||
// 6: Cidade
|
||
// 7: Estado
|
||
// 8: Gastos Captacao
|
||
// 9: Pre Venda
|
||
|
||
fotStr := strings.TrimSpace(record[0])
|
||
empresaName := strings.TrimSpace(record[1])
|
||
cursoName := strings.TrimSpace(record[2])
|
||
observacoes := strings.TrimSpace(record[3])
|
||
instituicao := strings.TrimSpace(record[4])
|
||
anoFormatura := strings.TrimSpace(record[5])
|
||
cidade := strings.TrimSpace(record[6])
|
||
estado := strings.TrimSpace(record[7])
|
||
gastosStr := strings.TrimSpace(record[8])
|
||
preVendaStr := strings.TrimSpace(record[9])
|
||
|
||
// Basic Validation
|
||
if fotStr == "" || fotStr == "FOT" { // Skip header repetition or empty lines
|
||
continue
|
||
}
|
||
|
||
fot, err := strconv.Atoi(fotStr)
|
||
if err != nil {
|
||
log.Printf("Row %d: Invalid FOT '%s'. Skipping.", rowsProcessed, fotStr)
|
||
rowsFailed++
|
||
continue
|
||
}
|
||
|
||
// 1. Upsert Empresa
|
||
var empresaID string
|
||
if empresaName == "" {
|
||
empresaName = "Não Identificada"
|
||
}
|
||
err = db.QueryRowContext(ctx, `
|
||
INSERT INTO empresas (nome) VALUES ($1)
|
||
ON CONFLICT (nome) DO UPDATE SET nome = EXCLUDED.nome
|
||
RETURNING id`, empresaName).Scan(&empresaID)
|
||
if err != nil {
|
||
log.Printf("Row %d: Failed to upsert empresa '%s': %v", rowsProcessed, empresaName, err)
|
||
rowsFailed++
|
||
continue
|
||
}
|
||
|
||
// 2. Upsert Curso
|
||
var cursoID string
|
||
if cursoName == "" || cursoName == "-" {
|
||
cursoName = "Geral"
|
||
}
|
||
err = db.QueryRowContext(ctx, `
|
||
INSERT INTO cursos (nome) VALUES ($1)
|
||
ON CONFLICT (nome) DO UPDATE SET nome = EXCLUDED.nome
|
||
RETURNING id`, cursoName).Scan(&cursoID)
|
||
if err != nil {
|
||
log.Printf("Row %d: Failed to upsert curso '%s': %v", rowsProcessed, cursoName, err)
|
||
rowsFailed++
|
||
continue
|
||
}
|
||
|
||
// 3. Upsert Ano Formatura
|
||
var anoID string
|
||
if anoFormatura == "" {
|
||
anoFormatura = "Indefinido"
|
||
}
|
||
err = db.QueryRowContext(ctx, `
|
||
INSERT INTO anos_formaturas (ano_semestre) VALUES ($1)
|
||
ON CONFLICT (ano_semestre) DO UPDATE SET ano_semestre = EXCLUDED.ano_semestre
|
||
RETURNING id`, anoFormatura).Scan(&anoID)
|
||
if err != nil {
|
||
log.Printf("Row %d: Failed to upsert ano '%s': %v", rowsProcessed, anoFormatura, err)
|
||
rowsFailed++
|
||
continue
|
||
}
|
||
|
||
// 4. Parse Currency (Gastos Captação)
|
||
// Format: "R$ 2.176,60" -> 2176.60
|
||
gastosVal := 0.0
|
||
if gastosStr != "" && gastosStr != "-" {
|
||
// Remove "R$", "." and trim
|
||
clean := strings.ReplaceAll(gastosStr, "R$", "")
|
||
clean = strings.ReplaceAll(clean, ".", "") // Remove thousand separator
|
||
clean = strings.ReplaceAll(clean, ",", ".") // Replace decimal separator
|
||
clean = strings.TrimSpace(clean)
|
||
|
||
// Handle trailing/leading spaces hidden chars if any
|
||
// Just parse float
|
||
val, err := strconv.ParseFloat(clean, 64)
|
||
if err == nil {
|
||
gastosVal = val
|
||
} else {
|
||
// log.Printf("Row %d: Warning parsing gastos '%s' -> '%s': %v", rowsProcessed, gastosStr, clean, err)
|
||
}
|
||
}
|
||
|
||
// 5. Parse Boolean (Pre Venda)
|
||
preVenda := false
|
||
if strings.ToLower(preVendaStr) == "sim" {
|
||
preVenda = true
|
||
}
|
||
|
||
// 6. Insert Cadastro FOT
|
||
_, err = db.ExecContext(ctx, `
|
||
INSERT INTO cadastro_fot (
|
||
fot, empresa_id, curso_id, ano_formatura_id,
|
||
instituicao, cidade, estado, observacoes,
|
||
gastos_captacao, pre_venda
|
||
) VALUES (
|
||
$1, $2, $3, $4,
|
||
$5, $6, $7, $8,
|
||
$9, $10
|
||
)
|
||
ON CONFLICT (fot) DO UPDATE SET
|
||
empresa_id = EXCLUDED.empresa_id,
|
||
curso_id = EXCLUDED.curso_id,
|
||
ano_formatura_id = EXCLUDED.ano_formatura_id,
|
||
instituicao = EXCLUDED.instituicao,
|
||
cidade = EXCLUDED.cidade,
|
||
estado = EXCLUDED.estado,
|
||
observacoes = EXCLUDED.observacoes,
|
||
gastos_captacao = EXCLUDED.gastos_captacao,
|
||
pre_venda = EXCLUDED.pre_venda,
|
||
updated_at = NOW()
|
||
`,
|
||
fot, empresaID, cursoID, anoID,
|
||
instituicao, cidade, estado, observacoes,
|
||
gastosVal, preVenda,
|
||
)
|
||
|
||
if err != nil {
|
||
log.Printf("Row %d: Failed to insert FOT %d: %v", rowsProcessed, fot, err)
|
||
rowsFailed++
|
||
} else {
|
||
rowsInserted++
|
||
if rowsInserted%50 == 0 {
|
||
fmt.Printf("Progress: %d records upserted...\n", rowsInserted)
|
||
}
|
||
}
|
||
}
|
||
|
||
fmt.Printf("\n--- Import Summary ---\n")
|
||
fmt.Printf("Total Rows Processed: %d\n", rowsProcessed)
|
||
fmt.Printf("Successfully Upserted: %d\n", rowsInserted)
|
||
fmt.Printf("Failed: %d\n", rowsFailed)
|
||
}
|