W metodzie SendPrinterCommand (PrinterService.cs) sprawdź po printerId czy drukarka istnieje w bazie danych w tabeli FiscalPrintersPosnet. Jeśli istnieje oraz jej wartość IsActive = true to korzystaj z metody PrintRecipt z serwisu PrinterPosnetProxy. Napisz implementację takiego rozwiązania. Poniżej znajduje się obecny kod aplikacji.

/// PrinterService.cs

#nullable enable
using Abp.Application.Services.Dto;
using Abp.Auditing;
using Abp.Collections.Extensions;
using Abp.Configuration;
using Abp.Runtime.Caching;
using Abp.Timing;
using Medica.Commerce.HostCompanies;
using Medica.Commerce.Printers.ApiModel;
using Medica.Commerce.PrintersPosnet;
using Medica.Commerce.VatSettings;
using Medica.Commerce.Visits.Process.Payments;
using Medica.Configuration;
using Medica.Extensions;
using Medica.Helpers;
using Medica.Helpers.EntityHelpers;
using Medica.Telemetry;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Medica.Commerce.Printers;

public class PrinterService : IPrinterService
{
    public const string _statusCacheName = "PrinterStatusCache";
    private readonly Lazy<IPrinterJobDbAdapter> _printerJobDbAdapter;
    private readonly Lazy<IPrinterProxy> _printerProxy;
    private readonly Lazy<IPrinterPosnetProxy> _printerPosnetProxy;
    private readonly Lazy<ITelemetry> _telemetry;
    private readonly Lazy<ISettingManager> _settingManager;
    private readonly Lazy<IVatSettingsService> _vatSettingsService;
    private readonly Lazy<IHostCompanyDbAdapter> _hostCompanyDbAdapter;

    private readonly ITypedCache<string, PrinterData> _printersStatusCache;
    private readonly ITypedCache<long, IList<Printer>> _printersCache;
    private readonly ITypedCache<long, string> _userPrintersCache;

    private readonly ITimeProvider _timeProvider;

    public PrinterService(
        ICacheManager cacheManager,
        Lazy<IPrinterJobDbAdapter> printerJobDbAdapter,
        Lazy<IPrinterProxy> printerProxy,
        Lazy<IPrinterPosnetProxy> printerPosnetProxy,
        Lazy<ITelemetry> telemetry,
        Lazy<ISettingManager> settingManager,
        Lazy<IVatSettingsService> vatSettingsService,
        Lazy<IHostCompanyDbAdapter> hostCompanyDbAdapter, ITimeProvider timeProvider)
    {
        _printersCache = cacheManager.GetCache<long, IList<Printer>>("IPrintersCache");
        _userPrintersCache = cacheManager.GetCache<long, string>("IUserPrintersCache");
        _printersStatusCache = cacheManager.GetCache<string, PrinterData>(_statusCacheName);
        _printerJobDbAdapter = printerJobDbAdapter;
        _printerProxy = printerProxy;
        _printerPosnetProxy = printerPosnetProxy;
        _telemetry = telemetry;
        _settingManager = settingManager;
        _vatSettingsService = vatSettingsService;
        _hostCompanyDbAdapter = hostCompanyDbAdapter;
        _timeProvider = timeProvider;
    }

    public async Task<string?> GetUserPrinterId(long userId, long orgUnitId)
    {
        var printers = await GetPrintersAsync(PrinterQuery.Reception(orgUnitId));
        if (!printers.Any()) return null;
        if (printers.Count == 1)
        {
            var printerId = printers.First().Entity.Id;
            await _userPrintersCache.SetAsync(userId, printerId, TimeSpan.FromDays(1));
            return printerId;
        }
        if (await _userPrintersCache.GetOrDefaultAsync(userId) is not { } cachedPrinter) return null;
        if (printers.Any(v => v.Entity.Id == cachedPrinter)) return cachedPrinter;
        await _userPrintersCache.RemoveAsync(userId);
        return null;
    }

    public Task<Either> HandleFailedJob(Guid jobId, PrinterJobStatus newStatus)
    {
        return _printerJobDbAdapter.Value.GetShallowAsync(jobId).FlatMapAsync(job =>
        {
            if (job.Status is PrinterJobStatus.Error or PrinterJobStatus.NotStarted || job.IsExpired)
            {
                job.Status = newStatus;
                return UpdateJobStatusAsync(job).IgnoreValue();
            }

            return Either.UserFriendlyErrorTask(
                $"Invalid job status {job.Status} to manual set: {newStatus} ({job.Id})",
                "Nie można zmienić stanu wybranego wydruku");
        });
    }



    public async Task<Either<RetryJobResponse>> RetryJob(long userId, long orgUnitId, Guid jobId)
    {
        if (await GetUserPrinterId(userId, orgUnitId) is not { } printerId)
            return Either<RetryJobResponse>.UserFriendlyError(
                $"No printer defined for user: {userId}",
                "Użytkownik nie ma wybranej drukarki. Odśwież stronę i spróbuj ponownie");

        return await _printerJobDbAdapter.Value.GetWithSet(jobId).FlatMapAsync(job =>
        {
            if (job.Status is not (PrinterJobStatus.Error or PrinterJobStatus.NotStarted) && !job.IsExpired)
            {
                return Either<RetryJobResponse>.UserFriendlyErrorTask(
                    $"Invalid job status for retry: {job.Status} ({job.Id})",
                    "Nie można wznowić wydruku wybranego paragonu");
            }

            return GetValidatedPrinterId(printerId, orgUnitId).FlatMapAsync(async resolvedPrinterId =>
            {
                var jobByPrinter = new PrinterJob() { Id = jobId, PrinterId = job.PrinterId, PaymentMode = job.PaymentMode };
                var status = await FillJobStatus(jobByPrinter).MapAsync(v => v.Job.Status);
                if (status.Contains(PrinterJobStatus.Completed))
                {
                    job.Status = PrinterJobStatus.Completed;
                    job.CompletionTime = Clock.Now;
                    return await UpdateJobStatusAsync(job).MapAsync(_ => RetryJobResponse.AlreadyPrinted());
                }

                job.Model.Lines?.ForEach(line => line.Na = line.Na.SanitizeNameForPrinter());
                var jobData = new PrinterCreateJobData(job.Model, job.JobContext)
                {
                    OrgUnitId = orgUnitId,
                    PatientId = job.PatientId,
                    PrinterId = resolvedPrinterId,
                    SetId = job.PrinterJobSetId,
                    Source = job.PrinterJobSet.ResolvedOrThrow().Source,
                    PaymentMode = job.PaymentMode
                };
                job.Status = PrinterJobStatus.Retried;
                return await UpdateJobStatusAsync(job)
                    .FlatMapAsync(_ => CreateJobAsync(jobData).MapAsync(RetryJobResponse.FromJob));
            });
        });
    }

    public Task<Either<string?>> SetUserPrinterId(long userId, long orgUnitId, string printerId)
    {
        return GetPrintersInternalAsync(PrinterQuery.Reception(orgUnitId)).FlatMapAsync(async printers =>
        {
            if (printers.FirstOrDefault(v => v.Id == printerId) is not { } printer)
                return Either<string?>.UserFriendlyError($"Cannot set printer {printerId} for user {userId} at org unit: {orgUnitId}", "Drukarka nie jest skonfigurowana w wbranym ośrodku");

            await _userPrintersCache.SetAsync(userId, printer.Id, TimeSpan.FromDays(1));
            return Either<string?>.Success(printer.Id);
        });
    }

    [DisableAuditing]
    public Task<IList<PrinterData>> GetPrintersAsync(PrinterQuery query, bool usePosnetService = false)
    {
        return GetPrintersInternalAsync(query, usePosnetService).MatchAsync(
            async successList => await ResolveStatus(successList),
            er =>
            {
                _telemetry.Value.LogException(er.ToException());
                return Task.FromResult(new List<PrinterData>().ToIList());
            });
    }

    private async Task<IList<PrinterData>> ResolveStatus(IList<Printer> items)
    {
        var ids = items.Select(v => v.Id).ToArray();
        var values = await _printersStatusCache.TryGetValuesAsync(ids);
        var map = values.Where(v => v.HasValue).Select(v => v.Value).ToDictionary(v => v.Id, v => v);

        return items.Select(v => map.TryGetValue(v.Id, out var val)
            ? val ?? PrinterData.Create(v)
            : PrinterData.Create(v)
        ).ToIList();
    }


    public Task<Either<PrinterData>> GetPrinter(string id, bool usePosnetService = false)
    {
        return GetPrintersInternalAsync(PrinterQuery.Empty(), usePosnetService).MapAsync(list => list.FirstOrDefault(v => v.Id == id)).FlatMapAsync(printerOpt => printerOpt is { } printer
            ? AssemblePrinterData(id, printer, usePosnetService)
            : Either<PrinterData>.InternalErrorTask("No printer with given id: {id}"));
    }

    public Task<Either<PrinterId>> GetValidatedPrinterId(string printerId, long orgUnitId)
    {
        return GetPrinter(printerId).FlatMapAsync(status =>
        {
            // Kiosk additional validation
            if (status.Entity.IsKiosk)
            {
                if (status.IsLowPaper)
                    return Either<PrinterId>.UserFriendlyError("Kiosk: Low paper", "Brak papieru");
            }

            if (status.DeviceStatus != PrinterDeviceState.Ready)
                return Either<PrinterId>.UserFriendlyError($"Printer {printerId} - Invalid printer status: {Enum.GetName(typeof(PrinterDeviceState), status.DeviceStatus)}",
                    "Drukarka fiskalna nie jest w trybie gotowości");

            if (!status.IsJobQueueEmpty)
                return Either<PrinterId>.UserFriendlyError($"Printer {printerId} - job queue is not empty", "Drukarka fiskalna obecnie wykonuje inną operację");

            if (!status.AreVatLevelsOk)
                return Either<PrinterId>.UserFriendlyError($"Printer {printerId} - Invalid VAT levels configuration", "Drukarka fiskalna ma niepoprawnie skonfigurowane stawki VAT");

            if (status.Entity.OrgUnitId != orgUnitId)
                return Either<PrinterId>.UserFriendlyError($"Printer {printerId}, OrgUnit: {status.Entity.OrgUnitId}), ContextOrgUnit: {orgUnitId} - OrgUnitId mismatch",
                    "Drukarka fiskalna jest przypisana do innego oddziału");

            if (status.DeviceMechanismStatus is not PrinterDeviceMechanismState.Ok)
                return Either<PrinterId>.UserFriendlyError(
                    $"Printer {printerId} - Invalid mechanism status: {Enum.GetName(typeof(PrinterDeviceMechanismState), status.DeviceMechanismStatus)}",
                    "Drukarka fiskalna - nieprawidłowy status mechanizmu papieru");

            return new PrinterId(printerId, status.Entity.OrgUnitId, status.VatLevels)
            {
                Serial = status.SerialNumber
            }.AsSuccess();
        });
    }

    private async Task<Either<PrinterData>> AssemblePrinterData(string id, Printer printer, bool usePosnetService = false)
    {
        IPrinterProxyBase proxy = usePosnetService ? _printerPosnetProxy.Value : _printerProxy.Value;
        return await Either.CombineAsync(
            () => _vatSettingsService.Value.GetAllAsync(),
            () => proxy.GetPrinterStatus(id),
            (vatLevels, printerStatus) => PrinterData.Create(printer, printerStatus, vatLevels)
        );
    }

    public async Task<Either<PrinterData>> RestartProxy(string id)
    {
        var now = _timeProvider.Now();
        var cacheItem = await _printersStatusCache.TryGetValueAsync(id);
        var data = cacheItem.HasValue ? cacheItem.Value : PrinterData.CreateUnknown(id);
        await _printersStatusCache.SetAsync(id, data with { LastRestartTime = now });
        return await _printerProxy.Value.RestartProxy(id)
            .FlatMapAsync(() => GetPrinter(id))
            .MapAsync(v => v with { LastRestartTime = now });
    }


    private async Task<Either<IList<Printer>>> GetPrintersInternalAsync(PrinterQuery query, bool usePosnetService = false)
    {
        var printersEnabled = await _settingManager.Value.GetSettingValueAsync<bool>(AppSettingsNames.Printers.PrintersEnabled);
        if (!printersEnabled) return new List<Printer>().AsSuccess();

        var cacheTime = await _settingManager.Value.GetSettingValueAsync<int>(AppSettingsNames.Printers.CacheTimeMinutes);
        if (!usePosnetService && !query.NoCache && await _printersCache.GetOrDefaultAsync(0L) is { } cachedPrinters) return await FilterPrinters(cachedPrinters, query);

        if (usePosnetService)
        {
            return await _printerPosnetProxy.Value.GetPrintersAsync()
                .FlatMapAsync(apiPrinters => FilterPrinters(apiPrinters, query));
        }

        return await _printerProxy.Value.GetPrintersAsync()
            .TransparentExecuteAsync(async apiPrinters => await _printersCache.SetAsync(0L, apiPrinters, TimeSpan.FromMinutes(cacheTime)))
            .FlatMapAsync(apiPrinters => FilterPrinters(apiPrinters, query));
    }

    public Task<Either<PrinterJobStatus>> GetPrinterJobStatusAsync(Guid jobId)
    {
        return GetJobAsync(jobId).MapAsync(v => v.Status);
    }

    private Task<Either<IList<Printer>>> FilterPrinters(IEnumerable<Printer> fullList, PrinterQuery query)
    {
        return GetQueryOrgUnits(query).FlatMapAsync(orgUnits => fullList
            .WhereIf(query.Target != null, v => (v.Target ?? PrinterTarget.Reception) == query.Target!)
            .WhereIf(orgUnits != null, v => orgUnits?.Contains(v.OrgUnitId) == true)
            .ToList().AsSuccess());
    }

    private async Task<Either<ISet<long>?>> GetQueryOrgUnits(PrinterQuery query)
    {
        if (query.HostCompanyId is not { } hostCompanyId) return Either<ISet<long>?>.Success(query.OrgUnitId?.AsSet());
        return await _hostCompanyDbAdapter.Value.GetAsync(hostCompanyId).MapAsync(v => (ISet<long>)v.OrganizationUnits.ResolvedOrThrow().Select(v => v.Id).ToHashSet());
    }


    private record PrinterStatusData(string? Id, string? GlobalId, PrinterStatusVatGet VatGetValue);

    private async Task<PrinterStatusData> GetPrinterStatusData(string printerId)
    {
        var result = await _printerProxy.Value.GetPrinterStatus(printerId)
            .MapAsync(v =>
            {
                var counters = v.Results.GetCounters();
                return new PrinterStatusData(counters.Bt, counters.Hn, v.Results.Vatget);
            });
        return result.Or(new PrinterStatusData(null, null, new PrinterStatusVatGet()));
    }

    public Task<Either<PagedResultDto<PrinterJob>>> SearchAsync(ReceiptQuery query)
    {
        return _printerJobDbAdapter.Value.SearchAsync(query);
    }

    public Task<Either<decimal>> SearchSumAsync(ReceiptQuery query) => _printerJobDbAdapter.Value.GetPriceSum(query);

    public Task<Either<IList<long>>> GetReceiptCreatorsAsync()
    {
        return _printerJobDbAdapter.Value.GetCreatorsAsync();
    }


    private Task<Either<Guid?>> SendPrinterCommand(PrinterCreateJobData data)
    {
        return data.PrinterId is not { } printerId
            ? Either<Guid?>.SuccessTask(null)
            : _printerProxy.Value.PrintReceipt(data.Model, printerId.Value).FlatMapAsync(v => Either<Guid?>.Success(v));
    }

    private async Task<Either<PrinterJob>> PollPrinterJob(PrinterJob printerJob)
    {
        if (!string.IsNullOrWhiteSpace(printerJob.PrinterId))
        {
            var delayMs = await _settingManager.Value.GetSettingValueAsync<int>(AppSettingsNames.Printers.RetryDelayMs);
            var count = 0;
            var startTimestamp = Clock.Now;
            while (count++ < 4 && Clock.Now.Subtract(startTimestamp) < TimeSpan.FromSeconds(5))
            {
                var pollResult = await FillJobStatus(printerJob);
                if (!pollResult.Map(v => v.Job.Status).Contains(PrinterJobStatus.Started)) break;
                await Task.Delay(delayMs);
            }
        }


        //As printer request is already sent, we want to insert entity to db and prevent rollback caused by further failure
        return await _printerJobDbAdapter.Value.InsertNonTransactionalAsync(printerJob);
    }


    public Task<Either<PrinterJob>> CreateJobAsync(PrinterCreateJobData data)
    {
        return SendPrinterCommand(data).FlatMapAsync(async transactionId =>
        {
            var entity = new PrinterJob()
            {
                CreatorUserId = data.RequesterId,
                Id = transactionId ?? Guid.NewGuid(),
                PrinterId = data.PrinterId?.Value ?? string.Empty,
                PrinterSerial = data.PrinterId?.Serial,
                Model = data.Model,
                JobContext = data.JobContext,
                ExpirationTime = Clock.Now.AddSeconds(20),
                Status = data.PrinterId != null ? PrinterJobStatus.Started : PrinterJobStatus.NotStarted,
                PatientId = data.PatientId,
                OrganizationUnitId = data.OrgUnitId,
                PaymentMode = data.PaymentMode,
                VatLevels = data.PrinterId?.VatLevels ?? new PrinterStatusVatGet()
            };
            if (data.SetId is { } notNullSetId)
            {
                entity.PrinterJobSetId = notNullSetId;
            }
            else
            {
                entity.PrinterJobSet = new PrinterJobSet() { Source = data.Source };
            }


            return await PollPrinterJob(entity);
        });
    }


    private Task<Either<PrinterJob>> GetJobAsync(Guid id)
    {
        return _printerJobDbAdapter.Value.GetAsync(id).FlatMapAsync(job =>
        {
            return job.Status == PrinterJobStatus.Started
                ? FillJobStatus(job).FlatMapAsync(j => j.NeedUpdate
                    ? UpdateJobStatusAsync(j.Job)
                    : j.Job.AsSuccessTask())
                : job.AsSuccessTask();
        });
    }

    private Task<Either<PrinterJob>> UpdateJobStatusAsync(PrinterJob job)
    {
        return _printerJobDbAdapter.Value.UpdateAsync(job);
    }

    private record FillJobStatusResult(PrinterJob Job, bool NeedUpdate);

    private Task<Either<FillJobStatusResult>> FillJobStatus(PrinterJob job)
    {
        return _printerProxy.Value.GetReceiptStatus(job.Id, job.PrinterId).FlatMapAsync(printerStatus =>
        {
            var now = Clock.Now;

            if (printerStatus.Hits is not { } hits)
            {
                job.Status = PrinterJobStatus.Error;
                job.CompletionTime = now;
                job.Error = "Printer hits array is null";
                return new FillJobStatusResult(job, true).AsSuccess();
            }

            var errorOpt = hits.Select(v => v.Stat)
                .FilterNotNull()
                .Select(v => v.Error)
                .FirstOrDefault(v => !string.IsNullOrWhiteSpace(v));
            var isPending = hits.Any(v => v.InProgress);
            if (hits.Count == 0)
            {
                job.Status = PrinterJobStatus.Error;
                job.Error = "No printer hits";
                job.CompletionTime = now;
            }
            else if (errorOpt is { } error)
            {
                job.Status = PrinterJobStatus.Error;
                job.Error = PrinterErrorResolver.ResolveError(error);
                job.CompletionTime = now;
            }
            else if (!isPending && printerStatus.Ok)
            {
                job.CompletionTime = now;
                job.Status = PrinterJobStatus.Completed;

                var stat = hits.Select(v => v.Stat)
                    .FilterNotNull()
                    .FirstOrDefault();

                job.ReceiptId = stat?.Bn;
                job.ReceiptGlobalId = stat?.Hn;
            }
            else if (job.ExpirationTime <= Clock.Now)
            {
                job.Status = PrinterJobStatus.Expired;
            }
            else
            {
                return new FillJobStatusResult(job, false).AsSuccess();
            }

            return new FillJobStatusResult(job, true).AsSuccess();
        });
    }
}

/// PrinterPosnetProxy.cs

using Medica.Commerce.Printers.ApiModel;
using Microsoft.Extensions.Configuration;
using RestSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Medica.Commerce.PrintersPosnet;

public class PrinterPosnetProxy : IPrinterPosnetProxy
{
    private readonly IPrinterPosnetService _printerPosnetServive;
    private readonly IPrinterPosnetProxyClient _printerPosnetProxyClient;

    private readonly string _port;

    public PrinterPosnetProxy(
        IConfiguration configuration,
        IPrinterPosnetService printerPosnetServive,
        IPrinterPosnetProxyClient printerPosnetProxyClient)
    {
        _port = configuration["FiscalPrinterApi:Port"];
        _printerPosnetServive = printerPosnetServive;
        _printerPosnetProxyClient = printerPosnetProxyClient;
    }

    public async Task<Either<IList<Printer>>> GetPrintersAsync()
    {
        var posnetPrinters = await _printerPosnetServive.GetAllPrintersAsync();
        if (posnetPrinters == null || !posnetPrinters.Any())
        {
            return Either<IList<Printer>>.InternalError("No Posnet printers found.");
        }

        var printers = posnetPrinters.Select(p => new Printer
        {
            Id = p.Id.ToString(),
            Name = p.Name,
            OrgUnitId = p.OrganizationUnitId,
            Target = PrinterPosnetTarget.GetByEnumVal(p.Target)
        }).ToList();

        return Either<IList<Printer>>.Success(printers);
    }

    public async Task<Either<PrinterStatus>> GetPrinterStatus(string id)
    {
        var ipAddress = await GetIpAddressAsync(new Guid(id));
        if (string.IsNullOrWhiteSpace(ipAddress))
        {
            return Either<PrinterStatus>.InternalError($"Ip address of printer with ID {id} not found.");
        }

        var request = new RestRequest("/status", Method.GET, DataFormat.Json);
        request.AddQueryParameter("ip", ipAddress);
        request.AddQueryParameter("port", _port);
        return await _printerPosnetProxyClient.ExecuteAsync<PrinterStatus>(request);
    }

    public async Task<Either<Guid>> PrintReceipt(PrinterRequestModel data, string id)
    {
        var ipAddress = await GetIpAddressAsync(new Guid(id));
        if (string.IsNullOrWhiteSpace(ipAddress))
        {
            return Either<Guid>.InternalError($"Ip address of printer with ID {id} not found.");
        }

        var request = new RestRequest("/paragon", Method.POST, DataFormat.Json);
        request.AddQueryParameter("ip", ipAddress);
        request.AddQueryParameter("port", _port);
        request.AddJsonBody(data);
        return await _printerPosnetProxyClient.ExecuteAsync<PrinterResponse>(request)
            .FlatMapAsync(response => response.Transaction == Guid.Empty
                ? Either<Guid>.InternalError($"Response data does not contain valid transaction id")
                : response.Transaction.AsSuccess()
            );
    }

    public async Task<Either<ReceiptStatusResponse>> GetReceiptStatus(Guid id, string printerId)
    {
        var request = new RestRequest($"/paragon/{id}", Method.GET, DataFormat.Json);
        return await _printerPosnetProxyClient.ExecuteAsync<ReceiptStatusResponse>(request);
    }

    private async Task<Either<string>> GetIpAddressAsync(Guid id)
    {
        var result = await _printerPosnetServive.GetAsync(id);
        return result.Match(
            success => Either<string>.Success(success.IpAddress),
            error => throw new Exception(error.Message)
        );
    }
}

/// IPrinterPosnetProxy.cs

using Medica.Commerce.Printers;

namespace Medica.Commerce.PrintersPosnet;

public interface IPrinterPosnetProxy : IPrinterProxyBase
{
}

/// IPrinterProxyBase.cs

using Abp.Dependency;
using Medica.Commerce.Printers.ApiModel;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Medica.Commerce.Printers;

public interface IPrinterProxyBase : ITransientDependency
{
    Task<Either<IList<Printer>>> GetPrintersAsync();
    Task<Either<PrinterStatus>> GetPrinterStatus(string printerId);
    Task<Either<Guid>> PrintReceipt(PrinterRequestModel data, string printerId);
    Task<Either<ReceiptStatusResponse>> GetReceiptStatus(Guid id, string printerId);
}

/// IPrinterPosnetService.cs

using Medica.Commerce.PrintersPosnet.Repository;
using Medica.Helpers.Services;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Medica.Commerce.PrintersPosnet;

public interface IPrinterPosnetService : ICrudService<Guid, FiscalPrinterPosnet, PrinterPosnetQuery>
{
    Task<List<FiscalPrinterPosnet>> GetAllPrintersAsync();
}

/// PrinterPosnetService.cs

using Abp.Configuration;
using Medica.Commerce.PrintersPosnet.Repository;
using Medica.Helpers.Services;
using Medica.Telemetry;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Medica.Commerce.PrintersPosnet;

public class PrinterPosnetService : CrudService<Guid, FiscalPrinterPosnet, PrinterPosnetQuery>, IPrinterPosnetService
{
    private readonly IPrinterPosnetDbAdapter _printerPosnetDbAdapter;
    private readonly ISettingManager _settingManager;
    private readonly ITelemetry _telemetry;

    public PrinterPosnetService(
        IPrinterPosnetDbAdapter dbAdapter,
        ISettingManager settingManager,
        ITelemetry telemetry)
    : base(dbAdapter)
    {
        _printerPosnetDbAdapter = dbAdapter;
        _settingManager = settingManager;
        _telemetry = telemetry;
    }

    protected override void UpdateStaticFields(FiscalPrinterPosnet target, FiscalPrinterPosnet source)
    {
        target.Name = source.Name;
        target.PrinterId = source.PrinterId;
        target.IpAddress = source.IpAddress;
        target.Target = source.Target;
        target.IsActive = source.IsActive;
        target.OrganizationUnitId = source.OrganizationUnitId;
    }

    protected override Task<Either> ValidateDeleteRequest(Guid id)
    {
        return Either.SuccessTask();
    }

    public async Task<List<FiscalPrinterPosnet>> GetAllPrintersAsync()
    {
        try
        {
            var result = await _printerPosnetDbAdapter.GetAllAsync();

            var printers = result.OrThrow();

            if (printers.Any())
            {
                return printers.ToList();
            }
            else
            {
                var exceptionMessage = "Failed to retrieve printers: No printers found.";
                _telemetry?.LogException(new Exception(exceptionMessage));
                throw new Exception("Nie udało się pobrać drukarek fiskalnych Posnet.");
            }
        }
        catch (Exception ex)
        {
            _telemetry?.LogException(ex);
            throw new Exception("Nie udało się pobrać drukarek fiskalnych Posnet.", ex);
        }
    }
}

/// FiscalPrinterPosnet.cs

#nullable enable

using Abp.Domain.Entities;
using Abp.Domain.Entities.Auditing;
using Abp.Organizations;
using Medica.Helpers.Database;
using Medica.OrganizationUnits;
using System;

namespace Medica.Commerce.PrintersPosnet.Repository;

public class FiscalPrinterPosnet : FullAuditedEntity<Guid>, IMustHaveOrganizationUnit, IMustHaveTenant, INamedEntity, IPassivable
{
    public string Name { get; set; } = default!;

    public string? PrinterId { get; set; }

    public string? IpAddress { get; set; }

    public int Target { get; set; }

    public bool IsActive { get; set; }

    public int TenantId { get; set; }

    public long OrganizationUnitId { get; set; }

    public virtual MedicaOrganizationUnit? OrganizationUnit { get; set; }
}

/// CrudService.cs

using System.Threading.Tasks;
using Abp.Application.Services.Dto;
using Abp.Domain.Entities;
using Medica.Helpers.Database;
using Medica.Helpers.PagedQueryHelpers;

namespace Medica.Helpers.Services;

public abstract class CrudService<TKey, TEntity> : ICrudService<TKey, TEntity> where TEntity : class, IEntity<TKey>
{

    private readonly IDbAdapterBaseGeneric<TKey, TEntity> _dbAdapter;

    protected CrudService(IDbAdapterBaseGeneric<TKey, TEntity> dbAdapter)
    {
        _dbAdapter = dbAdapter;
    }

    public virtual Task<Either<TEntity>> CreateAsync(TEntity entity) => _dbAdapter.InsertAsync(entity);

    public virtual Task<Either<TEntity>> UpdateAsync(TEntity entity) => _dbAdapter.UpdateFieldsAndReturnValue(entity.Id, existing => UpdateStaticFields(existing, entity));

    public virtual Task<Either<TEntity>> GetAsync(TKey id) => _dbAdapter.GetForEditAsync(id);

    public virtual Task<Either> DeleteAsync(TKey id) => ValidateDeleteRequest(id).FlatMapAsync(() => _dbAdapter.DeleteAsync(id));

    protected abstract void UpdateStaticFields(TEntity target, TEntity source);

    protected virtual Task<Either> ValidateDeleteRequest(TKey id) => Either.SuccessTask();
}

public abstract class CrudService<TKey, TEntity, TQuery> : CrudService<TKey, TEntity>, ICrudService<TKey, TEntity, TQuery>
    where TEntity : class, IEntity<TKey>
    where TQuery : PagedQuery
{
    private readonly IDbAdapterBaseGeneric<TKey, TEntity, TQuery> _dbAdapter;

    protected CrudService(IDbAdapterBaseGeneric<TKey, TEntity, TQuery> dbAdapter) : base(dbAdapter)
    {
        _dbAdapter = dbAdapter;
    }

    public virtual Task<Either<PagedResultDto<TEntity>>> SearchAsync(TQuery query) => _dbAdapter.SearchAsync(query);
}