From 9a107ce9e8ddf461a39bf39822069ec582b90357 Mon Sep 17 00:00:00 2001 From: Jose Henrique Date: Sat, 7 Jun 2025 11:56:03 -0300 Subject: [PATCH] adding despesas e receitas --- .../Controllers/CandidatoController.cs | 17 +++ OpenCand.API/Model/ListSearchResult.cs | 10 ++ OpenCand.API/Program.cs | 1 + .../Repository/DespesaReceitaRepository.cs | 42 ++++++ OpenCand.API/Services/OpenCandService.cs | 31 ++++ OpenCand.Core/Models/Candidato.cs | 1 + OpenCand.Core/Models/Despesa.cs | 25 ++++ OpenCand.Core/Models/Receita.cs | 26 ++++ OpenCand.ETL/Config/CsvSettings.cs | 2 + OpenCand.ETL/Extensions/StringExtensions.cs | 12 ++ .../Parser/CsvMappers/BemCandidatoMap.cs | 15 -- .../Parser/CsvMappers/CandidatoMap.cs | 15 -- .../Parser/CsvMappers/RedeSocialMap.cs | 14 -- .../Parser/CsvServices/CsvFixerService.cs | 6 +- OpenCand.ETL/Parser/Models/CandidatoCSV.cs | 3 + OpenCand.ETL/Parser/Models/DespesasCSV.cs | 97 ++++++++++++ OpenCand.ETL/Parser/Models/ReceitasCSV.cs | 94 ++++++++++++ OpenCand.ETL/Parser/ParserManager.cs | 18 ++- .../BemCandidatoParserService.cs | 3 +- .../ParserServices/CandidatoParserService.cs | 1 + .../ParserServices/DespesaParserService.cs | 88 +++++++++++ .../ParserServices/ReceitaParserService.cs | 84 +++++++++++ OpenCand.ETL/Program.cs | 7 + .../Repository/CandidatoRepository.cs | 17 ++- .../Repository/DespesaReceitaRepository.cs | 139 ++++++++++++++++++ .../Services/DespesaReceitaService.cs | 54 +++++++ OpenCand.ETL/appsettings.json | 8 +- README.md | 89 ++++++++++- db/db.sql | 61 +++++++- 29 files changed, 922 insertions(+), 58 deletions(-) create mode 100644 OpenCand.API/Repository/DespesaReceitaRepository.cs create mode 100644 OpenCand.Core/Models/Despesa.cs create mode 100644 OpenCand.Core/Models/Receita.cs create mode 100644 OpenCand.ETL/Extensions/StringExtensions.cs delete mode 100644 OpenCand.ETL/Parser/CsvMappers/BemCandidatoMap.cs delete mode 100644 OpenCand.ETL/Parser/CsvMappers/CandidatoMap.cs delete mode 100644 OpenCand.ETL/Parser/CsvMappers/RedeSocialMap.cs create mode 100644 OpenCand.ETL/Parser/Models/DespesasCSV.cs create mode 100644 OpenCand.ETL/Parser/Models/ReceitasCSV.cs create mode 100644 OpenCand.ETL/Parser/ParserServices/DespesaParserService.cs create mode 100644 OpenCand.ETL/Parser/ParserServices/ReceitaParserService.cs create mode 100644 OpenCand.ETL/Repository/DespesaReceitaRepository.cs create mode 100644 OpenCand.ETL/Services/DespesaReceitaService.cs diff --git a/OpenCand.API/Controllers/CandidatoController.cs b/OpenCand.API/Controllers/CandidatoController.cs index 97b4cc7..69bace7 100644 --- a/OpenCand.API/Controllers/CandidatoController.cs +++ b/OpenCand.API/Controllers/CandidatoController.cs @@ -22,6 +22,11 @@ namespace OpenCand.API.Controllers [EnableRateLimiting(RateLimitingConfig.CandidatoSearchPolicy)] public async Task CandidatoSearch([FromQuery] string q) { + if (string.IsNullOrEmpty(q) || q.Length == 1) + { + throw new ArgumentException("Query parameter 'q' cannot be null/empty.", nameof(q)); + } + return await openCandService.SearchCandidatosAsync(q); } @@ -52,5 +57,17 @@ namespace OpenCand.API.Controllers await Task.Delay(randomWait); return await openCandService.GetCandidatoCpfById(id); } + + [HttpGet("{id}/despesas")] + public async Task GetCandidatoDespesas([FromRoute] Guid id) + { + return await openCandService.GetDespesasByIdAndYear(id); + } + + [HttpGet("{id}/receitas")] + public async Task GetCandidatoReceitas([FromRoute] Guid id) + { + return await openCandService.GetReceitasByIdAndYear(id); + } } } diff --git a/OpenCand.API/Model/ListSearchResult.cs b/OpenCand.API/Model/ListSearchResult.cs index 6e27536..e0f2a8b 100644 --- a/OpenCand.API/Model/ListSearchResult.cs +++ b/OpenCand.API/Model/ListSearchResult.cs @@ -21,4 +21,14 @@ namespace OpenCand.API.Model { public string Cpf { get; set; } } + + public class DespesasResult + { + public List Despesas { get; set; } + } + + public class ReceitaResult + { + public List Receitas { get; set; } + } } diff --git a/OpenCand.API/Program.cs b/OpenCand.API/Program.cs index eda9cbd..2e39980 100644 --- a/OpenCand.API/Program.cs +++ b/OpenCand.API/Program.cs @@ -59,6 +59,7 @@ namespace OpenCand.API builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); + builder.Services.AddScoped(); builder.Services.AddScoped(); } } diff --git a/OpenCand.API/Repository/DespesaReceitaRepository.cs b/OpenCand.API/Repository/DespesaReceitaRepository.cs new file mode 100644 index 0000000..b961d85 --- /dev/null +++ b/OpenCand.API/Repository/DespesaReceitaRepository.cs @@ -0,0 +1,42 @@ +using Dapper; +using Microsoft.Extensions.Configuration; +using Npgsql; +using OpenCand.Core.Models; + +namespace OpenCand.Repository +{ + public class DespesaReceitaRepository : BaseRepository + { + public DespesaReceitaRepository(IConfiguration configuration) : base(configuration) + { + } + + public async Task> GetDespesasByCandidatoIdYearAsync(Guid idcandidato) + { + using (var connection = new NpgsqlConnection(ConnectionString)) + { + return (await connection.QueryAsync(@" + SELECT * FROM despesas_candidato + WHERE idcandidato = @idcandidato + ORDER BY valor DESC;", new + { + idcandidato + })).AsList(); + } + } + + public async Task> GetReceitasByCandidatoIdYearAsync(Guid idcandidato) + { + using (var connection = new NpgsqlConnection(ConnectionString)) + { + return (await connection.QueryAsync(@" + SELECT * FROM receitas_candidato + WHERE idcandidato = @idcandidato + ORDER BY valor DESC;", new + { + idcandidato + })).AsList(); + } + } + } +} diff --git a/OpenCand.API/Services/OpenCandService.cs b/OpenCand.API/Services/OpenCandService.cs index eafdb68..af1cd0e 100644 --- a/OpenCand.API/Services/OpenCandService.cs +++ b/OpenCand.API/Services/OpenCandService.cs @@ -12,6 +12,7 @@ namespace OpenCand.API.Services private readonly OpenCandRepository openCandRepository; private readonly CandidatoRepository candidatoRepository; private readonly BemCandidatoRepository bemCandidatoRepository; + private readonly DespesaReceitaRepository despesaReceitaRepository; private readonly IConfiguration configuration; private readonly FotosSettings fotoSettings; private readonly ILogger logger; @@ -20,6 +21,7 @@ namespace OpenCand.API.Services OpenCandRepository openCandRepository, CandidatoRepository candidatoRepository, BemCandidatoRepository bemCandidatoRepository, + DespesaReceitaRepository despesaReceitaRepository, IOptions fotoSettings, IConfiguration configuration, ILogger logger) @@ -27,6 +29,7 @@ namespace OpenCand.API.Services this.openCandRepository = openCandRepository; this.candidatoRepository = candidatoRepository; this.bemCandidatoRepository = bemCandidatoRepository; + this.despesaReceitaRepository = despesaReceitaRepository; this.fotoSettings = fotoSettings.Value; this.configuration = configuration; this.logger = logger; @@ -113,5 +116,33 @@ namespace OpenCand.API.Services Cpf = result }; } + + public async Task GetDespesasByIdAndYear(Guid idcandidato) + { + var result = await despesaReceitaRepository.GetDespesasByCandidatoIdYearAsync(idcandidato); + if (result == null) + { + return new DespesasResult(); + } + + return new DespesasResult() + { + Despesas = result.OrderByDescending(d => d.Ano).ThenByDescending(d => d.Valor).ToList() + }; + } + + public async Task GetReceitasByIdAndYear(Guid idcandidato) + { + var result = await despesaReceitaRepository.GetReceitasByCandidatoIdYearAsync(idcandidato); + if (result == null) + { + return new ReceitaResult(); + } + + return new ReceitaResult() + { + Receitas = result.OrderByDescending(d => d.Ano).ThenByDescending(d => d.Valor).ToList() + }; + } } } diff --git a/OpenCand.Core/Models/Candidato.cs b/OpenCand.Core/Models/Candidato.cs index 4cc9b9f..d9716db 100644 --- a/OpenCand.Core/Models/Candidato.cs +++ b/OpenCand.Core/Models/Candidato.cs @@ -40,6 +40,7 @@ namespace OpenCand.Core.Models public string Apelido { get; set; } public string SqCandidato { get; set; } public int Ano { get; set; } + public string Turno { get; set; } public string TipoEleicao { get; set; } public string SiglaUF { get; set; } public string NomeUE { get; set; } diff --git a/OpenCand.Core/Models/Despesa.cs b/OpenCand.Core/Models/Despesa.cs new file mode 100644 index 0000000..125bba0 --- /dev/null +++ b/OpenCand.Core/Models/Despesa.cs @@ -0,0 +1,25 @@ +namespace OpenCand.Core.Models +{ + public class Despesa + { + public Guid IdDespesa { get; set; } + public Guid IdCandidato { get; set; } + public int Ano { get; set; } + public int Turno { get; set; } + + public string SqCandidato { get; set; } + + public string SgPartido { get; set; } + public string TipoFornecedor { get; set; } + public string CpfFornecedor { get; set; } + public string CnpjFornecedor { get; set; } + public string NomeFornecedor { get; set; } + public string NomeFornecedorRFB { get; set; } + public string MunicipioFornecedor { get; set; } + public string TipoDocumento { get; set; } + public DateTime? DataDespesa { get; set; } + public string OrigemDespesa { get; set; } + public string Descricao { get; set; } + public float Valor { get; set; } + } +} diff --git a/OpenCand.Core/Models/Receita.cs b/OpenCand.Core/Models/Receita.cs new file mode 100644 index 0000000..0599017 --- /dev/null +++ b/OpenCand.Core/Models/Receita.cs @@ -0,0 +1,26 @@ +namespace OpenCand.Core.Models +{ + public class Receita + { + public Guid IdReceita { get; set; } + public Guid IdCandidato { get; set; } + public int Ano { get; set; } + public int Turno { get; set; } + + public string SqCandidato { get; set; } + + public string SgPartido { get; set; } + public string FonteReceita { get; set; } + public string OrigemReceita { get; set; } + public string NaturezaReceita { get; set; } + public string EspecieReceita { get; set; } + public string CpfDoador { get; set; } + public string CnpjDoador { get; set; } + public string NomeDoador { get; set; } + public string NomeDoadorRFB { get; set; } + public string MunicipioDoador { get; set; } + public DateTime? DataReceita { get; set; } + public string Descricao { get; set; } + public float Valor { get; set; } + } +} diff --git a/OpenCand.ETL/Config/CsvSettings.cs b/OpenCand.ETL/Config/CsvSettings.cs index 24394ff..0da0c04 100644 --- a/OpenCand.ETL/Config/CsvSettings.cs +++ b/OpenCand.ETL/Config/CsvSettings.cs @@ -5,5 +5,7 @@ namespace OpenCand.Config public string CandidatosFolder { get; set; } = string.Empty; public string BensCandidatosFolder { get; set; } = string.Empty; public string RedesSociaisFolder { get; set; } = string.Empty; + public string ReceitaCandidatoFolder { get; set; } = string.Empty; + public string DespesaCandidatoFolder { get; set; } = string.Empty; } } diff --git a/OpenCand.ETL/Extensions/StringExtensions.cs b/OpenCand.ETL/Extensions/StringExtensions.cs new file mode 100644 index 0000000..00f2f62 --- /dev/null +++ b/OpenCand.ETL/Extensions/StringExtensions.cs @@ -0,0 +1,12 @@ +namespace OpenCand.ETL.Extensions +{ + public static class StringExtensions + { + public static bool IsNullOrEmpty(this string value) + { + return string.IsNullOrEmpty(value) || + value == "#NE" || + value == "#NULO"; + } + } +} diff --git a/OpenCand.ETL/Parser/CsvMappers/BemCandidatoMap.cs b/OpenCand.ETL/Parser/CsvMappers/BemCandidatoMap.cs deleted file mode 100644 index ceb731b..0000000 --- a/OpenCand.ETL/Parser/CsvMappers/BemCandidatoMap.cs +++ /dev/null @@ -1,15 +0,0 @@ -using CsvHelper.Configuration; -using OpenCand.Parser.Models; -using System.Globalization; - -namespace OpenCand.Parser.CsvMappers -{ - public class BemCandidatoMap : ClassMap - { - public BemCandidatoMap() - { - AutoMap(CultureInfo.InvariantCulture); - // Explicitly handle any special mappings if needed - } - } -} diff --git a/OpenCand.ETL/Parser/CsvMappers/CandidatoMap.cs b/OpenCand.ETL/Parser/CsvMappers/CandidatoMap.cs deleted file mode 100644 index 72d4556..0000000 --- a/OpenCand.ETL/Parser/CsvMappers/CandidatoMap.cs +++ /dev/null @@ -1,15 +0,0 @@ -using CsvHelper.Configuration; -using OpenCand.Parser.Models; -using System.Globalization; - -namespace OpenCand.Parser.CsvMappers -{ - public class CandidatoMap : ClassMap - { - public CandidatoMap() - { - AutoMap(CultureInfo.InvariantCulture); - // Explicitly handle any special mappings if needed - } - } -} diff --git a/OpenCand.ETL/Parser/CsvMappers/RedeSocialMap.cs b/OpenCand.ETL/Parser/CsvMappers/RedeSocialMap.cs deleted file mode 100644 index 779ef61..0000000 --- a/OpenCand.ETL/Parser/CsvMappers/RedeSocialMap.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Globalization; -using CsvHelper.Configuration; -using OpenCand.Parser.Models; - -namespace OpenCand.ETL.Parser.CsvMappers -{ - public class RedeSocialMap : ClassMap - { - public RedeSocialMap() - { - AutoMap(CultureInfo.InvariantCulture); - } - } -} diff --git a/OpenCand.ETL/Parser/CsvServices/CsvFixerService.cs b/OpenCand.ETL/Parser/CsvServices/CsvFixerService.cs index 13a178b..9167aa4 100644 --- a/OpenCand.ETL/Parser/CsvServices/CsvFixerService.cs +++ b/OpenCand.ETL/Parser/CsvServices/CsvFixerService.cs @@ -73,8 +73,10 @@ namespace OpenCand.Parser.Services { if (columns.Length > headerCount) { - logger.LogCritical($"FixCsvFile - Line {i + 1} has {columns.Length} columns, expected {headerCount}. Halting process."); - return string.Empty; // Critical error, cannot fix this line => needs manual intervention + // logger.LogCritical($"FixCsvFile - Line {i + 1} has {columns.Length} columns, expected {headerCount}. Halting process."); + // return string.Empty; // Critical error, cannot fix this line => needs manual intervention + // consider as normal line + break; } logger.LogWarning($"FixCsvFile - Line {i + 1} has {columns.Length} columns, expected {headerCount}. Attempting to fix [i = {lineJump}]..."); diff --git a/OpenCand.ETL/Parser/Models/CandidatoCSV.cs b/OpenCand.ETL/Parser/Models/CandidatoCSV.cs index 4ba07b6..98a987b 100644 --- a/OpenCand.ETL/Parser/Models/CandidatoCSV.cs +++ b/OpenCand.ETL/Parser/Models/CandidatoCSV.cs @@ -11,6 +11,9 @@ namespace OpenCand.Parser.Models [Name("TP_ABRANGENCIA")] public string TipoAbrangencia { get; set; } + [Name("NR_TURNO")] + public string Turno { get; set; } + [Name("SG_UF")] public string SiglaUF { get; set; } diff --git a/OpenCand.ETL/Parser/Models/DespesasCSV.cs b/OpenCand.ETL/Parser/Models/DespesasCSV.cs new file mode 100644 index 0000000..abd6d02 --- /dev/null +++ b/OpenCand.ETL/Parser/Models/DespesasCSV.cs @@ -0,0 +1,97 @@ +using CsvHelper.Configuration.Attributes; + +namespace OpenCand.Parser.Models +{ + public class DespesasCSV + { + [Name("AA_ELEICAO")] + public int AnoEleicao { get; set; } + + [Name("ST_TURNO")] + public string Turno { get; set; } + + [Name("DT_PRESTACAO_CONTAS")] + public string DataPrestacaoContas { get; set; } + + [Name("SQ_PRESTADOR_CONTAS")] + public string SequencialPrestadorContas { get; set; } + + [Name("SG_UF")] + public string SiglaUF { get; set; } + + [Name("NM_UE")] + public string NomeUE { get; set; } + + [Name("NR_CNPJ_PRESTADOR_CONTA")] + public string CnpjPrestadorConta { get; set; } + + [Name("SQ_CANDIDATO")] + public string SequencialCandidato { get; set; } + + [Name("NR_CPF_CANDIDATO")] + public string CpfCandidato { get; set; } + + [Name("SG_PARTIDO")] + public string SiglaPartido { get; set; } + + [Name("DS_TIPO_FORNECEDOR")] + public string TipoFornecedor { get; set; } + + [Name("CD_CNAE_FORNECEDOR")] + public string CodigoCnaeFornecedor { get; set; } + + [Name("DS_CNAE_FORNECEDOR")] + public string DescricaoCnaeFornecedor { get; set; } + + [Name("NR_CPF_CNPJ_FORNECEDOR")] + public string CpfCnpjFornecedor { get; set; } + + [Name("NM_FORNECEDOR")] + public string NomeFornecedor { get; set; } + + [Name("NM_FORNECEDOR_RFB")] + public string NomeFornecedorRFB { get; set; } + + [Name("SG_UF_FORNECEDOR")] + public string SiglaUFFornecedor { get; set; } + + [Name("NM_MUNICIPIO_FORNECEDOR")] + public string NomeMunicipioFornecedor { get; set; } + + [Name("SQ_CANDIDATO_FORNECEDOR")] + public string SequencialCandidatoFornecedor { get; set; } + + [Name("NR_CANDIDATO_FORNECEDOR")] + public string NumeroCandidatoFornecedor { get; set; } + + [Name("DS_CARGO_FORNECEDOR")] + public string CargoFornecedor { get; set; } + + [Name("NR_PARTIDO_FORNECEDOR")] + public string NumeroPartidoFornecedor { get; set; } + + [Name("SG_PARTIDO_FORNECEDOR")] + public string SiglaPartidoFornecedor { get; set; } + + [Name("NM_PARTIDO_FORNECEDOR")] + public string NomePartidoFornecedor { get; set; } + + [Name("DS_TIPO_DOCUMENTO")] + public string TipoDocumento { get; set; } + + [Name("DS_ORIGEM_DESPESA")] + public string OrigemDespesa { get; set; } + + [Name("SQ_DESPESA")] + public string SequencialDespesa { get; set; } + + [Name("DT_DESPESA")] + public string DataDespesa { get; set; } + + [Name("DS_DESPESA")] + public string DescricaoDespesa { get; set; } + + [Name("VR_DESPESA_CONTRATADA")] + public float ValorDespesaContratada { get; set; } + } +} diff --git a/OpenCand.ETL/Parser/Models/ReceitasCSV.cs b/OpenCand.ETL/Parser/Models/ReceitasCSV.cs new file mode 100644 index 0000000..4ac7be0 --- /dev/null +++ b/OpenCand.ETL/Parser/Models/ReceitasCSV.cs @@ -0,0 +1,94 @@ +using CsvHelper.Configuration.Attributes; + +namespace OpenCand.Parser.Models +{ + public class ReceitasCSV + { + [Name("AA_ELEICAO")] + public int AnoEleicao { get; set; } + + [Name("ST_TURNO")] + public string Turno { get; set; } + + [Name("SQ_PRESTADOR_CONTAS")] + public string SequencialPrestadorContas { get; set; } + + [Name("SG_UF")] + public string SiglaUF { get; set; } + + [Name("NM_UE")] + public string NomeUE { get; set; } + + [Name("NR_CNPJ_PRESTADOR_CONTA")] + public string CnpjPrestadorConta { get; set; } + + [Name("SQ_CANDIDATO")] + public string SequencialCandidato { get; set; } + + [Name("NR_CPF_CANDIDATO")] + public string CpfCandidato { get; set; } + + [Name("SG_PARTIDO")] + public string SiglaPartido { get; set; } + + [Name("DS_FONTE_RECEITA")] + public string FonteReceita { get; set; } + + [Name("DS_ORIGEM_RECEITA")] + public string OrigemReceita { get; set; } + + [Name("DS_NATUREZA_RECEITA")] + public string NaturezaReceita { get; set; } + + [Name("DS_ESPECIE_RECEITA")] + public string EspecieReceita { get; set; } + + [Name("CD_CNAE_DOADOR")] + public string CodigoCnaeDoador { get; set; } + + [Name("DS_CNAE_DOADOR")] + public string DescricaoCnaeDoador { get; set; } + + [Name("NR_CPF_CNPJ_DOADOR")] + public string CpfCnpjDoador { get; set; } + + [Name("NM_DOADOR")] + public string NomeDoador { get; set; } + + [Name("NM_DOADOR_RFB")] + public string NomeDoadorRFB { get; set; } + + [Name("SG_UF_DOADOR")] + public string SiglaUFDoaror { get; set; } + + [Name("NM_MUNICIPIO_DOADOR")] + public string NomeMunicipioDoador { get; set; } + + [Name("SQ_CANDIDATO_DOADOR")] + public string SequencialCandidatoDoador { get; set; } + + [Name("SG_PARTIDO_DOADOR")] + public string SiglaPartidoDoador { get; set; } + + [Name("NR_RECIBO_DOACAO")] + public string NumeroReciboDoacao { get; set; } + + [Name("NR_DOCUMENTO_DOACAO")] + public string NumeroDocumentoDoacao { get; set; } + + [Name("SQ_RECEITA")] + public string SequencialReceita { get; set; } + + [Name("DT_RECEITA")] + public string DataReceita { get; set; } + + [Name("DS_RECEITA")] + public string DescricaoReceita { get; set; } + + [Name("VR_RECEITA")] + public float ValorReceita { get; set; } + + [Name("DS_GENERO")] + public string Genero { get; set; } + } +} diff --git a/OpenCand.ETL/Parser/ParserManager.cs b/OpenCand.ETL/Parser/ParserManager.cs index 731682c..17e3ba6 100644 --- a/OpenCand.ETL/Parser/ParserManager.cs +++ b/OpenCand.ETL/Parser/ParserManager.cs @@ -15,6 +15,8 @@ namespace OpenCand.Parser private readonly CsvParserService candidatoParserService; private readonly CsvParserService bemCandidatoParserService; private readonly CsvParserService redeSocialParserService; + private readonly CsvParserService despesaParserService; + private readonly CsvParserService receitaParserService; private readonly string BasePath; @@ -24,7 +26,9 @@ namespace OpenCand.Parser IConfiguration configuration, CsvParserService candidatoParserService, CsvParserService bemCandidatoParserService, - CsvParserService redeSocialParserService) + CsvParserService redeSocialParserService, + CsvParserService despesaParserService, + CsvParserService receitaParserService) { this.logger = logger; this.csvSettings = csvSettings.Value; @@ -32,6 +36,8 @@ namespace OpenCand.Parser this.candidatoParserService = candidatoParserService; this.bemCandidatoParserService = bemCandidatoParserService; this.redeSocialParserService = redeSocialParserService; + this.despesaParserService = despesaParserService; + this.receitaParserService = receitaParserService; // Get the base path from either SampleFolder in csvSettings or the BasePath in configuration BasePath = configuration.GetValue("BasePath") ?? string.Empty; @@ -49,10 +55,14 @@ namespace OpenCand.Parser var candidatosDirectory = Path.Combine(BasePath, csvSettings.CandidatosFolder); var bensCandidatosDirectory = Path.Combine(BasePath, csvSettings.BensCandidatosFolder); var redesSociaisDirectory = Path.Combine(BasePath, csvSettings.RedesSociaisFolder); + var despesasDirectory = Path.Combine(BasePath, csvSettings.DespesaCandidatoFolder); + var receitasDirectory = Path.Combine(BasePath, csvSettings.ReceitaCandidatoFolder); - await ParseFolder(candidatosDirectory, candidatoParserService); - await ParseFolder(bensCandidatosDirectory, bemCandidatoParserService); - await ParseFolder(redesSociaisDirectory, redeSocialParserService); + //await ParseFolder(candidatosDirectory, candidatoParserService); + //await ParseFolder(bensCandidatosDirectory, bemCandidatoParserService); + //await ParseFolder(redesSociaisDirectory, redeSocialParserService); + await ParseFolder(despesasDirectory, despesaParserService); + await ParseFolder(receitasDirectory, receitaParserService); logger.LogInformation("ParseFullDataAsync - Full data parsing completed!"); } diff --git a/OpenCand.ETL/Parser/ParserServices/BemCandidatoParserService.cs b/OpenCand.ETL/Parser/ParserServices/BemCandidatoParserService.cs index 02cf6a3..b494fee 100644 --- a/OpenCand.ETL/Parser/ParserServices/BemCandidatoParserService.cs +++ b/OpenCand.ETL/Parser/ParserServices/BemCandidatoParserService.cs @@ -5,6 +5,7 @@ using OpenCand.ETL.Contracts; using OpenCand.Parser.Models; using OpenCand.Services; using OpenCand.Parser.Services; +using OpenCand.ETL.Extensions; namespace OpenCand.ETL.Parser.ParserServices { @@ -25,7 +26,7 @@ namespace OpenCand.ETL.Parser.ParserServices { // Parse decimal value decimal? valor = null; - if (!string.IsNullOrEmpty(record.ValorBemCandidato)) + if (!record.ValorBemCandidato.IsNullOrEmpty()) { string normalizedValue = record.ValorBemCandidato.Replace(".", "").Replace(",", "."); if (decimal.TryParse(normalizedValue, NumberStyles.Any, CultureInfo.InvariantCulture, out var parsedValue)) diff --git a/OpenCand.ETL/Parser/ParserServices/CandidatoParserService.cs b/OpenCand.ETL/Parser/ParserServices/CandidatoParserService.cs index 64e7519..57bb9ef 100644 --- a/OpenCand.ETL/Parser/ParserServices/CandidatoParserService.cs +++ b/OpenCand.ETL/Parser/ParserServices/CandidatoParserService.cs @@ -63,6 +63,7 @@ namespace OpenCand.ETL.Parser.ParserServices Apelido = record.Apelido, SqCandidato = record.SequencialCandidato, Ano = record.AnoEleicao, + Turno = record.Turno, TipoEleicao = record.TipoAbrangencia, NomeUE = record.NomeUE, SiglaUF = record.SiglaUF, diff --git a/OpenCand.ETL/Parser/ParserServices/DespesaParserService.cs b/OpenCand.ETL/Parser/ParserServices/DespesaParserService.cs new file mode 100644 index 0000000..a7b4cad --- /dev/null +++ b/OpenCand.ETL/Parser/ParserServices/DespesaParserService.cs @@ -0,0 +1,88 @@ +using Microsoft.Extensions.Logging; +using OpenCand.Core.Models; +using OpenCand.ETL.Contracts; +using OpenCand.ETL.Extensions; +using OpenCand.ETL.Services; +using OpenCand.Parser.Models; + +namespace OpenCand.ETL.Parser.ParserServices +{ + public class DespesaParserService : IParserService + { + private readonly ILogger logger; + private readonly DespesaReceitaService despesaReceitaService; + + public DespesaParserService( + ILogger logger, + DespesaReceitaService despesaReceitaService) + { + this.logger = logger; + this.despesaReceitaService = despesaReceitaService; + } + + public async Task ParseObject(DespesasCSV record) + { + var despesa = new Despesa + { + SgPartido = record.SiglaPartido, + Ano = record.AnoEleicao, + Turno = int.Parse(record.Turno), + Descricao = record.DescricaoDespesa, + OrigemDespesa = record.OrigemDespesa, + MunicipioFornecedor = record.NomeMunicipioFornecedor, + NomeFornecedor = record.NomeFornecedor, + NomeFornecedorRFB = record.NomeFornecedorRFB, + SqCandidato = record.SequencialCandidato, + TipoDocumento = record.TipoDocumento, + TipoFornecedor = record.TipoFornecedor, + Valor = record.ValorDespesaContratada / 100 + }; + + if (DateTime.TryParse(record.DataDespesa, out var dataDespesa)) + { + despesa.DataDespesa = dataDespesa; + } + else + { + despesa.DataDespesa = null; + } + + if (record.CpfCnpjFornecedor.Length == 0 || record.CpfCnpjFornecedor == "-4") + { + despesa.CpfFornecedor = null; + despesa.CnpjFornecedor = null; + } + else if (record.CpfCnpjFornecedor.Length == 11) + { + despesa.CpfFornecedor = record.CpfCnpjFornecedor; + despesa.CnpjFornecedor = null; + } + else if (record.CpfCnpjFornecedor.Length == 14) + { + despesa.CnpjFornecedor = record.CpfCnpjFornecedor; + despesa.CpfFornecedor = null; + } + + if (despesa.Descricao.IsNullOrEmpty()) + despesa.Descricao = null; + if (despesa.OrigemDespesa.IsNullOrEmpty()) + despesa.OrigemDespesa = null; + if (despesa.MunicipioFornecedor.IsNullOrEmpty()) + despesa.MunicipioFornecedor = null; + if (despesa.NomeFornecedor.IsNullOrEmpty()) + despesa.NomeFornecedor = null; + if (despesa.NomeFornecedorRFB.IsNullOrEmpty()) + despesa.NomeFornecedorRFB = null; + if (despesa.TipoDocumento.IsNullOrEmpty()) + despesa.TipoDocumento = null; + if (despesa.CpfFornecedor.IsNullOrEmpty()) + despesa.CpfFornecedor = null; + if (despesa.CnpjFornecedor.IsNullOrEmpty()) + despesa.CnpjFornecedor = null; + if (despesa.TipoFornecedor.IsNullOrEmpty()) + despesa.TipoFornecedor = null; + + await despesaReceitaService.AddDespesaAsync(despesa); + } + } +} diff --git a/OpenCand.ETL/Parser/ParserServices/ReceitaParserService.cs b/OpenCand.ETL/Parser/ParserServices/ReceitaParserService.cs new file mode 100644 index 0000000..1fd3a85 --- /dev/null +++ b/OpenCand.ETL/Parser/ParserServices/ReceitaParserService.cs @@ -0,0 +1,84 @@ +using Microsoft.Extensions.Logging; +using OpenCand.Core.Models; +using OpenCand.ETL.Contracts; +using OpenCand.ETL.Extensions; +using OpenCand.ETL.Services; +using OpenCand.Parser.Models; + +namespace OpenCand.ETL.Parser.ParserServices +{ + public class ReceitaParserService : IParserService + { + private readonly ILogger logger; + private readonly DespesaReceitaService despesaReceitaService; + + public ReceitaParserService( + ILogger logger, + DespesaReceitaService despesaReceitaService) + { + this.logger = logger; + this.despesaReceitaService = despesaReceitaService; + } + + public async Task ParseObject(ReceitasCSV record) + { + var receita = new Receita + { + EspecieReceita = record.EspecieReceita, + MunicipioDoador = record.NomeMunicipioDoador, + SgPartido = record.SiglaPartido, + FonteReceita = record.FonteReceita, + NaturezaReceita = record.NaturezaReceita, + Ano = record.AnoEleicao, + Descricao = record.DescricaoReceita, + NomeDoador = record.NomeDoador, + NomeDoadorRFB = record.NomeDoadorRFB, + OrigemReceita = record.OrigemReceita, + Turno = int.Parse(record.Turno), + SqCandidato = record.SequencialCandidato, + Valor = record.ValorReceita / 100 + }; + + if (DateTime.TryParse(record.DataReceita, out var dataReceita)) + { + receita.DataReceita = dataReceita; + } + else + { + receita.DataReceita = null; + } + + if (record.CpfCnpjDoador.Length == 0 || record.CpfCnpjDoador == "-4") { + receita.CpfDoador = null; + receita.CnpjDoador = null; + } + else if (record.CpfCnpjDoador.Length == 11) + { + receita.CpfDoador = record.CpfCnpjDoador; + receita.CnpjDoador = null; + } + else if (record.CpfCnpjDoador.Length == 14) + { + receita.CnpjDoador = record.CpfCnpjDoador; + receita.CpfDoador = null; + } + + if (receita.Descricao.IsNullOrEmpty()) + receita.Descricao = null; + if (receita.MunicipioDoador.IsNullOrEmpty()) + receita.MunicipioDoador = null; + if (receita.NomeDoador.IsNullOrEmpty()) + receita.NomeDoador = null; + if (receita.NomeDoadorRFB.IsNullOrEmpty()) + receita.NomeDoadorRFB = null; + if (receita.OrigemReceita.IsNullOrEmpty()) + receita.OrigemReceita = null; + if (receita.CpfDoador.IsNullOrEmpty()) + receita.CpfDoador = null; + if (receita.CnpjDoador.IsNullOrEmpty()) + receita.CnpjDoador = null; + + await despesaReceitaService.AddReceitaAsync(receita); + } + } +} diff --git a/OpenCand.ETL/Program.cs b/OpenCand.ETL/Program.cs index 8f54b2d..a294514 100644 --- a/OpenCand.ETL/Program.cs +++ b/OpenCand.ETL/Program.cs @@ -6,6 +6,7 @@ using OpenCand.Config; using OpenCand.ETL.Contracts; using OpenCand.ETL.Parser.ParserServices; using OpenCand.ETL.Repository; +using OpenCand.ETL.Services; using OpenCand.Parser; using OpenCand.Parser.Models; using OpenCand.Parser.Services; @@ -60,13 +61,19 @@ namespace OpenCand services.AddTransient, CandidatoParserService>(); services.AddTransient, BemCandidatoParserService>(); services.AddTransient, RedeSocialParserService>(); + services.AddTransient, DespesaParserService>(); + services.AddTransient, ReceitaParserService>(); services.AddTransient>(); services.AddTransient>(); services.AddTransient>(); + services.AddTransient>(); + services.AddTransient>(); services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); diff --git a/OpenCand.ETL/Repository/CandidatoRepository.cs b/OpenCand.ETL/Repository/CandidatoRepository.cs index 7919fc4..c1d2ef3 100644 --- a/OpenCand.ETL/Repository/CandidatoRepository.cs +++ b/OpenCand.ETL/Repository/CandidatoRepository.cs @@ -49,8 +49,8 @@ namespace OpenCand.Repository using (var connection = new NpgsqlConnection(ConnectionString)) { await connection.ExecuteAsync(@" - INSERT INTO candidato_mapping (idcandidato, cpf, nome, apelido, sqcandidato, ano, tipoeleicao, siglauf, nomeue, cargo, nrcandidato, sgpartido, resultado) - VALUES (@idcandidato, @cpf, @nome, @apelido, @sqcandidato, @ano, @tipoeleicao, @siglauf, @nomeue, @cargo, @nrcandidato, @sgpartido, @resultado) + INSERT INTO candidato_mapping (idcandidato, cpf, nome, apelido, sqcandidato, ano, turno, tipoeleicao, siglauf, nomeue, cargo, nrcandidato, sgpartido, resultado) + VALUES (@idcandidato, @cpf, @nome, @apelido, @sqcandidato, @ano, @turno, @tipoeleicao, @siglauf, @nomeue, @cargo, @nrcandidato, @sgpartido, @resultado) ON CONFLICT DO NOTHING;", new { @@ -60,6 +60,7 @@ namespace OpenCand.Repository apelido = candidatoMapping.Apelido, sqcandidato = candidatoMapping.SqCandidato, ano = candidatoMapping.Ano, + turno = candidatoMapping.Turno, tipoeleicao = candidatoMapping.TipoEleicao, siglauf = candidatoMapping.SiglaUF, nomeue = candidatoMapping.NomeUE, @@ -83,6 +84,18 @@ namespace OpenCand.Repository } } + public async Task GetIdCandidatoBySqCandidato(string sqcandidato) + { + using (var connection = new NpgsqlConnection(ConnectionString)) + { + var query = @" + SELECT idcandidato + FROM candidato_mapping + WHERE sqcandidato = @sqcandidato"; + return await connection.QueryFirstOrDefaultAsync(query, new { sqcandidato }); + } + } + public async Task GetCandidatoByNome(string nome, DateTime datanascimento) { using (var connection = new NpgsqlConnection(ConnectionString)) diff --git a/OpenCand.ETL/Repository/DespesaReceitaRepository.cs b/OpenCand.ETL/Repository/DespesaReceitaRepository.cs new file mode 100644 index 0000000..b07aa82 --- /dev/null +++ b/OpenCand.ETL/Repository/DespesaReceitaRepository.cs @@ -0,0 +1,139 @@ +using Dapper; +using Microsoft.Extensions.Configuration; +using Npgsql; +using OpenCand.Core.Models; + +namespace OpenCand.Repository +{ + public class DespesaReceitaRepository : BaseRepository + { + public DespesaReceitaRepository(IConfiguration configuration) : base(configuration) + { + } + + public async Task AddDespesaAsync(Despesa despesa) + { + using (var connection = new NpgsqlConnection(ConnectionString)) + { + await connection.ExecuteAsync(@" + INSERT INTO despesas_candidato ( + idcandidato, + ano, + turno, + sqcandidato, + sgpartido, + tipofornecedor, + cnpjfornecedor, + cpffornecedor, + nomefornecedor, + nomefornecedorrfb, + municipiofornecedor, + tipodocumento, + datadespesa, + descricao, + origemdespesa, + valor + ) VALUES ( + @idCandidato, + @ano, + @turno, + @sqCandidato, + @sgPartido, + @tipoFornecedor, + @cnpjFornecedor, + @cpfFornecedor, + @nomeFornecedor, + @nomeFornecedorRFB, + @municipioFornecedor, + @tipoDocumento, + @dataDespesa, + @descricao, + @origemdespesa, + @valor + )", new + { + idCandidato = despesa.IdCandidato, + ano = despesa.Ano, + turno = despesa.Turno, + sqCandidato = despesa.SqCandidato, + sgPartido = despesa.SgPartido, + tipoFornecedor = despesa.TipoFornecedor, + cnpjFornecedor = despesa.CnpjFornecedor, + cpfFornecedor = despesa.CpfFornecedor, + nomeFornecedor = despesa.NomeFornecedor, + nomeFornecedorRFB = despesa.NomeFornecedorRFB, + municipioFornecedor = despesa.MunicipioFornecedor, + tipoDocumento = despesa.TipoDocumento, + dataDespesa = despesa.DataDespesa, + descricao = despesa.Descricao, + origemdespesa = despesa.OrigemDespesa, + valor = despesa.Valor + }); + } + } + + public async Task AddReceitaAsync(Receita receita) + { + using (var connection = new NpgsqlConnection(ConnectionString)) + { + await connection.ExecuteAsync(@" + INSERT INTO receitas_candidato ( + idcandidato, + ano, + turno, + sqcandidato, + sgpartido, + fontereceita, + origemreceita, + naturezareceita, + especiereceita, + cnpjdoador, + cpfdoador, + nomedoador, + nomedoadorrfb, + municipiodoador, + datareceita, + descricao, + valor + ) VALUES ( + @idCandidato, + @ano, + @turno, + @sqCandidato, + @sgPartido, + @fonteReceita, + @origemReceita, + @naturezaReceita, + @especieReceita, + @cnpjDoador, + @cpfDoador, + @nomeDoador, + @nomeDoadorRFB, + @municipioDoador, + @dataReceita, + @descricao, + @valor + )", new + { + idCandidato = receita.IdCandidato, + ano = receita.Ano, + turno = receita.Turno, + sqCandidato = receita.SqCandidato, + sgPartido = receita.SgPartido, + fonteReceita = receita.FonteReceita, + origemReceita = receita.OrigemReceita, + naturezaReceita = receita.NaturezaReceita, + especieReceita = receita.EspecieReceita, + cnpjDoador = receita.CnpjDoador, + cpfDoador = receita.CpfDoador, + nomeDoador = receita.NomeDoador, + nomeDoadorRFB = receita.NomeDoadorRFB, + municipioDoador = receita.MunicipioDoador, + dataReceita = receita.DataReceita, + descricao = receita.Descricao, + valor = receita.Valor + }); + } + } + } +} diff --git a/OpenCand.ETL/Services/DespesaReceitaService.cs b/OpenCand.ETL/Services/DespesaReceitaService.cs new file mode 100644 index 0000000..34f5f27 --- /dev/null +++ b/OpenCand.ETL/Services/DespesaReceitaService.cs @@ -0,0 +1,54 @@ +using OpenCand.Core.Models; +using OpenCand.Repository; + +namespace OpenCand.ETL.Services +{ + public class DespesaReceitaService + { + private readonly DespesaReceitaRepository despesaReceitaRepository; + private readonly CandidatoRepository candidatorepository; + + public DespesaReceitaService(DespesaReceitaRepository despesaReceitaRepository, CandidatoRepository candidatoRepository) + { + this.despesaReceitaRepository = despesaReceitaRepository; + this.candidatorepository = candidatoRepository; + } + + public async Task AddDespesaAsync(Despesa despesa) + { + if (despesa == null || string.IsNullOrEmpty(despesa.SqCandidato)) + { + throw new ArgumentNullException(nameof(despesa), "Despesa cannot be null"); + } + + var idCandidato = await candidatorepository.GetIdCandidatoBySqCandidato(despesa.SqCandidato); + if (idCandidato == Guid.Empty) + { + throw new ArgumentException($"Candidato with SqCandidato {despesa.SqCandidato} not found."); + } + + despesa.IdCandidato = (Guid)idCandidato; + + await despesaReceitaRepository.AddDespesaAsync(despesa); + } + + public async Task AddReceitaAsync(Receita receita) + { + if (receita == null || string.IsNullOrEmpty(receita.SqCandidato)) + { + throw new ArgumentNullException(nameof(receita), "Receita cannot be null"); + } + + var idCandidato = await candidatorepository.GetIdCandidatoBySqCandidato(receita.SqCandidato); + if (idCandidato == Guid.Empty) + { + throw new ArgumentException($"Candidato with SqCandidato {receita.SqCandidato} not found."); + } + + receita.IdCandidato = (Guid)idCandidato; + + await despesaReceitaRepository.AddReceitaAsync(receita); + } + + } +} diff --git a/OpenCand.ETL/appsettings.json b/OpenCand.ETL/appsettings.json index 0a20f41..4626753 100644 --- a/OpenCand.ETL/appsettings.json +++ b/OpenCand.ETL/appsettings.json @@ -12,11 +12,15 @@ "CsvSettings": { "CandidatosFolder": "data/consulta_cand", "BensCandidatosFolder": "data/bem_candidato", - "RedesSociaisFolder": "data/rede_social" + "RedesSociaisFolder": "data/rede_social", + "DespesaCandidatoFolder": "data/despesas_candidato", + "ReceitaCandidatoFolder": "data/receitas_candidato" }, "ParserSettings": { "DefaultThreads": 40, - "CandidatoCSVThreads": 5 + "CandidatoCSVThreads": 5, + "DepesasCSVThreads": 50, + "ReceitasCSVThreads": 50 }, "BasePath": "sample" } diff --git a/README.md b/README.md index 16bee55..0c75456 100644 --- a/README.md +++ b/README.md @@ -8,4 +8,91 @@ OpenCand is built using: * .NET 8 - for parsing initial information from CSV files to the PostgreSQL database using Entity Framework. * .NET Core 8 - for the API * PostgreSQL - for the database -* React - for the front-end \ No newline at end of file +* React - for the front-end + +## Disponibilidade de dados (Prod) + +* ✅ = Disponível +* ❌ = Não disponível +* ⛔ = Sem dados + +| Nome do dado | Ano | Disponível? | +|-------------------|------|-------------| +| Candidatos | 2024 | ✅ | +| Bem Candidato | 2024 | ✅ | +| Despesas/Receitas | 2024 | ✅ | +| Rede Social | 2024 | ✅ | +| Fotos | 2024 | ✅ | +| - | - | - | +| Candidatos | 2022 | ✅ | +| Bem Candidato | 2022 | ✅ | +| Despesas/Receitas | 2022 | ✅ | +| Rede Social | 2022 | ✅ | +| Fotos | 2022 | ❌ | +| - | - | - | +| Candidatos | 2020 | ✅ | +| Bem Candidato | 2020 | ✅ | +| Despesas/Receitas | 2020 | ✅ | +| Rede Social | 2020 | ✅ | +| Fotos | 2020 | ❌ | +| - | - | - | +| Candidatos | 2018 | ✅ | +| Bem Candidato | 2018 | ✅ | +| Despesas/Receitas | 2018 | ❌ | +| Rede Social | 2018 | ❌ | +| Fotos | 2018 | ❌ | +| - | - | - | +| Candidatos | 2016 | ✅ | +| Bem Candidato | 2016 | ✅ | +| Despesas/Receitas | 2016 | ❌ | +| Rede Social | 2016 | ❌ | +| Fotos | 2016 | ❌ | +| - | - | - | +| Candidatos | 2014 | ✅ | +| Bem Candidato | 2014 | ✅ | +| Despesas/Receitas | 2014 | ❌ | +| Rede Social | 2014 | ❌ | +| Fotos | 2014 | ❌ | +| - | - | - | +| Candidatos | 2012 | ✅ | +| Bem Candidato | 2012 | ✅ | +| Despesas/Receitas | 2012 | ❌ | +| Rede Social | 2012 | ❌ | +| Fotos | 2012 | ❌ | +| - | - | - | +| Candidatos | 2010 | ✅ | +| Bem Candidato | 2010 | ✅ | +| Despesas/Receitas | 2010 | ❌ | +| Rede Social | 2010 | ❌ | +| Fotos | 2010 | ❌ | +| - | - | - | +| Candidatos | 2008 | ✅ | +| Bem Candidato | 2008 | ✅ | +| Despesas/Receitas | 2008 | ❌ | +| Rede Social | 2008 | ❌ | +| Fotos | 2008 | ❌ | +| - | - | - | +| Candidatos | 2006 | ✅ | +| Bem Candidato | 2006 | ✅ | +| Despesas/Receitas | 2006 | ❌ | +| Rede Social | 2006 | ❌ | +| Fotos | 2006 | ❌ | +| - | - | - | +| Candidatos | 2004 | ✅ | +| Bem Candidato | 2004 | ❌ | +| Despesas/Receitas | 2004 | ❌ | +| Rede Social | 2004 | ❌ | +| Fotos | 2004 | ❌ | +| - | - | - | +| Candidatos | 2002 | ✅ | +| Bem Candidato | 2002 | ❌ | +| Despesas/Receitas | 2002 | ❌ | +| Rede Social | 2002 | ❌ | +| Fotos | 2002 | ❌ | +| - | - | - | +| Candidatos | 2000 | ✅ | +| Bem Candidato | 2000 | ⛔ | +| Despesas/Receitas | 2000 | ⛔ | +| Rede Social | 2000 | ⛔ | +| Fotos | 2000 | ⛔ | +| - | - | - | diff --git a/db/db.sql b/db/db.sql index 7ee678a..8a08847 100644 --- a/db/db.sql +++ b/db/db.sql @@ -3,6 +3,8 @@ DROP TABLE IF EXISTS candidato_mapping CASCADE; DROP TABLE IF EXISTS rede_social CASCADE; DROP TABLE IF EXISTS candidato CASCADE; DROP TABLE IF EXISTS partido CASCADE; +DROP TABLE IF EXISTS despesas_candidato CASCADE; +DROP TABLE IF EXISTS receitas_candidato CASCADE; CREATE TABLE candidato ( idcandidato UUID NOT NULL PRIMARY KEY, @@ -25,7 +27,8 @@ CREATE TABLE candidato_mapping ( cpf VARCHAR(11), nome VARCHAR(255) NOT NULL, apelido VARCHAR(255), - sqcandidato TEXT, + sqcandidato VARCHAR(50) NOT NULL, + turno VARCHAR(2) NOT NULL, ano INT NOT NULL, tipoeleicao VARCHAR(50), siglauf VARCHAR(2), @@ -76,4 +79,58 @@ CREATE TABLE partido ( numero INT NOT NULL ); CREATE INDEX idx_partido_nome ON partido (nome); -CREATE INDEX idx_partido_numero ON partido (numero); \ No newline at end of file +CREATE INDEX idx_partido_numero ON partido (numero); + +---- Tables for storing despesas e receitas of candidacies +CREATE TABLE despesas_candidato ( + idreceita UUID NOT NULL DEFAULT gen_random_uuid(), + idcandidato UUID NOT NULL, + ano INT NOT NULL, + turno VARCHAR(2) NOT NULL, + sqcandidato VARCHAR(50) NOT NULL, + sgpartido VARCHAR(50) NOT NULL, + tipofornecedor VARCHAR(150), + cnpjfornecedor VARCHAR(14), + cpffornecedor VARCHAR(11), + nomefornecedor VARCHAR(255), + nomefornecedorrfb VARCHAR(255), + municipiofornecedor VARCHAR(100), + tipodocumento VARCHAR(50), + datadespesa TIMESTAMPTZ, + descricao TEXT, + origemdespesa TEXT, + valor NUMERIC(20, 2), + CONSTRAINT pk_despesas_candidato PRIMARY KEY (idreceita), + CONSTRAINT fk_despesas_candidato_candidato FOREIGN KEY (idcandidato) REFERENCES candidato(idcandidato) ON DELETE CASCADE ON UPDATE CASCADE +); +CREATE INDEX idx_despesas_candidato_idcandidato ON despesas_candidato (idcandidato); +CREATE INDEX idx_despesas_candidato_ano ON despesas_candidato (ano); +CREATE INDEX idx_despesas_candidato_sqcandidato ON despesas_candidato (sqcandidato); +CREATE INDEX idx_despesas_candidato_sgpartido ON despesas_candidato (sgpartido); + +CREATE TABLE receitas_candidato ( + idreceita UUID NOT NULL DEFAULT gen_random_uuid(), + idcandidato UUID NOT NULL, + ano INT NOT NULL, + turno VARCHAR(2) NOT NULL, + sqcandidato VARCHAR(50) NOT NULL, + sgpartido VARCHAR(50) NOT NULL, + fontereceita VARCHAR(150), + origemreceita VARCHAR(250), + naturezareceita VARCHAR(250), + especiereceita VARCHAR(250), + cnpjdoador VARCHAR(14), + cpfdoador VARCHAR(11), + nomedoador VARCHAR(255), + nomedoadorrfb VARCHAR(255), + municipiodoador VARCHAR(100), + datareceita TIMESTAMPTZ, + descricao TEXT, + valor NUMERIC(20, 2), + CONSTRAINT pk_receitas_candidato PRIMARY KEY (idreceita), + CONSTRAINT fk_receitas_candidato_candidato FOREIGN KEY (idcandidato) REFERENCES candidato(idcandidato) ON DELETE CASCADE ON UPDATE CASCADE +); +CREATE INDEX idx_receitas_candidato_idcandidato ON receitas_candidato (idcandidato); +CREATE INDEX idx_receitas_candidato_ano ON receitas_candidato (ano); +CREATE INDEX idx_receitas_candidato_sqcandidato ON receitas_candidato (sqcandidato); +CREATE INDEX idx_receitas_candidato_sgpartido ON receitas_candidato (sgpartido); \ No newline at end of file