diff --git a/src/food-market.infrastructure/Integrations/MoySklad/MoySkladDocumentImportService.cs b/src/food-market.infrastructure/Integrations/MoySklad/MoySkladDocumentImportService.cs index f01eb4a..a7c554a 100644 --- a/src/food-market.infrastructure/Integrations/MoySklad/MoySkladDocumentImportService.cs +++ b/src/food-market.infrastructure/Integrations/MoySklad/MoySkladDocumentImportService.cs @@ -71,7 +71,6 @@ private static DateTime MomentToUtc(string? moment) var done = (await _db.Supplies.Where(s => s.MoySkladExternalId != null) .Select(s => s.MoySkladExternalId!).ToListAsync(ct)).ToHashSet(); - var batch = 0; // expand=positions (НЕ positions.assortment — это раскрывает весь товар и делает // страницу гигантской/зависающей). assortment.meta.href в позиции есть и так — из него // берём id товара. @@ -102,15 +101,25 @@ private static DateTime MomentToUtc(string? moment) PostedAt = posted ? momentUtc : null, PostedByUserId = posted ? adminUserId : null, }; + // Агрегируем позиции по товару (один и тот же товар в документе — суммируем + // кол-во, цена — средневзвешенная): иначе два движения по одному товару дадут + // дубликат строки stocks в рамках одного SaveChanges. + var byProduct = new Dictionary(); foreach (var pos in d.Positions?.Rows ?? new()) { var prodMs = IdFromHref(pos.Assortment?.Meta?.Href); - if (prodMs is null || !products.TryGetValue(prodMs, out var pid)) continue; // услуга/неизвестный — пропуск позиции + if (prodMs is null || !products.TryGetValue(prodMs, out var pid)) continue; // услуга/неизвестный var up = pos.Price / 100m; + var cur = byProduct.GetValueOrDefault(pid); + byProduct[pid] = (cur.Qty + pos.Quantity, cur.Amount + pos.Quantity * up); + } + foreach (var (pid, agg) in byProduct) + { + var unit = agg.Qty != 0 ? decimal.Round(agg.Amount / agg.Qty, 4) : 0m; supply.Lines.Add(new foodmarket.Domain.Purchases.SupplyLine { - OrganizationId = orgId, ProductId = pid, Quantity = pos.Quantity, UnitPrice = up, - LineTotal = decimal.Round(pos.Quantity * up, 2), + OrganizationId = orgId, ProductId = pid, Quantity = agg.Qty, UnitPrice = unit, + LineTotal = decimal.Round(agg.Amount, 2), }); } _db.Supplies.Add(supply); @@ -123,9 +132,12 @@ private static DateTime MomentToUtc(string? moment) DocumentNumber: supply.Number, UnitCost: l.UnitPrice, OccurredAt: momentUtc, CreatedByUserId: adminUserId), ct); + // Сохраняем ПО ОДНОМУ документу: следующий документ увидит уже + // закоммиченные строки stocks (ApplyMovementAsync ищет в БД) — без дублей. + await _db.SaveChangesAsync(ct); + _db.ChangeTracker.Clear(); // не копим tracked-граф на 7k документов done.Add(d.Id); progress.Created++; - if (++batch >= 50) { await _db.SaveChangesAsync(ct); batch = 0; } } catch (Exception ex) { @@ -134,7 +146,6 @@ private static DateTime MomentToUtc(string? moment) _log.LogWarning(ex, "supply import failed {Id}", d.Id); } } - if (batch > 0) await _db.SaveChangesAsync(ct); return progress; }