From 437d6d8f428769a837bad1a6e114fd39a52faae3 Mon Sep 17 00:00:00 2001 From: zzdrojewskipaw Date: Thu, 27 Nov 2025 23:52:32 +0100 Subject: [PATCH] create invoice component --- BimAI.API/Controllers/InvoiceController.cs | 95 ++++++++++++ BimAI.API/Controllers/SyncController.cs | 11 +- BimAI.API/Program.cs | 1 + BimAI.Application/DTOModels/InvoiceDto.cs | 35 +++++ BimAI.Domain/Entities/Invoice.cs | 18 +++ BimAI.Infrastructure/Data/BimixDbContext.cs | 16 ++ BimAI.Infrastructure/Jobs/InvoiceSyncJob.cs | 31 ++++ .../Sync/InvoiceSyncService.cs | 137 ++++++++++++++++++ .../Components/InvoiceListComponent.razor | 118 +++++++++++++++ .../Components/InvoiceListComponent.razor.cs | 83 +++++++++++ .../Extensions/ServiceCollectionExtensions.cs | 6 + BimAI.UI.Shared/MainLayout.razor | 1 + BimAI.UI.Shared/Pages/InvoiceListPage.razor | 8 + BimAI.UI.Shared/Services/InvoiceService.cs | 65 +++++++++ 14 files changed, 624 insertions(+), 1 deletion(-) create mode 100644 BimAI.API/Controllers/InvoiceController.cs create mode 100644 BimAI.Application/DTOModels/InvoiceDto.cs create mode 100644 BimAI.Domain/Entities/Invoice.cs create mode 100644 BimAI.Infrastructure/Jobs/InvoiceSyncJob.cs create mode 100644 BimAI.Infrastructure/Sync/InvoiceSyncService.cs create mode 100644 BimAI.UI.Shared/Components/InvoiceListComponent.razor create mode 100644 BimAI.UI.Shared/Components/InvoiceListComponent.razor.cs create mode 100644 BimAI.UI.Shared/Pages/InvoiceListPage.razor create mode 100644 BimAI.UI.Shared/Services/InvoiceService.cs diff --git a/BimAI.API/Controllers/InvoiceController.cs b/BimAI.API/Controllers/InvoiceController.cs new file mode 100644 index 0000000..4893547 --- /dev/null +++ b/BimAI.API/Controllers/InvoiceController.cs @@ -0,0 +1,95 @@ +using BimAI.Application.DTOModels; +using BimAI.Application.DTOModels.Common; +using BimAI.Infrastructure.Data; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace BimAI.API.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class InvoiceController(BimAIDbContext context) : ControllerBase +{ + private readonly BimAIDbContext _context = context; + + [HttpGet] + public async Task>> GetInvoices([FromQuery] InvoiceFilterRequest request) + { + var query = _context.Invoices.AsQueryable(); + + if (!string.IsNullOrWhiteSpace(request.Search)) + { + var searchTerm = request.Search.ToLower(); + query = query.Where(x => + x.DocumentNo.ToLower().Contains(searchTerm) || + x.ClientName.ToLower().Contains(searchTerm) || + (x.ClientNip != null && x.ClientNip.ToLower().Contains(searchTerm)) + ); + } + + if (!string.IsNullOrWhiteSpace(request.DocumentNo)) + { + query = query.Where(x => x.DocumentNo.ToLower().Contains(request.DocumentNo.ToLower())); + } + + if (!string.IsNullOrWhiteSpace(request.ClientName)) + { + query = query.Where(x => x.ClientName.ToLower().Contains(request.ClientName.ToLower())); + } + + if (!string.IsNullOrWhiteSpace(request.Type)) + { + query = query.Where(x => x.Type.ToLower().Contains(request.Type.ToLower())); + } + + if (!string.IsNullOrWhiteSpace(request.Source)) + { + query = query.Where(x => x.Source.ToLower().Contains(request.Source.ToLower())); + } + + if (request.RegisterDateFrom.HasValue) + { + query = query.Where(x => x.RegisterDate >= request.RegisterDateFrom.Value); + } + + if (request.RegisterDateTo.HasValue) + { + query = query.Where(x => x.RegisterDate <= request.RegisterDateTo.Value); + } + + var totalCount = await query.CountAsync(); + + var items = await query + .OrderByDescending(x => x.RegisterDate) + .Skip((request.Page - 1) * request.PageSize) + .Take(request.PageSize) + .Select(x => new InvoiceDto + { + Id = x.Id, + DocumentNo = x.DocumentNo, + Type = x.Type, + RegisterDate = x.RegisterDate, + SellDate = x.SellDate, + ClientName = x.ClientName, + ClientId = x.ClientId, + ClientNip = x.ClientNip, + ClientAddress = x.ClientAddress, + Currency = x.Currency, + TotalNetto = x.TotalNetto, + TotalBrutto = x.TotalBrutto, + TotalVat = x.TotalVat, + Source = x.Source, + CreatedAt = x.CreatedAt, + UpdatedAt = x.UpdatedAt + }) + .ToListAsync(); + + return Ok(new PagedResult + { + Items = items, + TotalCount = totalCount, + Page = request.Page, + PageSize = request.PageSize, + }); + } +} diff --git a/BimAI.API/Controllers/SyncController.cs b/BimAI.API/Controllers/SyncController.cs index 0c28598..f027a6a 100644 --- a/BimAI.API/Controllers/SyncController.cs +++ b/BimAI.API/Controllers/SyncController.cs @@ -5,7 +5,9 @@ namespace BimAI.API.Controllers; [ApiController] [Route("api/[controller]")] -public class SyncController(ProductSyncService productSyncService) : ControllerBase +public class SyncController( + ProductSyncService productSyncService, + InvoiceSyncService invoiceSyncService) : ControllerBase { [HttpPost("run-product-sync")] public async Task RunProductSync() @@ -13,4 +15,11 @@ public class SyncController(ProductSyncService productSyncService) : ControllerB await productSyncService.RunAsync(); return Ok(); } + + [HttpPost("run-invoice-sync")] + public async Task RunInvoiceSync() + { + await invoiceSyncService.RunAsync(); + return Ok(); + } } \ No newline at end of file diff --git a/BimAI.API/Program.cs b/BimAI.API/Program.cs index 5afbfe5..c9ffe1d 100644 --- a/BimAI.API/Program.cs +++ b/BimAI.API/Program.cs @@ -15,6 +15,7 @@ var builder = WebApplication.CreateBuilder(args); var connectionString = builder.Configuration.GetConnectionString("DefaultConnection"); builder.Services.AddDbContext(options => options.UseSqlServer(connectionString)); builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddHttpClient(); builder.Services.AddControllers(); diff --git a/BimAI.Application/DTOModels/InvoiceDto.cs b/BimAI.Application/DTOModels/InvoiceDto.cs new file mode 100644 index 0000000..35a4d6d --- /dev/null +++ b/BimAI.Application/DTOModels/InvoiceDto.cs @@ -0,0 +1,35 @@ +namespace BimAI.Application.DTOModels; + + +public class InvoiceDto +{ + public Guid Id { get; set; } + public string DocumentNo { get; set; } = string.Empty; + public string Type { get; set; } = string.Empty; + public DateTime RegisterDate { get; set; } + public DateTime SellDate { get; set; } + public string ClientName { get; set; } = string.Empty; + public string? ClientId { get; set; } + public string? ClientNip { get; set; } + public string? ClientAddress { get; set; } + public string Currency { get; set; } = string.Empty; + public decimal TotalNetto { get; set; } + public decimal TotalBrutto { get; set; } + public decimal TotalVat { get; set; } + public string Source { get; set; } = string.Empty; + public DateTime CreatedAt { get; set; } + public DateTime UpdatedAt { get; set; } +} + +public class InvoiceFilterRequest +{ + public string? Search { get; set; } + public string? DocumentNo { get; set; } + public string? ClientName { get; set; } + public string? Type { get; set; } + public string? Source { get; set; } + public DateTime? RegisterDateFrom { get; set; } + public DateTime? RegisterDateTo { get; set; } + public int Page { get; set; } = 1; + public int PageSize { get; set; } = 20; +} \ No newline at end of file diff --git a/BimAI.Domain/Entities/Invoice.cs b/BimAI.Domain/Entities/Invoice.cs new file mode 100644 index 0000000..6483fbc --- /dev/null +++ b/BimAI.Domain/Entities/Invoice.cs @@ -0,0 +1,18 @@ +namespace BimAI.Domain.Entities; + +public class Invoice: BaseEntity +{ + public required string DocumentNo { get; set; } + public required string Type { get; set; } + public DateTime RegisterDate { get; set; } + public DateTime SellDate { get; set; } + public required string ClientName { get; set; } + public string? ClientId { get; set; } + public string? ClientNip { get; set; } + public string? ClientAddress { get; set; } + public required string Currency { get; set; } + public decimal TotalNetto { get; set; } + public decimal TotalBrutto { get; set; } + public decimal TotalVat { get; set; } + public required string Source { get; set; } +} \ No newline at end of file diff --git a/BimAI.Infrastructure/Data/BimixDbContext.cs b/BimAI.Infrastructure/Data/BimixDbContext.cs index 41954cd..3a64fc7 100644 --- a/BimAI.Infrastructure/Data/BimixDbContext.cs +++ b/BimAI.Infrastructure/Data/BimixDbContext.cs @@ -6,6 +6,7 @@ namespace BimAI.Infrastructure.Data; public class BimAIDbContext(DbContextOptions options) : DbContext(options) { public DbSet Products { get; set; } + public DbSet Invoices { get; set; } public DbSet SyncStates { get; set; } public DbSet Users { get; set; } @@ -20,6 +21,21 @@ public class BimAIDbContext(DbContextOptions options) : DbContex modelBuilder.Entity().Property(x => x.Ean).IsRequired().HasMaxLength(50); modelBuilder.Entity().Property(x => x.StockAddresses).IsRequired().HasMaxLength(512); + // Invoice properties + modelBuilder.Entity().HasKey(x => x.Id); + modelBuilder.Entity().Property(x => x.DocumentNo).IsRequired().HasMaxLength(100); + modelBuilder.Entity().Property(x => x.Type).IsRequired().HasMaxLength(50); + modelBuilder.Entity().Property(x => x.ClientName).IsRequired().HasMaxLength(255); + modelBuilder.Entity().Property(x => x.ClientId).HasMaxLength(100); + modelBuilder.Entity().Property(x => x.ClientNip).HasMaxLength(50); + modelBuilder.Entity().Property(x => x.ClientAddress).HasMaxLength(500); + modelBuilder.Entity().Property(x => x.Currency).IsRequired().HasMaxLength(10); + modelBuilder.Entity().Property(x => x.Source).IsRequired().HasMaxLength(50); + modelBuilder.Entity().Property(x => x.TotalNetto).HasPrecision(18, 2); + modelBuilder.Entity().Property(x => x.TotalBrutto).HasPrecision(18, 2); + modelBuilder.Entity().Property(x => x.TotalVat).HasPrecision(18, 2); + modelBuilder.Entity().HasIndex(x => new { x.DocumentNo, x.Source }).IsUnique().HasDatabaseName("IX_Invoices_DocumentNo_Source"); + // SyncState properties modelBuilder.Entity().HasKey((x => x.Entity)); diff --git a/BimAI.Infrastructure/Jobs/InvoiceSyncJob.cs b/BimAI.Infrastructure/Jobs/InvoiceSyncJob.cs new file mode 100644 index 0000000..b2b1274 --- /dev/null +++ b/BimAI.Infrastructure/Jobs/InvoiceSyncJob.cs @@ -0,0 +1,31 @@ +using BimAI.Infrastructure.Sync; +using Microsoft.Extensions.Logging; + +namespace BimAI.Infrastructure.Jobs; + +public class InvoiceSyncJob +{ + private readonly InvoiceSyncService _invoiceSyncService; + private readonly ILogger _logger; + + public InvoiceSyncJob(InvoiceSyncService invoiceSyncService, ILogger logger) + { + _invoiceSyncService = invoiceSyncService; + _logger = logger; + } + + public async Task ExecuteAsync() + { + _logger.LogInformation("Starting invoice sync..."); + + try + { + await _invoiceSyncService.RunAsync(); + _logger.LogInformation("Invoice sync finished."); + } catch (Exception ex) + { + _logger.LogError(ex, "Error during invoice sync."); + throw; + } + } +} diff --git a/BimAI.Infrastructure/Sync/InvoiceSyncService.cs b/BimAI.Infrastructure/Sync/InvoiceSyncService.cs new file mode 100644 index 0000000..f44e6dc --- /dev/null +++ b/BimAI.Infrastructure/Sync/InvoiceSyncService.cs @@ -0,0 +1,137 @@ +using System.Text.Json; +using System.Web; +using BimAI.Domain.Entities; +using BimAI.Infrastructure.Data; +using Microsoft.Extensions.Configuration; + +namespace BimAI.Infrastructure.Sync; + +public class InvoiceSyncService(HttpClient httpClient, BimAIDbContext db, IConfiguration configuration) +{ + private string DecodeHtmlEntities(string text) + { + if (string.IsNullOrEmpty(text)) + return text; + + return HttpUtility.HtmlDecode(text); + } + + public async Task RunAsync() + { + var apiKey = configuration["E5_CRM:ApiKey"]; + var syncState = db.SyncStates.FirstOrDefault(x => x.Entity == "Invoice") ?? new SyncState { Entity = "Invoice", LastSynced = 0 }; + + var url = $"https://crm.twinpol.com/REST/index.php?key={apiKey}&action=bimai.export.ecommerce&since=0"; + var response = await httpClient.GetStringAsync(url); + + var jsonDoc = JsonSerializer.Deserialize(response); + if (!jsonDoc.TryGetProperty("data", out var dataElement)) + { + Console.WriteLine("[SYNC] No 'data' property in response"); + return; + } + + var invoices = dataElement.EnumerateArray(); + var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + + foreach (var invoiceJson in invoices) + { + try + { + var e5Id = invoiceJson.GetProperty("e5Id").GetString() ?? ""; + var documentNo = DecodeHtmlEntities(invoiceJson.GetProperty("documentNo").GetString() ?? ""); + var type = DecodeHtmlEntities(invoiceJson.GetProperty("type").GetString() ?? ""); + var registerDateStr = invoiceJson.GetProperty("registerDate").GetString() ?? ""; + var sellDateStr = invoiceJson.GetProperty("sellDate").GetString() ?? ""; + var clientName = DecodeHtmlEntities(invoiceJson.GetProperty("clientName").GetString() ?? ""); + var clientId = DecodeHtmlEntities(invoiceJson.GetProperty("clientId").GetString() ?? ""); + var clientNip = DecodeHtmlEntities(invoiceJson.GetProperty("clientNip").GetString() ?? ""); + var clientAddress = DecodeHtmlEntities(invoiceJson.GetProperty("clientAddress").GetString() ?? ""); + var currency = invoiceJson.GetProperty("currency").GetString() ?? "PLN"; + var totalNettoStr = invoiceJson.GetProperty("totalNetto").GetString() ?? "0"; + var totalBruttoStr = invoiceJson.GetProperty("totalBrutto").GetString() ?? "0"; + var totalVatStr = invoiceJson.GetProperty("totalVat").GetString() ?? "0"; + var source = invoiceJson.GetProperty("source").GetString() ?? ""; + + if (!DateTime.TryParse(registerDateStr, out var registerDate)) + { + Console.WriteLine($"[SYNC] Invalid registerDate for invoice {documentNo}: {registerDateStr}"); + continue; + } + + if (!DateTime.TryParse(sellDateStr, out var sellDate)) + { + Console.WriteLine($"[SYNC] Invalid sellDate for invoice {documentNo}: {sellDateStr}"); + continue; + } + + if (!decimal.TryParse(totalNettoStr, out var totalNetto)) + totalNetto = 0; + + if (!decimal.TryParse(totalBruttoStr, out var totalBrutto)) + totalBrutto = 0; + + if (!decimal.TryParse(totalVatStr, out var totalVat)) + totalVat = 0; + + var existing = db.Invoices.FirstOrDefault(x => x.DocumentNo == documentNo && x.Source == source); + + if (existing == null) + { + var invoice = new Invoice + { + DocumentNo = documentNo, + Type = type, + RegisterDate = registerDate, + SellDate = sellDate, + ClientName = clientName, + ClientId = string.IsNullOrWhiteSpace(clientId) ? null : clientId, + ClientNip = string.IsNullOrWhiteSpace(clientNip) ? null : clientNip, + ClientAddress = string.IsNullOrWhiteSpace(clientAddress) ? null : clientAddress, + Currency = currency, + TotalNetto = totalNetto, + TotalBrutto = totalBrutto, + TotalVat = totalVat, + Source = source, + CreatedAt = DateTime.UtcNow, + UpdatedAt = DateTime.UtcNow + }; + db.Invoices.Add(invoice); + Console.WriteLine($"[SYNC] Added invoice: {documentNo} from {source}"); + } + else + { + existing.Type = type; + existing.RegisterDate = registerDate; + existing.SellDate = sellDate; + existing.ClientName = clientName; + existing.ClientId = string.IsNullOrWhiteSpace(clientId) ? null : clientId; + existing.ClientNip = string.IsNullOrWhiteSpace(clientNip) ? null : clientNip; + existing.ClientAddress = string.IsNullOrWhiteSpace(clientAddress) ? null : clientAddress; + existing.Currency = currency; + existing.TotalNetto = totalNetto; + existing.TotalBrutto = totalBrutto; + existing.TotalVat = totalVat; + existing.UpdatedAt = DateTime.UtcNow; + Console.WriteLine($"[SYNC] Updated invoice: {documentNo} from {source}"); + } + } + catch (Exception ex) + { + Console.WriteLine($"[SYNC] Error processing invoice: {ex.Message}"); + } + } + + syncState.LastSynced = now; + if (db.SyncStates.FirstOrDefault(x => x.Entity == "Invoice") == null) + { + db.SyncStates.Add(syncState); + } + else + { + db.SyncStates.Update(syncState); + } + await db.SaveChangesAsync(); + Console.WriteLine("[SYNC] Invoice sync completed"); + } +} diff --git a/BimAI.UI.Shared/Components/InvoiceListComponent.razor b/BimAI.UI.Shared/Components/InvoiceListComponent.razor new file mode 100644 index 0000000..fe32673 --- /dev/null +++ b/BimAI.UI.Shared/Components/InvoiceListComponent.razor @@ -0,0 +1,118 @@ +@using MudBlazor.Internal +Lista Faktur + + + + + + + + + + + + + + + + + + + + + + + Wyczyść filtry + + + + + + + + + + + Numer dokumentu + Typ + Klient + Data rejestracji + Data sprzedaży + Wartość brutto + Akcje + + + @context.DocumentNo + @context.Type + @context.ClientName + @context.RegisterDate.ToShortDateString() + @context.SellDate.ToShortDateString() + @context.TotalBrutto.ToString("C2") + + + + + + + + Brak faktur do wyświetlenia + + + Ładowanie... + + + + @if (invoices.TotalCount > 0) + { + + + + Wyniki @((invoices.Page - 1) * invoices.PageSize + 1) - @Math.Min(invoices.Page * invoices.PageSize, invoices.TotalCount) + z @invoices.TotalCount + + + + + + + } diff --git a/BimAI.UI.Shared/Components/InvoiceListComponent.razor.cs b/BimAI.UI.Shared/Components/InvoiceListComponent.razor.cs new file mode 100644 index 0000000..072939c --- /dev/null +++ b/BimAI.UI.Shared/Components/InvoiceListComponent.razor.cs @@ -0,0 +1,83 @@ +using BimAI.UI.Shared.Services; +using Microsoft.AspNetCore.Components; +using BimAI.Application.DTOModels; +using BimAI.Application.DTOModels.Common; +using MudBlazor; + +namespace BimAI.UI.Shared.Components; + +public partial class InvoiceListComponent : ComponentBase +{ + [Inject] private InvoiceService InvoiceService { get; set; } = default!; + + private PagedResult invoices = new(); + private InvoiceFilterRequest filterRequest = new(); + private bool isLoading = false; + + protected override async Task OnInitializedAsync() + { + Console.WriteLine("========== InvoiceListComponent OnInitializedAsync =========="); + await LoadInvoices(); + } + + private async Task LoadInvoices() + { + isLoading = true; + Console.WriteLine($"========== LoadInvoices - Starting load with Page: {filterRequest.Page}, PageSize: {filterRequest.PageSize} =========="); + + try + { + invoices = await InvoiceService.GetInvoiceAsync(filterRequest); + Console.WriteLine($"========== LoadInvoices - Loaded {invoices.Items?.Count ?? 0} invoices, Total: {invoices.TotalCount} =========="); + } + catch (Exception ex) + { + Console.WriteLine($"========== ERROR Loading invoices failed: {ex.Message} =========="); + Console.WriteLine($"========== ERROR Stack trace: {ex.StackTrace} =========="); + if (ex.InnerException != null) + { + Console.WriteLine($"========== ERROR Inner exception: {ex.InnerException.Message} =========="); + } + } + finally + { + isLoading = false; + } + } + + private async Task SearchInvoices() + { + filterRequest.Page = 1; + await LoadInvoices(); + } + + private async Task OnPageChanged(int page) + { + filterRequest.Page = page; + await LoadInvoices(); + } + + private async Task ClearFilters() + { + filterRequest = new InvoiceFilterRequest(); + await LoadInvoices(); + } + + private async Task ViewInvoice(Guid invoiceId) + { + // TODO + Console.WriteLine($"Zobacz fakturę: {invoiceId}"); + } + + private async Task EditInvoice(Guid invoiceId) + { + // TODO + Console.WriteLine($"Edytuj fakturę: {invoiceId}"); + } + + private async Task DeleteInvoice(Guid invoiceId) + { + // TODO + Console.WriteLine($"Usuń fakturę: {invoiceId}"); + } +} \ No newline at end of file diff --git a/BimAI.UI.Shared/Extensions/ServiceCollectionExtensions.cs b/BimAI.UI.Shared/Extensions/ServiceCollectionExtensions.cs index 10bb813..1f697e1 100644 --- a/BimAI.UI.Shared/Extensions/ServiceCollectionExtensions.cs +++ b/BimAI.UI.Shared/Extensions/ServiceCollectionExtensions.cs @@ -11,6 +11,12 @@ public static class ServiceCollectionExtensions { client.BaseAddress = new Uri(apiBaseUrl); }); + + services.AddHttpClient(client => + { + client.BaseAddress = new Uri(apiBaseUrl); + }); + return services; } diff --git a/BimAI.UI.Shared/MainLayout.razor b/BimAI.UI.Shared/MainLayout.razor index 7adcd86..2140dd2 100644 --- a/BimAI.UI.Shared/MainLayout.razor +++ b/BimAI.UI.Shared/MainLayout.razor @@ -28,6 +28,7 @@ Dashboard Products + Invoices diff --git a/BimAI.UI.Shared/Pages/InvoiceListPage.razor b/BimAI.UI.Shared/Pages/InvoiceListPage.razor new file mode 100644 index 0000000..4e4ee66 --- /dev/null +++ b/BimAI.UI.Shared/Pages/InvoiceListPage.razor @@ -0,0 +1,8 @@ +@page "/invoices" +@using BimAI.UI.Shared.Components + +Faktury + + + + \ No newline at end of file diff --git a/BimAI.UI.Shared/Services/InvoiceService.cs b/BimAI.UI.Shared/Services/InvoiceService.cs new file mode 100644 index 0000000..615e088 --- /dev/null +++ b/BimAI.UI.Shared/Services/InvoiceService.cs @@ -0,0 +1,65 @@ +using System.Text.Json; +using BimAI.Application.DTOModels; +using BimAI.Application.DTOModels.Common; +using Microsoft.AspNetCore.WebUtilities; + +namespace BimAI.UI.Shared.Services; + +public class InvoiceService(HttpClient httpClient) +{ + private readonly HttpClient _httpClient = httpClient; + private readonly JsonSerializerOptions _jsonOptions = new() + { + PropertyNameCaseInsensitive = true + }; + + public async Task> GetInvoiceAsync(InvoiceFilterRequest request) + { + var queryParams = new Dictionary + { + ["page"] = request.Page.ToString(), + ["pageSize"] = request.PageSize.ToString(), + }; + + // if (!string.IsNullOrWhiteSpace(request.Search)) + // { + // queryParams["search"] = request.Search; + // } + // if (!string.IsNullOrWhiteSpace(request.Name)) + // { + // queryParams["name"] = request.Name; + // } + // if (!string.IsNullOrWhiteSpace(request.Code)) + // { + // queryParams["code"] = request.Code; + // } + + // if (!string.IsNullOrWhiteSpace(request.Ean)) + // { + // queryParams["ean"] = request.Ean; + // } + + var uri = QueryHelpers.AddQueryString("api/invoice", queryParams); + Console.WriteLine($"========== InvoiceService - Full URL: {_httpClient.BaseAddress}{uri} =========="); + + var response = await _httpClient.GetAsync(uri); + Console.WriteLine($"========== InvoiceService - Response status: {response.StatusCode} =========="); + + if (!response.IsSuccessStatusCode) + { + var errorContent = await response.Content.ReadAsStringAsync(); + Console.WriteLine($"========== InvoiceService - Error response: {errorContent} =========="); + } + + response.EnsureSuccessStatusCode(); + + var json = await response.Content.ReadAsStringAsync(); + Console.WriteLine($"========== InvoiceService - Response JSON length: {json.Length} =========="); + Console.WriteLine($"========== InvoiceService - Response JSON: {json.Substring(0, Math.Min(500, json.Length))} =========="); + + var result = JsonSerializer.Deserialize>(json, _jsonOptions); + Console.WriteLine($"========== InvoiceService - Deserialized result - Items count: {result?.Items?.Count ?? 0}, Total: {result?.TotalCount ?? 0} =========="); + + return result ?? new PagedResult(); + } +} \ No newline at end of file