fix(migration): приёмки — сохранение по одному документу + агрегация позиций по товару
Батч из 50 приёмок с общим товаром давал дубль строки stocks (ApplyMovementAsync создавал новую строку, не видя ещё не сохранённую). Теперь: SaveChanges после КАЖДОГО документа (следующий видит закоммиченные остатки) + агрегация позиций по товару в документе (средневзвешенная цена) + ChangeTracker.Clear (не копим граф на 7k докумов). Идемпотентно по msId — повторный запуск продолжает с места. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
10cd9f989b
commit
6fdc9e527a
|
|
@ -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<Guid, (decimal Qty, decimal Amount)>();
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue