fix(migration): импорт переносит дубль-ШК и дубль-артикул карточки (дизамбиг артикула, пары товар-ШК)
Some checks are pending
Auto-tag / Create date-tag (push) Waiting to run
CI / Backend (.NET 8) (push) Waiting to run
CI / Web (React + Vite) (push) Waiting to run
CI / POS (WPF, Windows) (push) Waiting to run
Docker API / Build + push API (push) Waiting to run
Docker API / Deploy API on stage (push) Blocked by required conditions
Some checks are pending
Auto-tag / Create date-tag (push) Waiting to run
CI / Backend (.NET 8) (push) Waiting to run
CI / Web (React + Vite) (push) Waiting to run
CI / POS (WPF, Windows) (push) Waiting to run
Docker API / Build + push API (push) Waiting to run
Docker API / Deploy API on stage (push) Blocked by required conditions
- create: артикул-коллизия → суффикс msId (перенести карточку, не падать на unique) - create/update: ШК не уникален → добавляем код каждой карточке (пары товар|код против повторов), дубли ловит «Проверка данных» Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
b1bdf5c228
commit
150947c5fb
|
|
@ -317,11 +317,21 @@ await foreach (var c in _client.StreamCounterpartiesAsync(token, ct))
|
|||
.ToDictionaryAsync(p => p.Article!, p => p, StringComparer.OrdinalIgnoreCase, ct);
|
||||
|
||||
// Barcode → Product id (ограничиваем tenant'ом). Нужен для merge-логики.
|
||||
var existingProductIdByBarcode = await _db.ProductBarcodes
|
||||
// Код НЕ уникален (Phase44), поэтому при коллизии оставляем ПЕРВОГО владельца
|
||||
// (для merge-by-barcode legacy). Для «одна карточка — свой ШК» используем
|
||||
// ownBarcodePairs ниже.
|
||||
var existingProductIdByBarcode = (await _db.ProductBarcodes
|
||||
.Where(b => b.OrganizationId == orgId)
|
||||
.ToDictionaryAsync(b => b.Code, b => b.ProductId, ct);
|
||||
.Select(b => new { b.Code, b.ProductId }).ToListAsync(ct))
|
||||
.GroupBy(x => x.Code).ToDictionary(g => g.Key, g => g.First().ProductId);
|
||||
|
||||
var existingBarcodeSet = new HashSet<string>(existingProductIdByBarcode.Keys);
|
||||
// Пары (товар, код) — чтобы один и тот же код можно было добавить РАЗНЫМ товарам,
|
||||
// но не дублировать один код у одного товара при повторных прогонах.
|
||||
var ownBarcodePairs = new HashSet<(Guid, string)>(
|
||||
(await _db.ProductBarcodes.Where(b => b.OrganizationId == orgId)
|
||||
.Select(b => new { b.ProductId, b.Code }).ToListAsync(ct))
|
||||
.Select(x => (x.ProductId, x.Code)));
|
||||
|
||||
// Дедуп внутри входа: один и тот же barcode встречается у нескольких
|
||||
// товаров MoySklad → импортируем первый, остальные пропускаем в отчёт.
|
||||
|
|
@ -425,14 +435,17 @@ await foreach (var p in _client.StreamProductsAsync(token, ct, includeArchived:
|
|||
if (string.IsNullOrEmpty(existing.MoySkladExternalId))
|
||||
existing.MoySkladExternalId = p.Id;
|
||||
|
||||
// Добавляем новые штрихкоды, оставляя уже существующие как есть.
|
||||
// Добавляем ШК карточки, если у ЭТОГО товара его ещё нет (дубль кода у
|
||||
// РАЗНЫХ товаров допустим — Phase44). Так баркод-дубль карточки получает
|
||||
// свой штрихкод, а повторный прогон не плодит дубли-строки.
|
||||
foreach (var b in barcodes)
|
||||
{
|
||||
if (existingBarcodeSet.Contains(b.Code)) continue;
|
||||
if (ownBarcodePairs.Contains((existing.Id, b.Code))) continue;
|
||||
b.OrganizationId = orgId;
|
||||
existing.Barcodes.Add(b);
|
||||
existingBarcodeSet.Add(b.Code);
|
||||
existingProductIdByBarcode[b.Code] = existing.Id;
|
||||
ownBarcodePairs.Add((existing.Id, b.Code));
|
||||
existingProductIdByBarcode.TryAdd(b.Code, existing.Id);
|
||||
}
|
||||
|
||||
// Прайс retail: если у существующего товара нет цены этого типа —
|
||||
|
|
@ -468,11 +481,17 @@ await foreach (var p in _client.StreamProductsAsync(token, ct, includeArchived:
|
|||
}
|
||||
else
|
||||
{
|
||||
// Артикул уникален в орг (индекс). Если он уже занят ДРУГОЙ карточкой —
|
||||
// дизамбигуируем суффиксом msId, чтобы перенести карточку отдельно
|
||||
// (а не падать на уникальности и не схлопывать её).
|
||||
var uniqueArticle = article;
|
||||
if (!string.IsNullOrWhiteSpace(uniqueArticle) && existingByArticle.ContainsKey(uniqueArticle))
|
||||
uniqueArticle = Trim(article, 480) + "·" + (p.Id ?? Guid.NewGuid().ToString("N"))[..8];
|
||||
var product = new Product
|
||||
{
|
||||
OrganizationId = orgId,
|
||||
Name = Trim(p.Name, 500) ?? "(без имени)",
|
||||
Article = Trim(article, 500),
|
||||
Article = Trim(uniqueArticle, 500),
|
||||
Description = p.Description,
|
||||
UnitOfMeasureId = uomId,
|
||||
Vat = vat,
|
||||
|
|
@ -497,14 +516,10 @@ await foreach (var p in _client.StreamProductsAsync(token, ct, includeArchived:
|
|||
});
|
||||
}
|
||||
|
||||
// ШК НЕ уникален (Phase44): переносим штрихкод КАЖДОЙ карточки, даже если
|
||||
// такой код уже у другого товара (дубль ловит «Проверка данных» → dup-barcode).
|
||||
foreach (var b in barcodes)
|
||||
{
|
||||
if (existingBarcodeSet.Contains(b.Code))
|
||||
{
|
||||
errors.Add($"{p.Name}: штрихкод {b.Code} уже занят, пропущен.");
|
||||
if (progress is not null) progress.Errors = errors;
|
||||
continue;
|
||||
}
|
||||
b.OrganizationId = orgId;
|
||||
product.Barcodes.Add(b);
|
||||
existingBarcodeSet.Add(b.Code);
|
||||
|
|
@ -512,7 +527,7 @@ await foreach (var p in _client.StreamProductsAsync(token, ct, includeArchived:
|
|||
|
||||
_db.Products.Add(product);
|
||||
if (p.Id is not null) existingByMsId[p.Id] = product;
|
||||
if (!string.IsNullOrWhiteSpace(article)) existingByArticle[article] = product;
|
||||
if (!string.IsNullOrWhiteSpace(product.Article)) existingByArticle[product.Article!] = product;
|
||||
foreach (var b in product.Barcodes) existingProductIdByBarcode[b.Code] = product.Id;
|
||||
created++;
|
||||
if (progress is not null) progress.Created = created;
|
||||
|
|
|
|||
Loading…
Reference in a new issue