< Summary

Information
Class: Services.AiAssistant.AiChatEnrichmentBuilder
Assembly: Services
File(s): /home/runner/work/Studio2Prod/Studio2Prod/AppRepository/AiAssistant/AiChatEnrichmentBuilder.cs
Line coverage
100%
Covered lines: 117
Uncovered lines: 0
Coverable lines: 117
Total lines: 176
Line coverage: 100%
Branch coverage
78%
Covered branches: 22
Total branches: 28
Branch coverage: 78.5%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
BuildForArticleAsync()100%22100%
ResolveProductVisualAsync()100%66100%
BuildStockTableAsync()100%66100%
BuildMovementMapAsync()60%1010100%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/AppRepository/AiAssistant/AiChatEnrichmentBuilder.cs

#LineLine coverage
 1using Microsoft.EntityFrameworkCore;
 2using OzonRepositories.Context;
 3using Services.ApiServices;
 4using Services.BitrixStockServices;
 5using Services.StockSyncServices;
 6
 7namespace Services.AiAssistant;
 8
 9public sealed class AiChatEnrichmentBuilder
 10{
 11    private readonly IBitrixStockSyncArticleHistoryService _bitrixHistory;
 12    private readonly ApplicationDbContext _db;
 13    private readonly IWarehouseStockLookupService _warehouseLookup;
 14    private readonly OneCDataManager _oneCDataManager;
 15
 1916    public AiChatEnrichmentBuilder(
 1917        IBitrixStockSyncArticleHistoryService bitrixHistory,
 1918        ApplicationDbContext db,
 1919        IWarehouseStockLookupService warehouseLookup,
 1920        OneCDataManager oneCDataManager)
 21    {
 1922        _bitrixHistory    = bitrixHistory;
 1923        _db               = db;
 1924        _warehouseLookup  = warehouseLookup;
 1925        _oneCDataManager  = oneCDataManager;
 1926    }
 27
 28    public async Task<AiChatEnrichmentDto?> BuildForArticleAsync(
 29        string article,
 30        string? manufacturer,
 31        string? productImageUrl = null,
 32        string? productName = null,
 33        CancellationToken ct = default)
 34    {
 1935        article = article.Trim();
 1836        if (string.IsNullOrWhiteSpace(article))
 337            return null;
 38
 1539        var page = await _bitrixHistory.GetAllAsync(article, maxReports: 15, ct);
 1540        var visual = await ResolveProductVisualAsync(article, productImageUrl, productName, ct);
 41
 1542        return new AiChatEnrichmentDto
 1543        {
 1544            Article         = article,
 1545            ProductImageUrl = visual.ImageUrl,
 1546            ProductName     = visual.ProductName,
 1547            BitrixSync  = BitrixStockSyncUiMapper.Map(page),
 1548            StockTable  = await BuildStockTableAsync(article, manufacturer, ct),
 1549            MovementMap = await BuildMovementMapAsync(article, ct)
 1550        };
 1851    }
 52
 53    private async Task<(string? ImageUrl, string? ProductName)> ResolveProductVisualAsync(
 54        string article,
 55        string? imageUrl,
 56        string? productName,
 57        CancellationToken ct)
 58    {
 1559        if (!string.IsNullOrWhiteSpace(imageUrl))
 160            return (imageUrl.Trim(), productName);
 61
 1462        var fromProduct = await _db.Products
 1463            .AsNoTracking()
 1464            .Where(p => p.Article == article && p.ImageUrl != null && p.ImageUrl != "")
 1465            .OrderByDescending(p => p.Id)
 1466            .Select(p => p.ImageUrl)
 1467            .FirstOrDefaultAsync(ct);
 68
 1469        if (!string.IsNullOrWhiteSpace(fromProduct))
 70        {
 371            productName ??= await _db.WarehouseStockSnapshots
 372                .AsNoTracking()
 373                .Where(x => x.Article == article && x.ProductName != null && x.ProductName != "")
 374                .Select(x => x.ProductName)
 375                .FirstOrDefaultAsync(ct);
 376            return (fromProduct, productName);
 77        }
 78
 1179        return (null, productName);
 1580    }
 81
 82    private async Task<AiWarehouseStockUiDto?> BuildStockTableAsync(
 83        string article,
 84        string? manufacturer,
 85        CancellationToken ct)
 86    {
 1587        var lookup = await _warehouseLookup.LookupAsync(article, manufacturer, ct);
 1588        var rows = await _db.WarehouseStockSnapshots
 1589            .AsNoTracking()
 1590            .Where(x => x.Article != null && x.Article.Contains(article))
 1591            .OrderBy(x => x.Article)
 1592            .Take(12)
 1593            .ToListAsync(ct);
 94
 1595        if (rows.Count == 0)
 96        {
 1197            if (lookup is not { Found: true })
 1098                return null;
 99
 1100            return new AiWarehouseStockUiDto
 1101            {
 1102                Article = article,
 1103                Rows =
 1104                [
 1105                    new AiWarehouseStockRowUiDto
 1106                    {
 1107                        Article      = article,
 1108                        C0           = lookup.C0,
 1109                        M2Qty        = lookup.M2Qty,
 1110                        M3Qty        = lookup.M3Qty,
 1111                        B1Qty        = lookup.B1Qty,
 1112                        B2Qty        = lookup.B2Qty
 1113                    }
 1114                ]
 1115            };
 116        }
 117
 4118        return new AiWarehouseStockUiDto
 4119        {
 4120            Article = article,
 15121            Rows = rows.Select(r => new AiWarehouseStockRowUiDto
 15122            {
 15123                Article      = r.Article ?? article,
 15124                Manufacturer = r.Manufacturer,
 15125                ProductName  = r.ProductName,
 15126                M2Qty        = r.M2Qty,
 15127                M3Qty        = r.M3Qty,
 15128                B1Qty        = r.B1Qty,
 15129                B2Qty        = r.B2Qty,
 15130                C0           = r.C0,
 15131                SaleFlag     = r.SaleFlag,
 15132                SyncedAtMsk  = BitrixStockMskDateTime.Format(r.SyncedAtUtc)
 15133            }).ToList()
 4134        };
 15135    }
 136
 137    private async Task<AiMovementMapUiDto?> BuildMovementMapAsync(string article, CancellationToken ct)
 138    {
 139        try
 140        {
 15141            var map = await _oneCDataManager.GetMovementMapAsync(article);
 14142            if (map == null)
 13143                return null;
 144
 1145            return new AiMovementMapUiDto
 1146            {
 1147                Article          = map.Article ?? article,
 1148                NomenclatureName = map.NomenclatureName ?? article,
 1149                Balances = (map.CurrentBalance ?? []).Select(b => new AiMovementBalanceUiDto
 1150                {
 1151                    Warehouse = b.Warehouse ?? "—",
 1152                    Quantity  = b.Quantity
 1153                }).ToList(),
 1154                Items = (map.Movements ?? [])
 3155                    .Where(m => !m.IsDeleted)
 2156                    .OrderByDescending(m => m.Date)
 1157                    .Take(10)
 2158                    .Select(m => new AiMovementItemUiDto
 2159                    {
 2160                        DateMsk = m.Date.ToString("dd.MM.yyyy HH:mm"),
 2161                        Kind    = m.Kind.ToString().ToLowerInvariant(),
 2162                        DocType = m.DocType,
 2163                        DocNumber = m.DocNumber,
 2164                        Quantity  = m.Quantity,
 2165                        Price     = m.Price,
 2166                        WarehouseFrom = m.WarehouseFrom,
 2167                        WarehouseTo   = m.WarehouseTo
 2168                    }).ToList()
 1169            };
 170        }
 1171        catch
 172        {
 1173            return null;
 174        }
 15175    }
 176}