< Summary

Information
Class: Services.BackgroundJobsServices.OzonReportLoaderService
Assembly: Services
File(s): /home/runner/work/Studio2Prod/Studio2Prod/AppRepository/BackgroundJobsServices/OzonReportLoaderService.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 155
Coverable lines: 155
Total lines: 275
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 42
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%210%
LoadAndParseReportAsync()0%272160%
LoadAndParseReturnsReportAsync()0%272160%
DownloadReportBytesAsync()100%210%
BuildReportFileName(...)100%210%
BuildReturnsReportFileName(...)0%2040%
GuessContentType(...)0%4260%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/AppRepository/BackgroundJobsServices/OzonReportLoaderService.cs

#LineLine coverage
 1using Microsoft.AspNetCore.Http;
 2using Services.ApiServices.OzonApi;
 3using Services.ApiServices.OzonApi.Filters;
 4using Services.BackgroundJobsServices;
 5using Services.DataServices;
 6using Services.ParserServices.FileProcessing;
 7using SlqStudio.Application.Services.EmailService;
 8using Services.SystemMonitoringServices;
 9
 10namespace Services.BackgroundJobsServices;
 11
 12public interface IOzonReportLoaderService
 13{
 14    Task<(byte[] fileBytes, string fileName, List<(string OrderNumber, string Article)> parsedOrders)> LoadAndParseRepor
 15        int ozonClientId,
 16        DateTime reportDownloadFrom,
 17        DateTime reportDownloadTo,
 18        IReadOnlyList<string> rowFilterDateKeys,
 19        long executionId,
 20        long stepId,
 21        IBackgroundJobLoggingService loggingService,
 22        CancellationToken cancellationToken = default);
 23
 24    Task<(byte[] fileBytes, string fileName, List<string> shipmentNumbers)> LoadAndParseReturnsReportAsync(
 25        int ozonClientId,
 26        DateTime reportDownloadFrom,
 27        DateTime reportDownloadTo,
 28        IReadOnlyList<string> rowFilterDateKeys,
 29        long executionId,
 30        long stepId,
 31        IBackgroundJobLoggingService loggingService,
 32        CancellationToken cancellationToken = default);
 33}
 34
 35public class OzonReportLoaderService : IOzonReportLoaderService
 36{
 37    private readonly OzonClientServices _ozonClientServices;
 38    private readonly IHttpClientFactory _httpClientFactory;
 39    private readonly IEmailService _emailService;
 40    private readonly IFileProcessingService _fileProcessingService;
 41    private readonly ISystemMonitoringSignalWriter? _signals;
 42
 043    public OzonReportLoaderService(
 044        OzonClientServices ozonClientServices,
 045        IHttpClientFactory httpClientFactory,
 046        IEmailService emailService,
 047        IFileProcessingService fileProcessingService,
 048        ISystemMonitoringSignalWriter? signals = null)
 49    {
 050        _ozonClientServices = ozonClientServices;
 051        _httpClientFactory = httpClientFactory;
 052        _emailService = emailService;
 053        _fileProcessingService = fileProcessingService;
 054        _signals = signals;
 055    }
 56
 57    public async Task<(byte[] fileBytes, string fileName, List<(string OrderNumber, string Article)> parsedOrders)> Load
 58        int ozonClientId,
 59        DateTime reportDownloadFrom,
 60        DateTime reportDownloadTo,
 61        IReadOnlyList<string> rowFilterDateKeys,
 62        long executionId,
 63        long stepId,
 64        IBackgroundJobLoggingService loggingService,
 65        CancellationToken cancellationToken = default)
 66    {
 067        var reportLoadId = await loggingService.LogReportLoadAsync(
 068            executionId, stepId, ozonClientId, reportDownloadFrom, reportDownloadTo, cancellationToken: cancellationToke
 69
 70        try
 71        {
 072            var ozonClient = await _ozonClientServices.GetOzonClientAsync(ozonClientId);
 073            if (ozonClient == null || string.IsNullOrWhiteSpace(ozonClient.DecryptClientId) || string.IsNullOrWhiteSpace
 074                throw new Exception($"Ozon client {ozonClientId} not found or has invalid credentials");
 75
 076            var apiManager = new OzonApiDataManager(_httpClientFactory, _emailService, ozonClient.DecryptClientId, ozonC
 77
 078            var reportRequest = new PostingsReportRequest
 079            {
 080                Filter = new PostingsReportRequestFilter
 081                {
 082                    ProcessedAtFrom = reportDownloadFrom,
 083                    ProcessedAtTo = reportDownloadTo,
 084                    DeliverySchema = new[] { "fbs" },
 085                    IsExpress = false,
 086                    Sku = Array.Empty<string>(),
 087                    CancelReasonId = Array.Empty<string>(),
 088                    OfferId = string.Empty,
 089                    StatusAlias = Array.Empty<string>(),
 090                    Statuses = Array.Empty<string>(),
 091                    Title = string.Empty,
 092                    WarehouseId = Array.Empty<long>(),
 093                    DeliveryMethodId = Array.Empty<long>()
 094                },
 095                Language = "DEFAULT",
 096                With = new PostingsReportRequestWith
 097                {
 098                    AdditionalData = false,
 099                    AnalyticsData = false,
 0100                    CustomerData = false,
 0101                    JewelryCodes = false
 0102                }
 0103            };
 104
 0105            Console.WriteLine(
 0106                $"[OzonReportLoader] Requesting report for API period {reportDownloadFrom:O} - {reportDownloadTo:O}, row
 0107            var reportCode = await apiManager.GetReportCode(reportRequest);
 0108            await loggingService.UpdateReportLoadAsync(reportLoadId, status: "Requested", cancellationToken: cancellatio
 109
 0110            Console.WriteLine($"[OzonReportLoader] Report code: {reportCode}, waiting for ready...");
 0111            var fileUrl = await apiManager.WaitForReportReady(reportCode);
 0112            await loggingService.UpdateReportLoadAsync(reportLoadId, fileUrl: fileUrl, status: "Ready", cancellationToke
 113
 0114            Console.WriteLine($"[OzonReportLoader] Downloading report from: {fileUrl}");
 0115            var fileBytes = await DownloadReportBytesAsync(fileUrl, cancellationToken);
 0116            var fileName = BuildReportFileName(reportCode);
 117
 0118            await loggingService.UpdateReportLoadAsync(reportLoadId, fileName: fileName, fileSizeBytes: fileBytes.Length
 119
 0120            Console.WriteLine($"[OzonReportLoader] Parsing CSV file, size: {fileBytes.Length} bytes");
 0121            await using var stream = new MemoryStream(fileBytes);
 0122            var reportFile = new FormFile(stream, 0, fileBytes.Length, "excelFile", fileName)
 0123            {
 0124                Headers = new HeaderDictionary(),
 0125                ContentType = "text/csv"
 0126            };
 127
 0128            var parsedOrders = await _fileProcessingService.ProcessFileAsync(
 0129                reportFile,
 0130                dateColumnName: ReportDeliveryDateFilter.DeliveryDateColumnName,
 0131                selectedDateValues: rowFilterDateKeys);
 132
 0133            var uniqueOrderNumbers = parsedOrders.Select(o => o.OrderNumber).Distinct().Count();
 0134            await loggingService.UpdateReportLoadAsync(
 0135                reportLoadId,
 0136                totalRows: parsedOrders.Count,
 0137                rowsWithRequiredData: parsedOrders.Count,
 0138                uniqueOrderNumbers: uniqueOrderNumbers,
 0139                cancellationToken: cancellationToken);
 140
 0141            Console.WriteLine(
 0142                $"[OzonReportLoader] Success: {parsedOrders.Count} rows after filter [{string.Join(", ", rowFilterDateKe
 143
 0144            await (_signals?.RecordAsync("ozon-report-import", "success", cancellationToken: cancellationToken) ?? Task.
 0145            return (fileBytes, fileName, parsedOrders);
 0146        }
 0147        catch (Exception ex)
 148        {
 0149            await loggingService.UpdateReportLoadAsync(reportLoadId, status: "Failed", errorMessage: ex.Message, cancell
 0150            await (_signals?.RecordAsync("ozon-report-import", "failure", message: "Ошибка загрузки отчёта Ozon", cancel
 0151            throw;
 152        }
 0153    }
 154
 155    public async Task<(byte[] fileBytes, string fileName, List<string> shipmentNumbers)> LoadAndParseReturnsReportAsync(
 156        int ozonClientId,
 157        DateTime reportDownloadFrom,
 158        DateTime reportDownloadTo,
 159        IReadOnlyList<string> rowFilterDateKeys,
 160        long executionId,
 161        long stepId,
 162        IBackgroundJobLoggingService loggingService,
 163        CancellationToken cancellationToken = default)
 164    {
 0165        var reportLoadId = await loggingService.LogReportLoadAsync(
 0166            executionId, stepId, ozonClientId, reportDownloadFrom, reportDownloadTo, cancellationToken: cancellationToke
 167
 168        try
 169        {
 0170            var ozonClient = await _ozonClientServices.GetOzonClientAsync(ozonClientId);
 0171            if (ozonClient == null || string.IsNullOrWhiteSpace(ozonClient.DecryptClientId) || string.IsNullOrWhiteSpace
 0172                throw new Exception($"Ozon client {ozonClientId} not found or has invalid credentials");
 173
 0174            var apiManager = new OzonApiDataManager(_httpClientFactory, _emailService, ozonClient.DecryptClientId, ozonC
 175
 0176            var reportFromUtc = TimeZoneInfo.ConvertTimeToUtc(
 0177                DateTime.SpecifyKind(reportDownloadFrom.Date, DateTimeKind.Unspecified),
 0178                ReportDeliveryDateFilter.MoscowTimeZone);
 0179            var reportToUtc = TimeZoneInfo.ConvertTimeToUtc(
 0180                DateTime.SpecifyKind(reportDownloadTo, DateTimeKind.Unspecified),
 0181                ReportDeliveryDateFilter.MoscowTimeZone);
 0182            var reportCode = await apiManager.CreateSellerReturnsReportAsync(reportFromUtc, reportToUtc);
 0183            await loggingService.UpdateReportLoadAsync(reportLoadId, status: "Requested", cancellationToken: cancellatio
 184
 0185            var fileUrl = await apiManager.WaitForReportReady(reportCode);
 0186            await loggingService.UpdateReportLoadAsync(reportLoadId, fileUrl: fileUrl, status: "Ready", cancellationToke
 187
 0188            var fileBytes = await DownloadReportBytesAsync(fileUrl, cancellationToken);
 0189            var fileName = BuildReturnsReportFileName(reportCode, fileUrl);
 0190            var contentType = GuessContentType(fileName);
 191
 0192            await loggingService.UpdateReportLoadAsync(reportLoadId, fileName: fileName, fileSizeBytes: fileBytes.Length
 193
 0194            await using var stream = new MemoryStream(fileBytes);
 0195            var reportFile = new FormFile(stream, 0, fileBytes.Length, "excelFile", fileName)
 0196            {
 0197                Headers = new HeaderDictionary(),
 0198                ContentType = contentType
 0199            };
 200
 0201            var shipmentNumbers = await _fileProcessingService.ProcessShipmentOnlyAsync(
 0202                reportFile,
 0203                dateColumnName: ReportDeliveryDateFilter.ReturnsStatusDateColumnName,
 0204                selectedDateValues: rowFilterDateKeys);
 205
 0206            Console.WriteLine(
 0207                $"[OzonReportLoader] Returns: {shipmentNumbers.Count} rows after filter [{string.Join(", ", rowFilterDat
 208
 0209            var uniqueShipments = shipmentNumbers
 0210                .Where(x => !string.IsNullOrWhiteSpace(x))
 0211                .Select(x => x.Trim())
 0212                .Distinct(StringComparer.OrdinalIgnoreCase)
 0213                .ToList();
 214
 0215            await loggingService.UpdateReportLoadAsync(
 0216                reportLoadId,
 0217                totalRows: shipmentNumbers.Count,
 0218                rowsWithRequiredData: shipmentNumbers.Count,
 0219                uniqueOrderNumbers: uniqueShipments.Count,
 0220                cancellationToken: cancellationToken);
 221
 0222            await (_signals?.RecordAsync("ozon-report-import", "success", cancellationToken: cancellationToken) ?? Task.
 0223            return (fileBytes, fileName, uniqueShipments);
 0224        }
 0225        catch (Exception ex)
 226        {
 0227            await loggingService.UpdateReportLoadAsync(reportLoadId, status: "Failed", errorMessage: ex.Message, cancell
 0228            await (_signals?.RecordAsync("ozon-report-import", "failure", message: "Ошибка загрузки отчёта Ozon", cancel
 0229            throw;
 230        }
 0231    }
 232
 233    private async Task<byte[]> DownloadReportBytesAsync(string fileUrl, CancellationToken cancellationToken = default)
 234    {
 0235        using var client = _httpClientFactory.CreateClient();
 0236        return await OzonReportDownloadHelper.DownloadBytesWithRetryAsync(client, fileUrl, cancellationToken: cancellati
 0237    }
 238
 239    private static string BuildReportFileName(string reportCode)
 240    {
 0241        return $"ozon_report_{reportCode}_{DateTime.UtcNow:yyyyMMdd_HHmmss}.csv";
 242    }
 243
 244    private static string BuildReturnsReportFileName(string reportCode, string? fileUrl)
 245    {
 0246        var extension = ".xlsx";
 0247        if (!string.IsNullOrWhiteSpace(fileUrl))
 248        {
 249            try
 250            {
 0251                extension = Path.GetExtension(new Uri(fileUrl).AbsolutePath);
 0252            }
 0253            catch
 254            {
 0255                extension = ".xlsx";
 0256            }
 257        }
 258
 0259        if (string.IsNullOrWhiteSpace(extension))
 0260            extension = ".xlsx";
 261
 0262        return $"ozon_returns_{reportCode}_{DateTime.UtcNow:yyyyMMdd_HHmmss}{extension}";
 263    }
 264
 265    private static string GuessContentType(string fileName)
 266    {
 0267        var extension = Path.GetExtension(fileName)?.ToLowerInvariant();
 0268        return extension switch
 0269        {
 0270            ".xlsx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
 0271            ".xls" => "application/vnd.ms-excel",
 0272            _ => "text/csv"
 0273        };
 274    }
 275}