< Summary

Information
Class: Services.ApiServices._1CApi.Stock.OneCWarehouseStockExcelExporter
Assembly: Services
File(s): /home/runner/work/Studio2Prod/Studio2Prod/AppRepository/ApiServices/1CApi/Stock/OneCWarehouseStockExcelExporter.cs
Line coverage
97%
Covered lines: 100
Uncovered lines: 3
Coverable lines: 103
Total lines: 166
Line coverage: 97%
Branch coverage
65%
Covered branches: 26
Total branches: 40
Branch coverage: 65%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
ExportAsync()70%101095%
BuildWorkbook(...)65%2020100%
DescribeFilters(...)60%111081.81%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/AppRepository/ApiServices/1CApi/Stock/OneCWarehouseStockExcelExporter.cs

#LineLine coverage
 1using ClosedXML.Excel;
 2
 3namespace Services.ApiServices._1CApi.Stock;
 4
 5public interface IOneCWarehouseStockExcelExporter
 6{
 7    Task<OneCWarehouseStockExcelExportResult> ExportAsync(
 8        OneCWarehouseStockQuery query,
 9        CancellationToken cancellationToken = default);
 10}
 11
 12public sealed record OneCWarehouseStockExcelExportResult(
 13    byte[]? Bytes,
 14    string? Error,
 15    int RowCount)
 16{
 17    public bool Succeeded => Bytes is not null;
 18}
 19
 20/// <summary>Собирает в Excel все страницы одной выборки остатков, сохраняя правила фильтрации страницы.</summary>
 21public sealed class OneCWarehouseStockExcelExporter : IOneCWarehouseStockExcelExporter
 22{
 23    private const int ExportPageSize = 100;
 24    private readonly IOneCWarehouseStockService _stockService;
 25
 126    public OneCWarehouseStockExcelExporter(IOneCWarehouseStockService stockService)
 127        => _stockService = stockService;
 28
 29    public async Task<OneCWarehouseStockExcelExportResult> ExportAsync(
 30        OneCWarehouseStockQuery query,
 31        CancellationToken cancellationToken = default)
 32    {
 133        var rows = new List<OneCWarehouseStockRow>();
 134        OneCWarehouseStockResult? firstPage = null;
 135        var page = 1;
 36
 137        while (true)
 38        {
 239            var pageQuery = query with
 240            {
 241                Page = page,
 242                PageSize = ExportPageSize,
 243                ForceRefresh = page == 1 && query.ForceRefresh
 244            };
 245            var result = await _stockService.QueryAsync(pageQuery, cancellationToken);
 246            if (result.Failed)
 047                return new OneCWarehouseStockExcelExportResult(null, result.Warning ?? "Не удалось загрузить остатки из 
 48
 249            firstPage ??= result;
 250            rows.AddRange(result.Rows);
 51
 252            var totalPages = Math.Max(1, (int)Math.Ceiling(result.TotalCount / (double)ExportPageSize));
 253            if (page >= totalPages)
 54                break;
 55
 156            page++;
 57        }
 58
 159        return new OneCWarehouseStockExcelExportResult(BuildWorkbook(firstPage!, query, rows), null, rows.Count);
 160    }
 61
 62    private static byte[] BuildWorkbook(
 63        OneCWarehouseStockResult summary,
 64        OneCWarehouseStockQuery query,
 65        IReadOnlyList<OneCWarehouseStockRow> rows)
 66    {
 167        using var workbook = new XLWorkbook();
 168        var sheet = workbook.Worksheets.Add("Остатки 1С");
 169        sheet.ShowGridLines = false;
 70
 71        const int columns = 10;
 172        sheet.Range(1, 1, 1, columns).Merge();
 173        sheet.Cell(1, 1).Value = $"Остатки 1С: {summary.WarehouseName}";
 174        sheet.Cell(1, 1).Style.Font.Bold = true;
 175        sheet.Cell(1, 1).Style.Font.FontSize = 14;
 176        sheet.Cell(1, 1).Style.Font.FontColor = XLColor.FromHtml("0F172A");
 77
 178        sheet.Range(2, 1, 2, columns).Merge();
 179        sheet.Cell(2, 1).Value = $"Снимок: {summary.SnapshotAtUtc.ToLocalTime():dd.MM.yyyy HH:mm} · Позиций: {summary.To
 180        sheet.Cell(2, 1).Style.Font.Italic = true;
 181        sheet.Cell(2, 1).Style.Font.FontColor = XLColor.FromHtml("64748B");
 82
 183        sheet.Range(3, 1, 3, columns).Merge();
 184        sheet.Cell(3, 1).Value = DescribeFilters(query);
 185        sheet.Cell(3, 1).Style.Font.FontColor = XLColor.FromHtml("475569");
 186        sheet.Cell(3, 1).Style.Fill.BackgroundColor = XLColor.FromHtml("F8FAFC");
 87
 188        var headers = new[]
 189        {
 190            "Артикул", "Наименование", "Производитель", "Остаток, шт.", "Последний приход",
 191            "Дней на складе", "Категория давности", "Документ", "Тип документа", "Поставщик"
 192        };
 2293        for (var index = 0; index < headers.Length; index++)
 1094            sheet.Cell(5, index + 1).Value = headers[index];
 95
 196        var header = sheet.Range(5, 1, 5, columns);
 197        header.Style.Font.Bold = true;
 198        header.Style.Font.FontColor = XLColor.FromHtml("1E3A5F");
 199        header.Style.Fill.BackgroundColor = XLColor.FromHtml("DBEAFE");
 1100        header.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
 1101        header.Style.Alignment.Vertical = XLAlignmentVerticalValues.Center;
 1102        header.Style.Border.BottomBorder = XLBorderStyleValues.Thin;
 1103        header.Style.Border.BottomBorderColor = XLColor.FromHtml("93C5FD");
 104
 1105        var rowNumber = 6;
 204106        foreach (var row in rows)
 107        {
 101108            sheet.Cell(rowNumber, 1).Value = row.Article ?? string.Empty;
 101109            sheet.Cell(rowNumber, 2).Value = row.Name ?? string.Empty;
 101110            sheet.Cell(rowNumber, 3).Value = row.Producer ?? string.Empty;
 101111            sheet.Cell(rowNumber, 4).Value = row.Quantity;
 101112            sheet.Cell(rowNumber, 5).Value = row.LastReceiptDate;
 101113            sheet.Cell(rowNumber, 6).Value = row.DaysOnStock;
 101114            sheet.Cell(rowNumber, 7).Value = row.AgeBucket.HasValue
 101115                ? OneCWarehouseStockQueryBuilder.BucketLabel(row.AgeBucket.Value)
 101116                : string.Empty;
 101117            sheet.Cell(rowNumber, 8).Value = row.LastReceiptDocNumber ?? string.Empty;
 101118            sheet.Cell(rowNumber, 9).Value = row.LastReceiptDocKind ?? string.Empty;
 101119            sheet.Cell(rowNumber, 10).Value = row.LastReceiptSupplier ?? string.Empty;
 101120            rowNumber++;
 121        }
 122
 1123        if (rows.Count > 0)
 124        {
 1125            var dataRange = sheet.Range(6, 1, rowNumber - 1, columns);
 1126            dataRange.Style.Alignment.Vertical = XLAlignmentVerticalValues.Center;
 1127            dataRange.Style.Border.BottomBorder = XLBorderStyleValues.Hair;
 1128            dataRange.Style.Border.BottomBorderColor = XLColor.FromHtml("E2E8F0");
 1129            sheet.Range(6, 4, rowNumber - 1, 4).Style.NumberFormat.Format = "#,##0.00";
 1130            sheet.Range(6, 5, rowNumber - 1, 5).Style.DateFormat.Format = "dd.MM.yyyy";
 1131            sheet.Range(6, 6, rowNumber - 1, 6).Style.NumberFormat.Format = "0";
 1132            sheet.Range(5, 1, rowNumber - 1, columns).SetAutoFilter();
 133        }
 134
 1135        sheet.Column(1).Width = 20;
 1136        sheet.Column(2).Width = 42;
 1137        sheet.Column(3).Width = 22;
 1138        sheet.Column(4).Width = 15;
 1139        sheet.Column(5).Width = 16;
 1140        sheet.Column(6).Width = 18;
 1141        sheet.Column(7).Width = 24;
 1142        sheet.Column(8).Width = 20;
 1143        sheet.Column(9).Width = 24;
 1144        sheet.Column(10).Width = 28;
 1145        sheet.SheetView.FreezeRows(5);
 146
 1147        using var stream = new MemoryStream();
 1148        workbook.SaveAs(stream);
 1149        return stream.ToArray();
 1150    }
 151
 152    private static string DescribeFilters(OneCWarehouseStockQuery query)
 153    {
 1154        var age = query.Mode switch
 1155        {
 0156            StockAgeFilterMode.OlderThanDays => $"лежит более {query.OlderThanDays ?? 0} дн.",
 0157            StockAgeFilterMode.ReceiptBetween => $"приход с {query.ReceiptFrom:dd.MM.yyyy} по {query.ReceiptTo:dd.MM.yyy
 1158            _ => "без ограничения по сроку поступления"
 1159        };
 1160        var filters = new List<string> { age };
 2161        if (!string.IsNullOrWhiteSpace(query.ArticleSearch)) filters.Add($"артикул: {query.ArticleSearch}");
 1162        if (!string.IsNullOrWhiteSpace(query.ProducerSearch)) filters.Add($"производитель: {query.ProducerSearch}");
 1163        if (query.MinQuantity > 0) filters.Add($"остаток от {query.MinQuantity:N2}");
 1164        return "Фильтры: " + string.Join(" · ", filters);
 165    }
 166}