2025-05-31 19:26:02 +02:00
|
|
|
|
using System.Globalization;
|
|
|
|
|
|
using Google.Apis.Sheets.v4;
|
|
|
|
|
|
using Google.Apis.Sheets.v4.Data;
|
|
|
|
|
|
using Microsoft.EntityFrameworkCore;
|
2025-06-02 18:53:25 +02:00
|
|
|
|
using Microsoft.Extensions.Logging;
|
2025-05-31 19:26:02 +02:00
|
|
|
|
using DiunaBI.Core.Models;
|
2025-06-06 22:15:23 +02:00
|
|
|
|
using DiunaBI.Core.Database.Context;
|
2025-05-31 19:26:02 +02:00
|
|
|
|
using DiunaBI.Core.Services;
|
|
|
|
|
|
|
|
|
|
|
|
namespace DiunaBI.Plugins.Morska.Processors;
|
|
|
|
|
|
|
2025-06-02 16:54:33 +02:00
|
|
|
|
public class T4R2Processor : MorskaBaseProcessor
|
|
|
|
|
|
{
|
2025-06-07 16:34:36 +02:00
|
|
|
|
public override string ProcessorType => "Morska.Process.T4.R2";
|
2025-06-02 16:54:33 +02:00
|
|
|
|
|
|
|
|
|
|
private readonly AppDbContext _db;
|
|
|
|
|
|
private readonly SpreadsheetsResource.ValuesResource _googleSheetValues;
|
2025-06-02 18:53:25 +02:00
|
|
|
|
private readonly ILogger<T4R2Processor> _logger;
|
|
|
|
|
|
|
2025-06-07 13:51:27 +02:00
|
|
|
|
// Configuration properties loaded from layer records
|
|
|
|
|
|
private int Year { get; set; }
|
|
|
|
|
|
private List<Record>? Sources { get; set; }
|
|
|
|
|
|
private string? LayerName { get; set; }
|
|
|
|
|
|
private string? ReportSheetName { get; set; }
|
|
|
|
|
|
private string? InvoicesSheetName { get; set; }
|
|
|
|
|
|
|
2025-06-02 16:54:33 +02:00
|
|
|
|
public T4R2Processor(
|
2025-06-02 18:53:25 +02:00
|
|
|
|
AppDbContext db,
|
|
|
|
|
|
SpreadsheetsResource.ValuesResource googleSheetValues,
|
|
|
|
|
|
ILogger<T4R2Processor> logger)
|
2025-06-02 16:54:33 +02:00
|
|
|
|
{
|
|
|
|
|
|
_db = db;
|
|
|
|
|
|
_googleSheetValues = googleSheetValues;
|
2025-06-02 18:53:25 +02:00
|
|
|
|
_logger = logger;
|
2025-06-02 16:54:33 +02:00
|
|
|
|
}
|
2025-06-02 18:53:25 +02:00
|
|
|
|
|
2025-06-02 16:54:33 +02:00
|
|
|
|
public override void Process(Layer processWorker)
|
2025-05-31 19:26:02 +02:00
|
|
|
|
{
|
2025-06-07 13:51:27 +02:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogInformation("{ProcessorType}: Starting processing for {ProcessWorkerName} ({ProcessWorkerId})",
|
|
|
|
|
|
ProcessorType, processWorker.Name, processWorker.Id);
|
|
|
|
|
|
|
|
|
|
|
|
// Load configuration from layer records
|
|
|
|
|
|
LoadConfiguration(processWorker);
|
|
|
|
|
|
|
|
|
|
|
|
// Validate required configuration
|
|
|
|
|
|
ValidateConfiguration();
|
|
|
|
|
|
|
|
|
|
|
|
// Perform the actual processing
|
|
|
|
|
|
PerformProcessing(processWorker);
|
|
|
|
|
|
|
|
|
|
|
|
_logger.LogInformation("{ProcessorType}: Successfully completed processing for {ProcessWorkerName}",
|
|
|
|
|
|
ProcessorType, processWorker.Name);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogError(e, "{ProcessorType}: Failed to process {ProcessWorkerName} ({ProcessWorkerId})",
|
|
|
|
|
|
ProcessorType, processWorker.Name, processWorker.Id);
|
|
|
|
|
|
throw;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void LoadConfiguration(Layer processWorker)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (processWorker.Records == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new InvalidOperationException("ProcessWorker has no records");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Load year
|
|
|
|
|
|
var yearStr = GetRecordValue(processWorker.Records, "Year");
|
|
|
|
|
|
if (string.IsNullOrEmpty(yearStr) || !int.TryParse(yearStr, out var year))
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new InvalidOperationException("Year record not found or invalid");
|
|
|
|
|
|
}
|
|
|
|
|
|
Year = year;
|
|
|
|
|
|
|
|
|
|
|
|
// Load sources
|
|
|
|
|
|
Sources = processWorker.Records.Where(x => x.Code == "Source").ToList();
|
|
|
|
|
|
if (Sources.Count == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new InvalidOperationException("Source records not found");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Load layer name
|
|
|
|
|
|
LayerName = GetRecordValue(processWorker.Records, "LayerName");
|
|
|
|
|
|
if (string.IsNullOrEmpty(LayerName))
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new InvalidOperationException("LayerName record not found");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Load report sheet name
|
|
|
|
|
|
ReportSheetName = GetRecordValue(processWorker.Records, "GoogleSheetName");
|
|
|
|
|
|
if (string.IsNullOrEmpty(ReportSheetName))
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new InvalidOperationException("GoogleSheetName record not found");
|
|
|
|
|
|
}
|
2025-06-02 18:53:25 +02:00
|
|
|
|
|
2025-06-07 13:51:27 +02:00
|
|
|
|
// Load invoices sheet name
|
|
|
|
|
|
InvoicesSheetName = GetRecordValue(processWorker.Records, "GoogleSheetName-Invoices");
|
|
|
|
|
|
if (string.IsNullOrEmpty(InvoicesSheetName))
|
2025-05-31 19:26:02 +02:00
|
|
|
|
{
|
2025-06-07 13:51:27 +02:00
|
|
|
|
throw new InvalidOperationException("GoogleSheetName-Invoices record not found");
|
2025-05-31 19:26:02 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-07 13:51:27 +02:00
|
|
|
|
_logger.LogDebug("{ProcessorType}: Configuration loaded - Year: {Year}, Sources: {SourceCount}, LayerName: {LayerName}",
|
|
|
|
|
|
ProcessorType, Year, Sources.Count, LayerName);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ValidateConfiguration()
|
|
|
|
|
|
{
|
|
|
|
|
|
var errors = new List<string>();
|
|
|
|
|
|
|
|
|
|
|
|
if (Year < 2000 || Year > 3000) errors.Add($"Invalid year: {Year}");
|
|
|
|
|
|
if (Sources == null || Sources.Count == 0) errors.Add("No sources configured");
|
|
|
|
|
|
if (string.IsNullOrEmpty(LayerName)) errors.Add("LayerName is required");
|
|
|
|
|
|
if (string.IsNullOrEmpty(ReportSheetName)) errors.Add("ReportSheetName is required");
|
|
|
|
|
|
if (string.IsNullOrEmpty(InvoicesSheetName)) errors.Add("InvoicesSheetName is required");
|
|
|
|
|
|
|
|
|
|
|
|
if (errors.Any())
|
2025-05-31 19:26:02 +02:00
|
|
|
|
{
|
2025-06-07 13:51:27 +02:00
|
|
|
|
throw new InvalidOperationException($"Configuration validation failed: {string.Join(", ", errors)}");
|
2025-05-31 19:26:02 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-07 13:51:27 +02:00
|
|
|
|
_logger.LogDebug("{ProcessorType}: Configuration validation passed", ProcessorType);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void PerformProcessing(Layer processWorker)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogDebug("{ProcessorType}: Processing data for Year: {Year} with {SourceCount} sources",
|
|
|
|
|
|
ProcessorType, Year, Sources!.Count);
|
|
|
|
|
|
|
|
|
|
|
|
// Get or create processed layer
|
|
|
|
|
|
var processedLayer = GetOrCreateProcessedLayer(processWorker);
|
|
|
|
|
|
|
|
|
|
|
|
// Process records for all sources
|
|
|
|
|
|
var newRecords = ProcessSources();
|
|
|
|
|
|
|
|
|
|
|
|
// Save results
|
|
|
|
|
|
SaveProcessedLayer(processedLayer, newRecords);
|
|
|
|
|
|
|
|
|
|
|
|
// Update Google Sheets reports
|
|
|
|
|
|
UpdateReport(processedLayer.Id);
|
|
|
|
|
|
|
|
|
|
|
|
_logger.LogInformation("{ProcessorType}: Successfully processed {RecordCount} records for layer {LayerName} ({LayerId})",
|
|
|
|
|
|
ProcessorType, newRecords.Count, processedLayer.Name, processedLayer.Id);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private Layer GetOrCreateProcessedLayer(Layer processWorker)
|
|
|
|
|
|
{
|
2025-06-02 16:54:33 +02:00
|
|
|
|
var processedLayer = _db.Layers
|
2025-06-07 13:51:27 +02:00
|
|
|
|
.Where(x => x.ParentId == processWorker.Id && !x.IsDeleted && !x.IsCancelled)
|
2025-05-31 19:26:02 +02:00
|
|
|
|
.OrderByDescending(x => x.CreatedAt)
|
|
|
|
|
|
.FirstOrDefault();
|
|
|
|
|
|
|
|
|
|
|
|
if (processedLayer == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
processedLayer = new Layer
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = Guid.NewGuid(),
|
|
|
|
|
|
Type = LayerType.Processed,
|
|
|
|
|
|
ParentId = processWorker.Id,
|
2025-06-07 13:51:27 +02:00
|
|
|
|
Number = _db.Layers.Count() + 1,
|
|
|
|
|
|
CreatedById = Guid.Parse("F392209E-123E-4651-A5A4-0B1D6CF9FF9D"),
|
|
|
|
|
|
ModifiedById = Guid.Parse("F392209E-123E-4651-A5A4-0B1D6CF9FF9D"),
|
|
|
|
|
|
CreatedAt = DateTime.UtcNow,
|
|
|
|
|
|
ModifiedAt = DateTime.UtcNow
|
2025-05-31 19:26:02 +02:00
|
|
|
|
};
|
2025-06-07 13:51:27 +02:00
|
|
|
|
processedLayer.Name = $"L{processedLayer.Number}-{LayerName}";
|
|
|
|
|
|
|
|
|
|
|
|
_logger.LogDebug("{ProcessorType}: Created new processed layer {LayerName}",
|
|
|
|
|
|
ProcessorType, processedLayer.Name);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2025-05-31 19:26:02 +02:00
|
|
|
|
processedLayer.ModifiedById = Guid.Parse("F392209E-123E-4651-A5A4-0B1D6CF9FF9D");
|
|
|
|
|
|
processedLayer.ModifiedAt = DateTime.UtcNow;
|
|
|
|
|
|
|
2025-06-07 13:51:27 +02:00
|
|
|
|
_logger.LogDebug("{ProcessorType}: Using existing processed layer {LayerName}",
|
|
|
|
|
|
ProcessorType, processedLayer.Name);
|
|
|
|
|
|
}
|
2025-05-31 19:26:02 +02:00
|
|
|
|
|
2025-06-07 13:51:27 +02:00
|
|
|
|
return processedLayer;
|
|
|
|
|
|
}
|
2025-05-31 19:26:02 +02:00
|
|
|
|
|
2025-06-07 13:51:27 +02:00
|
|
|
|
private List<Record> ProcessSources()
|
|
|
|
|
|
{
|
2025-05-31 19:26:02 +02:00
|
|
|
|
var newRecords = new List<Record>();
|
|
|
|
|
|
|
2025-06-07 13:51:27 +02:00
|
|
|
|
foreach (var source in Sources!)
|
2025-05-31 19:26:02 +02:00
|
|
|
|
{
|
2025-06-07 13:51:27 +02:00
|
|
|
|
_logger.LogDebug("{ProcessorType}: Processing source {Source}",
|
|
|
|
|
|
ProcessorType, source.Desc1);
|
2025-05-31 19:26:02 +02:00
|
|
|
|
|
2025-06-07 13:51:27 +02:00
|
|
|
|
var sourceCodes = GetSourceCodes(source);
|
|
|
|
|
|
var sourceRecords = ProcessSourceData(source, sourceCodes);
|
|
|
|
|
|
newRecords.AddRange(sourceRecords);
|
2025-05-31 19:26:02 +02:00
|
|
|
|
|
2025-06-07 13:51:27 +02:00
|
|
|
|
_logger.LogDebug("{ProcessorType}: Processed source {Source} - created {RecordCount} records",
|
|
|
|
|
|
ProcessorType, source.Desc1, sourceRecords.Count);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_logger.LogDebug("{ProcessorType}: Total records created: {TotalRecordCount}",
|
|
|
|
|
|
ProcessorType, newRecords.Count);
|
|
|
|
|
|
|
|
|
|
|
|
return newRecords;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private List<int> GetSourceCodes(Record source)
|
|
|
|
|
|
{
|
|
|
|
|
|
var rawSourceCodes = GetRecordValue(Sources!, $"Codes-{source.Desc1}");
|
|
|
|
|
|
if (string.IsNullOrEmpty(rawSourceCodes))
|
|
|
|
|
|
{
|
|
|
|
|
|
return new List<int>();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return ProcessHelper.ParseCodes(rawSourceCodes);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private List<Record> ProcessSourceData(Record source, List<int> sourceCodes)
|
|
|
|
|
|
{
|
|
|
|
|
|
var sourceRecords = new List<Record>();
|
|
|
|
|
|
var lastSourceCodes = new List<string>();
|
|
|
|
|
|
|
|
|
|
|
|
// Process monthly data (1-12)
|
|
|
|
|
|
for (var month = 1; month <= 12; month++)
|
|
|
|
|
|
{
|
|
|
|
|
|
var monthRecords = ProcessMonthData(source, sourceCodes, month, lastSourceCodes);
|
|
|
|
|
|
sourceRecords.AddRange(monthRecords);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Process year summary (month 13)
|
|
|
|
|
|
var yearSummaryRecords = ProcessYearSummaryData(source, sourceCodes);
|
|
|
|
|
|
sourceRecords.AddRange(yearSummaryRecords);
|
|
|
|
|
|
|
|
|
|
|
|
return sourceRecords;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private List<Record> ProcessMonthData(Record source, List<int> sourceCodes, int month, List<string> lastSourceCodes)
|
|
|
|
|
|
{
|
|
|
|
|
|
var monthRecords = new List<Record>();
|
2025-05-31 19:26:02 +02:00
|
|
|
|
|
2025-06-07 13:51:27 +02:00
|
|
|
|
if (IsDataAvailableForMonth(month))
|
|
|
|
|
|
{
|
|
|
|
|
|
var dataSource = GetMonthDataSource(source, month);
|
|
|
|
|
|
if (dataSource != null)
|
2025-05-31 19:26:02 +02:00
|
|
|
|
{
|
2025-06-07 13:51:27 +02:00
|
|
|
|
lastSourceCodes.Clear();
|
|
|
|
|
|
lastSourceCodes.AddRange(dataSource.Records!.Select(x => x.Code!));
|
|
|
|
|
|
|
|
|
|
|
|
var filteredRecords = FilterRecords(dataSource.Records!, sourceCodes);
|
|
|
|
|
|
var processedRecords = CreateMonthRecords(filteredRecords, source, month);
|
|
|
|
|
|
monthRecords.AddRange(processedRecords);
|
|
|
|
|
|
|
|
|
|
|
|
_logger.LogDebug("{ProcessorType}: Processed month {Month} for source {Source} - {RecordCount} records",
|
|
|
|
|
|
ProcessorType, month, source.Desc1, processedRecords.Count);
|
2025-05-31 19:26:02 +02:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2025-06-07 13:51:27 +02:00
|
|
|
|
_logger.LogWarning("{ProcessorType}: Data source {DataSource} not found",
|
|
|
|
|
|
ProcessorType, $"{Year}/{month:D2}-{source.Desc1}-T");
|
2025-05-31 19:26:02 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2025-06-07 13:51:27 +02:00
|
|
|
|
// Future months - create zero value records (except for FK2)
|
|
|
|
|
|
if (source.Desc1 != "FK2" && lastSourceCodes.Count > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
var futureRecords = CreateFutureMonthRecords(lastSourceCodes, sourceCodes, month);
|
|
|
|
|
|
monthRecords.AddRange(futureRecords);
|
|
|
|
|
|
|
|
|
|
|
|
_logger.LogDebug("{ProcessorType}: Created {RecordCount} zero-value records for future month {Month}",
|
|
|
|
|
|
ProcessorType, futureRecords.Count, month);
|
|
|
|
|
|
}
|
2025-05-31 19:26:02 +02:00
|
|
|
|
}
|
2025-06-02 18:53:25 +02:00
|
|
|
|
|
2025-06-07 13:51:27 +02:00
|
|
|
|
return monthRecords;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private bool IsDataAvailableForMonth(int month)
|
|
|
|
|
|
{
|
|
|
|
|
|
return (Year == DateTime.UtcNow.Year && month <= DateTime.UtcNow.Month) || Year < DateTime.UtcNow.Year;
|
|
|
|
|
|
}
|
2025-05-31 19:26:02 +02:00
|
|
|
|
|
2025-06-07 13:51:27 +02:00
|
|
|
|
private Layer? GetMonthDataSource(Record source, int month)
|
|
|
|
|
|
{
|
|
|
|
|
|
return _db.Layers
|
|
|
|
|
|
.Where(x => x.Type == LayerType.Processed &&
|
|
|
|
|
|
!x.IsDeleted && !x.IsCancelled &&
|
|
|
|
|
|
x.Name != null && x.Name.Contains($"{Year}/{month:D2}-{source.Desc1}-T"))
|
|
|
|
|
|
.Include(x => x.Records)
|
|
|
|
|
|
.AsNoTracking()
|
|
|
|
|
|
.FirstOrDefault();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private List<Record> FilterRecords(IEnumerable<Record> records, List<int> sourceCodes)
|
|
|
|
|
|
{
|
|
|
|
|
|
return records
|
|
|
|
|
|
.Where(x => sourceCodes.Count <= 0 || sourceCodes.Contains(int.Parse(x.Code!)))
|
|
|
|
|
|
.ToList();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private List<Record> CreateMonthRecords(List<Record> filteredRecords, Record source, int month)
|
|
|
|
|
|
{
|
|
|
|
|
|
return filteredRecords.Select(x => new Record
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = Guid.NewGuid(),
|
|
|
|
|
|
Code = $"{x.Code}{month:D2}",
|
|
|
|
|
|
CreatedAt = DateTime.UtcNow,
|
|
|
|
|
|
ModifiedAt = DateTime.UtcNow,
|
|
|
|
|
|
Value1 = source.Desc1 != "FK2" ? x.Value32 : x.Value1,
|
|
|
|
|
|
Desc1 = x.Desc1
|
|
|
|
|
|
}).ToList();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private List<Record> CreateFutureMonthRecords(List<string> lastSourceCodes, List<int> sourceCodes, int month)
|
|
|
|
|
|
{
|
|
|
|
|
|
return lastSourceCodes
|
|
|
|
|
|
.Where(x => sourceCodes.Contains(int.Parse(x)))
|
|
|
|
|
|
.Select(x => new Record
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = Guid.NewGuid(),
|
|
|
|
|
|
Code = $"{x}{month:D2}",
|
|
|
|
|
|
CreatedAt = DateTime.UtcNow,
|
|
|
|
|
|
ModifiedAt = DateTime.UtcNow,
|
|
|
|
|
|
Value1 = 0
|
|
|
|
|
|
}).ToList();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private List<Record> ProcessYearSummaryData(Record source, List<int> sourceCodes)
|
|
|
|
|
|
{
|
|
|
|
|
|
var dataSourceSum = _db.Layers
|
|
|
|
|
|
.Where(x => x.Type == LayerType.Processed &&
|
|
|
|
|
|
!x.IsDeleted && !x.IsCancelled &&
|
|
|
|
|
|
x.Name != null && x.Name.Contains($"{Year}/13-{source.Desc1}-T"))
|
|
|
|
|
|
.Include(x => x.Records)
|
|
|
|
|
|
.AsNoTracking()
|
|
|
|
|
|
.FirstOrDefault();
|
|
|
|
|
|
|
|
|
|
|
|
if (dataSourceSum == null)
|
2025-05-31 19:26:02 +02:00
|
|
|
|
{
|
2025-06-07 13:51:27 +02:00
|
|
|
|
_logger.LogWarning("{ProcessorType}: Year summary data source {DataSource} not found",
|
|
|
|
|
|
ProcessorType, $"{Year}/13-{source.Desc1}-T3");
|
|
|
|
|
|
return new List<Record>();
|
2025-05-31 19:26:02 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-07 13:51:27 +02:00
|
|
|
|
var filteredRecords = FilterRecords(dataSourceSum.Records!, sourceCodes);
|
|
|
|
|
|
var yearSummaryRecords = filteredRecords.Select(x => new Record
|
2025-05-31 19:26:02 +02:00
|
|
|
|
{
|
2025-06-07 13:51:27 +02:00
|
|
|
|
Id = Guid.NewGuid(),
|
|
|
|
|
|
Code = $"{x.Code}13",
|
|
|
|
|
|
CreatedAt = DateTime.UtcNow,
|
|
|
|
|
|
ModifiedAt = DateTime.UtcNow,
|
|
|
|
|
|
Value1 = x.Value32
|
|
|
|
|
|
}).ToList();
|
|
|
|
|
|
|
|
|
|
|
|
_logger.LogDebug("{ProcessorType}: Created {RecordCount} year summary records for source {Source}",
|
|
|
|
|
|
ProcessorType, yearSummaryRecords.Count, source.Desc1);
|
|
|
|
|
|
|
|
|
|
|
|
return yearSummaryRecords;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void SaveProcessedLayer(Layer processedLayer, List<Record> newRecords)
|
|
|
|
|
|
{
|
|
|
|
|
|
var existsInDb = _db.Layers.Any(x => x.Id == processedLayer.Id);
|
|
|
|
|
|
|
|
|
|
|
|
if (!existsInDb)
|
|
|
|
|
|
{
|
|
|
|
|
|
_db.Layers.Add(processedLayer);
|
|
|
|
|
|
_logger.LogDebug("{ProcessorType}: Added new processed layer to database", ProcessorType);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
_db.Layers.Update(processedLayer);
|
|
|
|
|
|
_logger.LogDebug("{ProcessorType}: Updated existing processed layer in database", ProcessorType);
|
2025-05-31 19:26:02 +02:00
|
|
|
|
}
|
2025-06-02 18:53:25 +02:00
|
|
|
|
|
2025-06-07 13:51:27 +02:00
|
|
|
|
SaveRecords(processedLayer.Id, newRecords);
|
|
|
|
|
|
_db.SaveChanges();
|
2025-06-02 18:53:25 +02:00
|
|
|
|
|
2025-06-07 13:51:27 +02:00
|
|
|
|
_logger.LogDebug("{ProcessorType}: Saved {RecordCount} records for layer {LayerId}",
|
|
|
|
|
|
ProcessorType, newRecords.Count, processedLayer.Id);
|
2025-06-02 18:53:25 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void SaveRecords(Guid layerId, ICollection<Record> records)
|
|
|
|
|
|
{
|
2025-06-07 13:51:27 +02:00
|
|
|
|
// Remove existing records for this layer
|
2025-06-02 18:53:25 +02:00
|
|
|
|
var toDelete = _db.Records.Where(x => x.LayerId == layerId).ToList();
|
|
|
|
|
|
if (toDelete.Count > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
_db.Records.RemoveRange(toDelete);
|
2025-06-07 13:51:27 +02:00
|
|
|
|
_logger.LogDebug("{ProcessorType}: Removed {DeletedCount} existing records for layer {LayerId}",
|
|
|
|
|
|
ProcessorType, toDelete.Count, layerId);
|
2025-06-02 18:53:25 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-07 13:51:27 +02:00
|
|
|
|
// Add new records
|
2025-06-02 18:53:25 +02:00
|
|
|
|
foreach (var record in records)
|
|
|
|
|
|
{
|
|
|
|
|
|
record.CreatedById = Guid.Parse("F392209E-123E-4651-A5A4-0B1D6CF9FF9D");
|
|
|
|
|
|
record.CreatedAt = DateTime.UtcNow;
|
|
|
|
|
|
record.ModifiedById = Guid.Parse("F392209E-123E-4651-A5A4-0B1D6CF9FF9D");
|
|
|
|
|
|
record.ModifiedAt = DateTime.UtcNow;
|
|
|
|
|
|
record.LayerId = layerId;
|
|
|
|
|
|
_db.Records.Add(record);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-07 13:51:27 +02:00
|
|
|
|
_logger.LogDebug("{ProcessorType}: Added {RecordCount} new records for layer {LayerId}",
|
|
|
|
|
|
ProcessorType, records.Count, layerId);
|
2025-05-31 19:26:02 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-07 13:51:27 +02:00
|
|
|
|
private void UpdateReport(Guid sourceId)
|
2025-05-31 19:26:02 +02:00
|
|
|
|
{
|
2025-06-07 13:51:27 +02:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogDebug("{ProcessorType}: Starting Google Sheets report update for layer {LayerId}",
|
|
|
|
|
|
ProcessorType, sourceId);
|
|
|
|
|
|
|
|
|
|
|
|
const string sheetId = "1FsUmk_YRIeeGzFCX9tuUJCaLyRtjutX2ZGAEU1DMfJQ";
|
2025-05-31 19:26:02 +02:00
|
|
|
|
|
2025-06-07 13:51:27 +02:00
|
|
|
|
var processedLayer = GetProcessedLayerData(sourceId);
|
|
|
|
|
|
if (processedLayer == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new InvalidOperationException($"Processed layer {sourceId} not found");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var codesRow = GetCodesFromSheet(sheetId);
|
|
|
|
|
|
|
|
|
|
|
|
UpdateMonthlyData(sheetId, processedLayer, codesRow);
|
|
|
|
|
|
UpdateYearSummary(sheetId, processedLayer, codesRow);
|
|
|
|
|
|
UpdateTimestamps(sheetId, processedLayer);
|
|
|
|
|
|
UpdateInvoicesData(sheetId, processedLayer);
|
|
|
|
|
|
|
|
|
|
|
|
_logger.LogInformation("{ProcessorType}: Successfully updated Google Sheets reports",
|
|
|
|
|
|
ProcessorType);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogError(e, "{ProcessorType}: Failed to update Google Sheets report for layer {LayerId}",
|
|
|
|
|
|
ProcessorType, sourceId);
|
|
|
|
|
|
throw;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private Layer? GetProcessedLayerData(Guid sourceId)
|
|
|
|
|
|
{
|
|
|
|
|
|
return _db.Layers
|
2025-05-31 19:26:02 +02:00
|
|
|
|
.Where(x => x.Id == sourceId && !x.IsDeleted && !x.IsCancelled)
|
|
|
|
|
|
.Include(x => x.Records)
|
|
|
|
|
|
.AsNoTracking()
|
|
|
|
|
|
.FirstOrDefault();
|
2025-06-07 13:51:27 +02:00
|
|
|
|
}
|
2025-05-31 19:26:02 +02:00
|
|
|
|
|
2025-06-07 13:51:27 +02:00
|
|
|
|
private IList<object> GetCodesFromSheet(string sheetId)
|
|
|
|
|
|
{
|
|
|
|
|
|
var request = _googleSheetValues.Get(sheetId, "C4:Z4");
|
|
|
|
|
|
var response = request.Execute();
|
|
|
|
|
|
return response.Values[0];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void UpdateMonthlyData(string sheetId, Layer processedLayer, IList<object> codesRow)
|
|
|
|
|
|
{
|
2025-05-31 19:26:02 +02:00
|
|
|
|
const int startRow = 6;
|
|
|
|
|
|
|
2025-06-07 13:51:27 +02:00
|
|
|
|
for (var month = 1; month <= 12; month++)
|
2025-05-31 19:26:02 +02:00
|
|
|
|
{
|
|
|
|
|
|
var values = new List<object>();
|
2025-06-07 13:51:27 +02:00
|
|
|
|
var monthStr = month < 10 ? $"0{month}" : month.ToString();
|
|
|
|
|
|
|
2025-05-31 19:26:02 +02:00
|
|
|
|
foreach (string code in codesRow)
|
|
|
|
|
|
{
|
2025-06-07 13:51:27 +02:00
|
|
|
|
var record = processedLayer.Records?.SingleOrDefault(x => x.Code == $"{code}{monthStr}");
|
|
|
|
|
|
values.Add(record?.Value1?.ToString() ?? "0");
|
2025-05-31 19:26:02 +02:00
|
|
|
|
}
|
2025-06-07 13:51:27 +02:00
|
|
|
|
|
2025-05-31 19:26:02 +02:00
|
|
|
|
var valueRange = new ValueRange
|
|
|
|
|
|
{
|
|
|
|
|
|
Values = new List<IList<object>> { values }
|
|
|
|
|
|
};
|
2025-06-07 13:51:27 +02:00
|
|
|
|
|
|
|
|
|
|
var row = (startRow + month).ToString();
|
|
|
|
|
|
var update = _googleSheetValues.Update(valueRange, sheetId, $"{ReportSheetName}!C{row}:XZ{row}");
|
2025-05-31 19:26:02 +02:00
|
|
|
|
update.ValueInputOption = SpreadsheetsResource.ValuesResource.UpdateRequest.ValueInputOptionEnum.USERENTERED;
|
|
|
|
|
|
update.Execute();
|
2025-06-07 13:51:27 +02:00
|
|
|
|
|
|
|
|
|
|
_logger.LogDebug("{ProcessorType}: Updated month {Month} data in Google Sheet",
|
|
|
|
|
|
ProcessorType, month);
|
2025-05-31 19:26:02 +02:00
|
|
|
|
}
|
2025-06-07 13:51:27 +02:00
|
|
|
|
}
|
2025-05-31 19:26:02 +02:00
|
|
|
|
|
2025-06-07 13:51:27 +02:00
|
|
|
|
private void UpdateYearSummary(string sheetId, Layer processedLayer, IList<object> codesRow)
|
|
|
|
|
|
{
|
|
|
|
|
|
const int startRow = 6;
|
2025-05-31 19:26:02 +02:00
|
|
|
|
var valuesSum = new List<object>();
|
|
|
|
|
|
var emptyRow = new List<object>();
|
2025-06-07 13:51:27 +02:00
|
|
|
|
|
2025-05-31 19:26:02 +02:00
|
|
|
|
foreach (string code in codesRow)
|
|
|
|
|
|
{
|
2025-06-07 13:51:27 +02:00
|
|
|
|
var record = processedLayer.Records?.SingleOrDefault(x => x.Code == $"{code}13");
|
2025-05-31 19:26:02 +02:00
|
|
|
|
emptyRow.Add("");
|
2025-06-07 13:51:27 +02:00
|
|
|
|
valuesSum.Add(record?.Value1?.ToString() ?? "0");
|
2025-05-31 19:26:02 +02:00
|
|
|
|
}
|
2025-06-07 13:51:27 +02:00
|
|
|
|
|
|
|
|
|
|
// Insert empty row before sum
|
|
|
|
|
|
var rowEmpty = (startRow + 13).ToString();
|
2025-05-31 19:26:02 +02:00
|
|
|
|
var valueRangeEmpty = new ValueRange
|
|
|
|
|
|
{
|
|
|
|
|
|
Values = new List<IList<object>> { emptyRow }
|
|
|
|
|
|
};
|
2025-06-07 13:51:27 +02:00
|
|
|
|
var updateEmpty = _googleSheetValues.Update(valueRangeEmpty, sheetId, $"{ReportSheetName}!C{rowEmpty}:XZ{rowEmpty}");
|
2025-05-31 19:26:02 +02:00
|
|
|
|
updateEmpty.ValueInputOption = SpreadsheetsResource.ValuesResource.UpdateRequest.ValueInputOptionEnum.USERENTERED;
|
|
|
|
|
|
updateEmpty.Execute();
|
|
|
|
|
|
|
2025-06-07 13:51:27 +02:00
|
|
|
|
// Update sum row
|
|
|
|
|
|
var rowSum = (startRow + 14).ToString();
|
2025-05-31 19:26:02 +02:00
|
|
|
|
var valueRangeSum = new ValueRange
|
|
|
|
|
|
{
|
|
|
|
|
|
Values = new List<IList<object>> { valuesSum }
|
|
|
|
|
|
};
|
2025-06-07 13:51:27 +02:00
|
|
|
|
var updateSum = _googleSheetValues.Update(valueRangeSum, sheetId, $"{ReportSheetName}!C{rowSum}:XZ{rowSum}");
|
2025-05-31 19:26:02 +02:00
|
|
|
|
updateSum.ValueInputOption = SpreadsheetsResource.ValuesResource.UpdateRequest.ValueInputOptionEnum.USERENTERED;
|
|
|
|
|
|
updateSum.Execute();
|
|
|
|
|
|
|
2025-06-07 13:51:27 +02:00
|
|
|
|
_logger.LogDebug("{ProcessorType}: Updated year summary data in Google Sheet", ProcessorType);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void UpdateTimestamps(string sheetId, Layer processedLayer)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Update UTC time
|
|
|
|
|
|
var timeUtc = processedLayer.ModifiedAt.ToString("dd.MM.yyyy HH:mm:ss", CultureInfo.GetCultureInfo("pl-PL"));
|
2025-05-31 19:26:02 +02:00
|
|
|
|
var valueRangeUtcTime = new ValueRange
|
|
|
|
|
|
{
|
2025-06-07 13:51:27 +02:00
|
|
|
|
Values = new List<IList<object>> { new List<object> { timeUtc } }
|
2025-05-31 19:26:02 +02:00
|
|
|
|
};
|
2025-06-07 13:51:27 +02:00
|
|
|
|
var updateTimeUtc = _googleSheetValues.Update(valueRangeUtcTime, sheetId, $"{ReportSheetName}!G1");
|
2025-05-31 19:26:02 +02:00
|
|
|
|
updateTimeUtc.ValueInputOption = SpreadsheetsResource.ValuesResource.UpdateRequest.ValueInputOptionEnum.USERENTERED;
|
|
|
|
|
|
updateTimeUtc.Execute();
|
|
|
|
|
|
|
2025-06-07 13:51:27 +02:00
|
|
|
|
// Update Warsaw time
|
2025-05-31 19:26:02 +02:00
|
|
|
|
var warsawTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Central European Standard Time");
|
2025-06-07 13:51:27 +02:00
|
|
|
|
var warsawTime = TimeZoneInfo.ConvertTimeFromUtc(processedLayer.ModifiedAt.ToUniversalTime(), warsawTimeZone);
|
|
|
|
|
|
var timeWarsaw = warsawTime.ToString("dd.MM.yyyy HH:mm:ss", CultureInfo.GetCultureInfo("pl-PL"));
|
2025-05-31 19:26:02 +02:00
|
|
|
|
var valueRangeWarsawTime = new ValueRange
|
|
|
|
|
|
{
|
2025-06-07 13:51:27 +02:00
|
|
|
|
Values = new List<IList<object>> { new List<object> { timeWarsaw } }
|
2025-05-31 19:26:02 +02:00
|
|
|
|
};
|
2025-06-07 13:51:27 +02:00
|
|
|
|
var updateTimeWarsaw = _googleSheetValues.Update(valueRangeWarsawTime, sheetId, $"{ReportSheetName}!G2");
|
2025-05-31 19:26:02 +02:00
|
|
|
|
updateTimeWarsaw.ValueInputOption = SpreadsheetsResource.ValuesResource.UpdateRequest.ValueInputOptionEnum.USERENTERED;
|
|
|
|
|
|
updateTimeWarsaw.Execute();
|
|
|
|
|
|
|
2025-06-07 13:51:27 +02:00
|
|
|
|
_logger.LogDebug("{ProcessorType}: Updated timestamps in Google Sheet - UTC: {TimeUtc}, Warsaw: {TimeWarsaw}",
|
|
|
|
|
|
ProcessorType, timeUtc, timeWarsaw);
|
|
|
|
|
|
}
|
2025-05-31 19:26:02 +02:00
|
|
|
|
|
2025-06-07 13:51:27 +02:00
|
|
|
|
private void UpdateInvoicesData(string sheetId, Layer processedLayer)
|
|
|
|
|
|
{
|
|
|
|
|
|
var invoices = processedLayer.Records!
|
|
|
|
|
|
.Where(x => x.Code!.Length == 12)
|
|
|
|
|
|
.OrderByDescending(x => x.Code)
|
|
|
|
|
|
.ToList();
|
2025-05-31 19:26:02 +02:00
|
|
|
|
|
|
|
|
|
|
var invoicesValues = new List<IList<object>>();
|
|
|
|
|
|
var cleanUpValues = new List<IList<object>>();
|
2025-06-07 13:51:27 +02:00
|
|
|
|
|
2025-05-31 19:26:02 +02:00
|
|
|
|
foreach (var invoice in invoices)
|
|
|
|
|
|
{
|
2025-06-07 13:51:27 +02:00
|
|
|
|
var invoiceDate = DateTime.ParseExact(invoice.Code!.Substring(0, 8), "yyyyMMdd", CultureInfo.InvariantCulture)
|
|
|
|
|
|
.ToString("dd.MM.yyyy", CultureInfo.GetCultureInfo("pl-PL"));
|
|
|
|
|
|
|
2025-05-31 19:26:02 +02:00
|
|
|
|
var invoiceRow = new List<object>
|
|
|
|
|
|
{
|
2025-06-07 13:51:27 +02:00
|
|
|
|
invoiceDate, "", invoice.Desc1!, invoice.Value1!
|
2025-05-31 19:26:02 +02:00
|
|
|
|
};
|
|
|
|
|
|
invoicesValues.Add(invoiceRow);
|
|
|
|
|
|
|
2025-06-07 13:51:27 +02:00
|
|
|
|
var cleanupRow = new List<object> { "", "", "", "" };
|
2025-05-31 19:26:02 +02:00
|
|
|
|
cleanUpValues.Add(cleanupRow);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-07 13:51:27 +02:00
|
|
|
|
// Clear existing data
|
2025-05-31 19:26:02 +02:00
|
|
|
|
var cleanupValueRange = new ValueRange { Values = cleanUpValues };
|
2025-06-07 13:51:27 +02:00
|
|
|
|
var cleanupInvoices = _googleSheetValues.Update(cleanupValueRange, sheetId, $"{InvoicesSheetName}!A6:E");
|
|
|
|
|
|
cleanupInvoices.ValueInputOption = SpreadsheetsResource.ValuesResource.UpdateRequest.ValueInputOptionEnum.USERENTERED;
|
2025-05-31 19:26:02 +02:00
|
|
|
|
cleanupInvoices.Execute();
|
|
|
|
|
|
|
2025-06-07 13:51:27 +02:00
|
|
|
|
// Update with new data
|
2025-05-31 19:26:02 +02:00
|
|
|
|
var invoicesValueRange = new ValueRange { Values = invoicesValues };
|
2025-06-07 13:51:27 +02:00
|
|
|
|
var updateInvoices = _googleSheetValues.Update(invoicesValueRange, sheetId, $"{InvoicesSheetName}!A6:E");
|
|
|
|
|
|
updateInvoices.ValueInputOption = SpreadsheetsResource.ValuesResource.UpdateRequest.ValueInputOptionEnum.USERENTERED;
|
2025-05-31 19:26:02 +02:00
|
|
|
|
updateInvoices.Execute();
|
|
|
|
|
|
|
2025-06-07 13:51:27 +02:00
|
|
|
|
_logger.LogDebug("{ProcessorType}: Updated {InvoiceCount} invoices in Google Sheet",
|
|
|
|
|
|
ProcessorType, invoices.Count);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private string? GetRecordValue(ICollection<Record> records, string code)
|
|
|
|
|
|
{
|
|
|
|
|
|
return records.FirstOrDefault(x => x.Code == code)?.Desc1;
|
2025-05-31 19:26:02 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|