diff --git a/docs/migration-progress.md b/docs/migration-progress.md
index c042ec1..173ab47 100644
--- a/docs/migration-progress.md
+++ b/docs/migration-progress.md
@@ -90,8 +90,9 @@ Part 1 (инвентаризация) и Part 3 (орг) — выполнены.
дублем ШК (MergeByBarcode=false, dupeInInput не пропускает, артикул-дубль дизамбигуируется
суффиксом msId). Перенос вернул **49 ранее слитых карточек** (каталог 30993 = МойСклад). Дубли
ШК видны в «Проверке данных» → dup-barcode (чек уже есть). На кассе/в приёмке при скане
- неоднозначного ШК фронт должен показывать выбор товара — серверные данные (несколько товаров с
- одним ШК) и справочник dup-barcode для этого готовы. **Остатки пересчитаны**: см. раздел сверки —
+ неоднозначного ШК показывается ОКНО ВЫБОРА товара: сервер `by-barcode` отдаёт список совпадений
+ (цена/остаток), веб-приёмка (`SupplyLineQuickAdd`) и касса (POS `SaleView`/`FindAllByBarcode`) —
+ показывают выбор. **Остатки пересчитаны**: см. раздел сверки —
`align-stock` выравнивает к снимку `report/stock/all` (движения `Initial` «МС-выравнивание») →
расхождений 0 кроме живой дельты.
2. **Долги — на уровне КОНТРАГЕНТА** (SupplierDebts переписан): Σприёмок − Σвозвратов − Σисходящих
diff --git a/src/food-market.pos.core/Data/PosStore.cs b/src/food-market.pos.core/Data/PosStore.cs
index 2c855d1..11a84ef 100644
--- a/src/food-market.pos.core/Data/PosStore.cs
+++ b/src/food-market.pos.core/Data/PosStore.cs
@@ -173,6 +173,21 @@ public int ProductCount()
return r.Read() ? Map(r) : null;
}
+ /// Все товары с таким штрихкодом (ШК не уникален — «минимум как МойСклад»).
+ /// Если вернулось >1 — касса показывает окно выбора товара.
+ public IReadOnlyList FindAllByBarcode(string code)
+ {
+ var lookup = OnlyDigitsOrRaw(code);
+ using var cmd = _cn.CreateCommand();
+ cmd.CommandText = @"SELECT p.* FROM products p JOIN barcodes b ON b.ProductId=p.Id
+ WHERE b.Code=$c AND p.IsArchived=0";
+ Bind(cmd, "$c", lookup);
+ using var r = cmd.ExecuteReader();
+ var list = new List();
+ while (r.Read()) list.Add(Map(r));
+ return list;
+ }
+
/// Избранные товары для плиток «быстрых товаров» на экране продажи.
public IReadOnlyList GetFavorites(int limit = 12)
{
diff --git a/src/food-market.pos/ViewModels/ViewModels.cs b/src/food-market.pos/ViewModels/ViewModels.cs
index cc61cc2..61039fa 100644
--- a/src/food-market.pos/ViewModels/ViewModels.cs
+++ b/src/food-market.pos/ViewModels/ViewModels.cs
@@ -531,6 +531,12 @@ public partial class SaleViewModel : ViewModelBase
[ObservableProperty] private bool _favoritesOpen = true;
public bool HasFavorites => Favorites.Count > 0;
+ // Выбор товара при сканировании ШК, который есть у НЕСКОЛЬКИХ карточек
+ // («минимум как МойСклад»: дубли ШК допустимы). Касса показывает окно выбора.
+ public ObservableCollection ScanCandidates { get; } = new();
+ [ObservableProperty] private bool _isScanPickerOpen;
+ [ObservableProperty] private string _scanPickerCode = "";
+
[ObservableProperty] private string _scanInput = "";
[ObservableProperty] private string _shiftNumber = "С-2026-000001";
[ObservableProperty] private string _message = "Отсканируйте товар или введите штрихкод";
@@ -677,8 +683,21 @@ private async Task Scan()
if (raw.Length == 0) return;
// Разбор этикеток по настройкам организации (по умолчанию ВЫКЛ → точный код).
var parsed = BarcodeParser.Parse(raw, _svc?.WeightedSettings);
+ // ШК может быть у НЕСКОЛЬКИХ карточек (дубли допустимы). Если так — окно выбора.
+ var matches = _svc?.Store.FindAllByBarcode(raw.Trim());
+ if (matches is null || matches.Count == 0) matches = _svc?.Store.FindAllByBarcode(parsed.Lookup);
+ if (matches is { Count: > 1 })
+ {
+ ScanCandidates.Clear();
+ foreach (var m in matches) ScanCandidates.Add(m);
+ ScanPickerCode = parsed.Lookup;
+ IsScanPickerOpen = true;
+ Message = $"Штрихкод {parsed.Lookup}: несколько товаров — выберите";
+ return;
+ }
// Сначала — точное совпадение штрихкода; этикеточный PLU как запасной путь.
- var p = _svc?.Store.FindByBarcode(raw.Trim())
+ var p = (matches is { Count: 1 } ? matches[0] : null)
+ ?? _svc?.Store.FindByBarcode(raw.Trim())
?? _svc?.Store.FindByBarcode(parsed.Lookup);
if (p is null)
{
@@ -708,6 +727,17 @@ private async Task Scan()
[RelayCommand] private void QuickCreateCancel() { IsQuickCreateOpen = false; QuickCreateName = ""; QuickCreatePrice = null; }
+ /// Выбор товара из окна выбора (ШК у нескольких карточек).
+ [RelayCommand] private async Task PickScanCandidate(PosProduct? p)
+ {
+ IsScanPickerOpen = false; ScanCandidates.Clear();
+ if (p is null) return;
+ if (p.IsWeighed) { await AddWeighedAsync(p); return; }
+ AddProduct(p, 1m);
+ }
+
+ [RelayCommand] private void ScanPickerCancel() { IsScanPickerOpen = false; ScanCandidates.Clear(); }
+
/// Создать товар по неизвестному штрихкоду на сервере и сразу добавить в чек.
[RelayCommand]
private async Task QuickCreateConfirmAsync()
diff --git a/src/food-market.pos/Views/SaleView.axaml b/src/food-market.pos/Views/SaleView.axaml
index 19b42df..9060de8 100644
--- a/src/food-market.pos/Views/SaleView.axaml
+++ b/src/food-market.pos/Views/SaleView.axaml
@@ -238,6 +238,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+