Inclui suporte para armazenar dados JSON na tabela ProcessedInvoices usando o tipo jsonb do PostgreSQL. Implementa conversão entre JsonDocument e string JSON no modelo. Cria migração para adicionar/remover a coluna. Atualiza versão do EF Core para 9.0.0. Ajusta gravação de faturas para salvar o corpo JSON recebido.
384 lines
16 KiB
C#
384 lines
16 KiB
C#
using System.Data.OleDb;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using System.Threading.RateLimiting;
|
|
using Faturas;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace Webhook_4docs
|
|
{
|
|
public class Program
|
|
{
|
|
private static readonly RateLimiter connRateLimiter = new ConcurrencyLimiter(new ConcurrencyLimiterOptions
|
|
{
|
|
PermitLimit = 1
|
|
});
|
|
public static void Main(string[] args)
|
|
{
|
|
static string IndexedFilename(string stub, string extension)
|
|
{
|
|
int ix = 0;
|
|
string? filename = null;
|
|
|
|
if (File.Exists(String.Format("{0}.{1}", stub, extension)))
|
|
{
|
|
do
|
|
{
|
|
ix++;
|
|
filename = String.Format("{0} ({1}).{2}", stub, ix, extension);
|
|
} while (File.Exists(filename));
|
|
}
|
|
else
|
|
{
|
|
filename = String.Format("{0}.{1}", stub, extension);
|
|
}
|
|
return filename;
|
|
}
|
|
|
|
var builder = WebApplication.CreateBuilder(args);
|
|
builder.Configuration.AddJsonFile("appsettings.json");
|
|
|
|
var connectionString = builder.Configuration.GetConnectionString("WebhookDbContext");
|
|
string? appFolder = builder.Configuration["PathBase"];
|
|
|
|
builder.Services.AddDbContext<WebhookDbContext>(options =>
|
|
options.UseNpgsql(connectionString)
|
|
);
|
|
|
|
builder.Services.AddLogging(loggingBuilder =>
|
|
{
|
|
loggingBuilder.ClearProviders();
|
|
loggingBuilder.AddConsole();
|
|
});
|
|
|
|
// Add services to the container.
|
|
builder.Services.AddRazorPages();
|
|
|
|
var app = builder.Build();
|
|
|
|
var logger = app.Services.GetRequiredService<ILogger<Program>>();
|
|
|
|
using (var scope = app.Services.CreateScope())
|
|
{
|
|
var dbContext = scope.ServiceProvider.GetRequiredService<WebhookDbContext>();
|
|
dbContext.Database.EnsureCreated();
|
|
dbContext.Database.Migrate();
|
|
}
|
|
|
|
string fatura_arquivo;
|
|
// Configure the HTTP request pipeline.
|
|
if (!app.Environment.IsDevelopment())
|
|
{
|
|
app.UseExceptionHandler("/Error");
|
|
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
|
|
app.UseHsts();
|
|
}
|
|
|
|
app.UsePathBase(appFolder);
|
|
|
|
app.UseRouting();
|
|
|
|
app.UseStaticFiles();
|
|
|
|
app.UseAuthorization();
|
|
|
|
app.MapRazorPages();
|
|
|
|
app.Use((context, next) =>
|
|
{
|
|
context.Request.PathBase = new PathString(appFolder);
|
|
return next(context);
|
|
});
|
|
|
|
// Endpoint para \api
|
|
app.UseEndpoints(endpoints =>
|
|
{
|
|
endpoints.MapControllers();
|
|
endpoints.MapPatch("/api", async context =>
|
|
{
|
|
|
|
string requestBody;
|
|
using (var reader = new StreamReader(context.Request.Body, Encoding.UTF8, true, 1024, true))
|
|
{
|
|
requestBody = await reader.ReadToEndAsync();
|
|
}
|
|
|
|
var JsonBody = JsonDocument.Parse(requestBody).RootElement;
|
|
var JsonBodyDocument = JsonDocument.Parse(requestBody);
|
|
string CaminhoDB = "X:/Middle/Informativo Setorial/Modelo Word/BD1_dados cadastrais e faturas.accdb";
|
|
|
|
if (JsonBody.TryGetProperty("requestID", out JsonElement fatura_ID_json))
|
|
{
|
|
string fatura_ID = fatura_ID_json.ToString();
|
|
|
|
if (!JsonBody.TryGetProperty("json", out JsonElement root)) { return; }
|
|
|
|
JsonElement DadosJson = JsonDocument.Parse(root.ToString()).RootElement;
|
|
|
|
string tipoDocumento = root.Get("documentType").ToString() ?? string.Empty.ToLower();
|
|
|
|
if (tipoDocumento != string.Empty && tipoDocumento != "nota_fiscal")
|
|
{
|
|
return;
|
|
}
|
|
|
|
Fatura fatura = new(fatura_ID, JsonBody);
|
|
bool completed = false;
|
|
|
|
while (!completed)
|
|
{
|
|
var connLease = await connRateLimiter.AcquireAsync();
|
|
|
|
if (connLease.IsAcquired)
|
|
{
|
|
using (OleDbConnection conn = new(@"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + CaminhoDB + ";Jet OLEDB:Database Password=gds21"))
|
|
{
|
|
if (conn != null)
|
|
{
|
|
if (conn.State == System.Data.ConnectionState.Closed)
|
|
{
|
|
await conn.OpenAsync();
|
|
}
|
|
|
|
try
|
|
{
|
|
fatura.Processar(conn);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
completed = true;
|
|
connLease.Dispose();
|
|
throw new Exception(ex.Message);
|
|
}
|
|
}
|
|
}
|
|
completed = true;
|
|
connLease.Dispose();
|
|
}
|
|
}
|
|
|
|
string? status = fatura.Status;
|
|
|
|
int status_id = 0;
|
|
|
|
switch (status)
|
|
{
|
|
case ("FATURA DUPLICADA NO BD"):
|
|
status_id = 4;
|
|
break;
|
|
case ("UNIDADE CONSUMIDORA NÃO LOCALIZADA NO BD"):
|
|
status_id = 5;
|
|
break;
|
|
case ("FATURA INCLUIDA NO BD"):
|
|
status_id = 6;
|
|
break;
|
|
case (_):
|
|
status = "ERRO";
|
|
status_id = 7;
|
|
break;
|
|
}
|
|
|
|
try
|
|
{
|
|
string path = $@"X:\Middle\Carteira {fatura.Gestao![0]}\Carteira {fatura.Gestao}\Faturas fourdocs\{status_id} - {status}";
|
|
if (status_id == 6 && fatura.PastaTUSD!.Exists)
|
|
{
|
|
path = fatura.PastaTUSD!.FullName;
|
|
}
|
|
|
|
if (status_id != 4)
|
|
{
|
|
fatura_arquivo = IndexedFilename($@"{path}\ID {fatura_ID!} - Mês {fatura.Mes} - Empresa {fatura.Empresa} - Unidade {fatura.Unidade}", "pdf");
|
|
CriarArquivo(fatura_arquivo, JsonBody.GetProperty("pdfFile").ToString());
|
|
|
|
var DatabaseModel = new ProcessedInvoices
|
|
{
|
|
InvoiceId = Int32.Parse(fatura_ID),
|
|
DateTimeProcessed = DateTime.UtcNow,
|
|
Status = status,
|
|
InvoicePath = fatura_arquivo,
|
|
JsonBody = JsonBodyDocument
|
|
};
|
|
|
|
logger.LogInformation("Fatura incluída no BD");
|
|
|
|
logger.LogInformation("Fatura salva na pasta " + fatura_arquivo);
|
|
|
|
using (var scope = app.Services.CreateScope())
|
|
{
|
|
var dbContext = scope.ServiceProvider.GetRequiredService<WebhookDbContext>();
|
|
dbContext.ProcessedInvoices.Add(DatabaseModel);
|
|
await dbContext.SaveChangesAsync();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
logger.LogInformation("Fatura duplicada no BD");
|
|
|
|
logger.LogInformation("Fatura descartada");
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
logger.LogError("Erro no processamento da fatura");
|
|
}
|
|
}
|
|
else if (JsonBody.TryGetProperty("healthy", out _) && JsonBody.TryGetProperty("blame", out _) && JsonBody.TryGetProperty("locationID", out _))
|
|
{
|
|
double errorID = 0;
|
|
|
|
switch (JsonBody.GetProperty("healthy").GetBoolean(), JsonBody.GetProperty("blame").ToString().ToLower())
|
|
{
|
|
case (false, "user"):
|
|
logger.LogError("Loc ID: " + JsonBody.GetProperty("locationID").ToString() + " Sem acesso");
|
|
errorID = 1;
|
|
break;
|
|
case (false, "system"):
|
|
logger.LogError("Loc ID: " + JsonBody.GetProperty("locationID").ToString() + " Com erro de sistema");
|
|
errorID = 2;
|
|
break;
|
|
case (false, _):
|
|
logger.LogError("Loc ID: " + JsonBody.GetProperty("locationID").ToString() + " Com erro " + JsonBody.GetProperty("blame").ToString());
|
|
errorID = 3;
|
|
break;
|
|
case (true, _):
|
|
//logger.LogInformation("Loc ID: " + JsonBody.GetProperty("locationID").ToString() + " está saudável");
|
|
break;
|
|
}
|
|
|
|
var errorText = GetErrorText(JsonBody);
|
|
await UpdateErrorIdStatusAsync(CaminhoDB, JsonBody.GetProperty("locationID").GetInt64(), errorID, errorText, logger);
|
|
|
|
}
|
|
});
|
|
//implementação futura para receber as faturas enviadas via api de forma assíncrona - Upload4Docs (substituir parte do agendador de tarefas)
|
|
endpoints.MapPut("/api", async context =>
|
|
{
|
|
var a = 10;
|
|
a++;
|
|
await Task.CompletedTask;
|
|
});
|
|
});
|
|
app.Run();
|
|
}
|
|
//tenta pegar o deactivationReport, se não conseguir ou for null, tenta pegar o websiteText, se não conseguir ou for null, tenta pegar o systemText. As propriedades podem não exisir
|
|
public static string GetErrorText(JsonElement root)
|
|
{
|
|
string errorText;
|
|
|
|
if (root.TryGetProperty("deactivationReport", out JsonElement deactivationReportElement) && deactivationReportElement.ValueKind != JsonValueKind.Null)
|
|
{
|
|
errorText = deactivationReportElement.ToString();
|
|
}
|
|
else if (root.TryGetProperty("websiteText", out JsonElement websiteTextElement) && websiteTextElement.ValueKind != JsonValueKind.Null)
|
|
{
|
|
errorText = websiteTextElement.ToString();
|
|
}
|
|
else if (root.TryGetProperty("systemText", out JsonElement systemTextElement) && systemTextElement.ValueKind != JsonValueKind.Null)
|
|
{
|
|
errorText = systemTextElement.ToString();
|
|
}
|
|
else
|
|
{
|
|
errorText = "Erro desconhecido";
|
|
}
|
|
|
|
return errorText;
|
|
}
|
|
public static async Task<int> UpdateErrorIdStatusAsync(string CaminhoDB, double location_id, double errorID, string systemText, ILogger logger)
|
|
{
|
|
int test = 0;
|
|
int maxRetries = 3;
|
|
int attempt = 0;
|
|
|
|
while (attempt < maxRetries)
|
|
{
|
|
var connLease = await connRateLimiter.AcquireAsync();
|
|
|
|
if (connLease.IsAcquired)
|
|
{
|
|
try
|
|
{
|
|
using (OleDbConnection conn = new (@"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + CaminhoDB + ";Jet OLEDB:Database Password=gds21"))
|
|
{
|
|
await conn.OpenAsync();
|
|
|
|
using (OleDbCommand cmd = new (
|
|
@"UPDATE AgVirtual4Docs
|
|
SET errorID = @errorID, status = @status
|
|
WHERE location_id = @location_id", conn))
|
|
{
|
|
// Adiciona parâmetros de forma segura
|
|
cmd.Parameters.AddWithValue("@errorID", errorID);
|
|
cmd.Parameters.AddWithValue("@status", systemText);
|
|
cmd.Parameters.AddWithValue("@location_id", location_id);
|
|
|
|
// Executa o comando e captura o resultado
|
|
test = await cmd.ExecuteNonQueryAsync();
|
|
}
|
|
}
|
|
|
|
break; // Sai do loop em caso de sucesso
|
|
}
|
|
catch (OleDbException ex)
|
|
{
|
|
// Registra o erro
|
|
logger.LogInformation($"Erro no OleDb update: {ex.Message} (Tentativa {attempt + 1} de {maxRetries})");
|
|
|
|
if (attempt < maxRetries - 1)
|
|
{
|
|
// Aguarda antes de tentar novamente
|
|
await Task.Delay(1000 * (int)Math.Pow(2, attempt)); // Retry com Backoff Exponencial
|
|
}
|
|
else
|
|
{
|
|
// Propaga a exceção na última tentativa
|
|
throw;
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
connLease.Dispose();
|
|
}
|
|
|
|
attempt++; // Incrementa a tentativa após adquirir o lease, mesmo que falhe
|
|
}
|
|
else
|
|
{
|
|
// Aguarda um curto período antes de tentar novamente se não conseguiu adquirir o lease
|
|
await Task.Delay(200);
|
|
}
|
|
}
|
|
|
|
return test;
|
|
}
|
|
public static void CriarArquivo(string fatura_arquivo, string pdfFile64)
|
|
{
|
|
//string fatura_arquivo = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + @"\test.pdf";
|
|
if (!File.Exists(fatura_arquivo))
|
|
{
|
|
byte[] bytes = Convert.FromBase64String(pdfFile64);
|
|
|
|
System.IO.FileStream stream = new(fatura_arquivo, FileMode.CreateNew);
|
|
System.IO.BinaryWriter writer =
|
|
new(stream);
|
|
writer.Write(bytes, 0, bytes.Length);
|
|
writer.Close();
|
|
}
|
|
}
|
|
}
|
|
public static partial class JsonExtensions
|
|
{
|
|
public static JsonElement? Get(this JsonElement element, string name) =>
|
|
element.ValueKind != JsonValueKind.Null && element.ValueKind != JsonValueKind.Undefined && element.TryGetProperty(name, out var value)
|
|
? value : (JsonElement?)null;
|
|
|
|
public static JsonElement? Get(this JsonElement element, int index)
|
|
{
|
|
if (element.ValueKind == JsonValueKind.Null || element.ValueKind == JsonValueKind.Undefined)
|
|
return null;
|
|
// Throw if index < 0
|
|
return index < element.GetArrayLength() ? element[index] : null;
|
|
}
|
|
}
|
|
} |