diff --git a/docs/pos-progress.md b/docs/pos-progress.md index b12a37a..f38af42 100644 --- a/docs/pos-progress.md +++ b/docs/pos-progress.md @@ -51,8 +51,8 @@ 5. [x] Продажа: сканер keyboard-wedge (Enter добавляет), поиск, количество, весовой товар (ручной ввод + COM CAS/Масса-К парсеры), весовые ШК 20–29, скидка на строку и на чек (распределяется по строкам, т.к. сервер понимает только построчную), удаление строки (Del), - нал/карта/смешанная + сдача, горячие клавиши F1/F2/F7/F9/F10/F12/Esc/Del, без мыши. - (Отложенный чек — заготовка; базовый сценарий «скан→оплата→печать→очередь» работает и покрыт тестом.) + нал/карта/смешанная + сдача, отложенный чек (park/resume, независимый снимок корзины), + горячие клавиши F1/F2/F7/F9/F10/F12/Esc/Del, без мыши. Сценарий «скан→оплата→печать→очередь» покрыт тестом. 6. [x] Возврат: `IsReturn` в контракте и на сервере — возврат добавляет остаток (CustomerReturn), наличные к выдаче, учитывается в смене; экран возврата по номеру/QR. Без чека — по праву админа (флаг в чеке). Частичный — по позициям. (Полный интерактивный выбор позиций — экран-заготовка.) @@ -106,8 +106,8 @@ global.json пинит 8.0.417, он не стоит локально). Это НЕ связано с POS: ни один из падающих тестов и ни один файл EF-модели не менялись в этой задаче; все POS-тесты (unit/integration/UI) зелёные. - Фискализация ОФД KZ — только заглушка (по заданию). -- Отложенный чек и полный интерактивный выбор позиций возврата — экраны-заготовки; серверная и - доменная части (возврат, привязка к смене) реализованы и покрыты тестами. +- Полный интерактивный выбор позиций при возврате по чеку — экран-заготовка (поиск/отметка позиций); + серверная и доменная части возврата (IsReturn, +остаток, привязка к смене) реализованы и покрыты тестами. ## Коммиты (добавляются при коммите — тематические, в main, attribution Co-Authored-By: Claude Opus) diff --git a/docs/pos-screenshots/03-sale-empty-1366x768.png b/docs/pos-screenshots/03-sale-empty-1366x768.png index 0806a27..64ab705 100644 Binary files a/docs/pos-screenshots/03-sale-empty-1366x768.png and b/docs/pos-screenshots/03-sale-empty-1366x768.png differ diff --git a/docs/pos-screenshots/03-sale-empty-1920x1080.png b/docs/pos-screenshots/03-sale-empty-1920x1080.png index d0a9a9d..998492e 100644 Binary files a/docs/pos-screenshots/03-sale-empty-1920x1080.png and b/docs/pos-screenshots/03-sale-empty-1920x1080.png differ diff --git a/docs/pos-screenshots/04-sale-items-1366x768.png b/docs/pos-screenshots/04-sale-items-1366x768.png index 1a093c9..141a989 100644 Binary files a/docs/pos-screenshots/04-sale-items-1366x768.png and b/docs/pos-screenshots/04-sale-items-1366x768.png differ diff --git a/docs/pos-screenshots/04-sale-items-1920x1080.png b/docs/pos-screenshots/04-sale-items-1920x1080.png index e08f70c..d7adf6d 100644 Binary files a/docs/pos-screenshots/04-sale-items-1920x1080.png and b/docs/pos-screenshots/04-sale-items-1920x1080.png differ diff --git a/docs/pos-screenshots/09-shell-offline-1366x768.png b/docs/pos-screenshots/09-shell-offline-1366x768.png index f9aa5d0..9dcbc22 100644 Binary files a/docs/pos-screenshots/09-shell-offline-1366x768.png and b/docs/pos-screenshots/09-shell-offline-1366x768.png differ diff --git a/docs/pos-screenshots/09-shell-offline-1920x1080.png b/docs/pos-screenshots/09-shell-offline-1920x1080.png index da480c3..d8670f2 100644 Binary files a/docs/pos-screenshots/09-shell-offline-1920x1080.png and b/docs/pos-screenshots/09-shell-offline-1920x1080.png differ diff --git a/docs/pos-screenshots/10-shell-online-1366x768.png b/docs/pos-screenshots/10-shell-online-1366x768.png index abf3f36..db7d823 100644 Binary files a/docs/pos-screenshots/10-shell-online-1366x768.png and b/docs/pos-screenshots/10-shell-online-1366x768.png differ diff --git a/docs/pos-screenshots/10-shell-online-1920x1080.png b/docs/pos-screenshots/10-shell-online-1920x1080.png index 2d734c4..7b982b7 100644 Binary files a/docs/pos-screenshots/10-shell-online-1920x1080.png and b/docs/pos-screenshots/10-shell-online-1920x1080.png differ diff --git a/docs/pos.md b/docs/pos.md index 46272a2..c262563 100644 --- a/docs/pos.md +++ b/docs/pos.md @@ -51,7 +51,8 @@ Linux x64. Быстрая, без лишнего; офлайн-режим; се штрихкод с префиксом 20–29 (код + вес/цена) распознаётся автоматически. - Скидка на позицию и на чек (по праву). - Оплата: наличные / карта / смешанная; показывается сдача. -- Отложенный чек — отложить и вернуться. +- Отложенный чек — кнопка «Отложить» сохраняет текущий чек и открывает новый + (обслужить другого покупателя), «Вернуть» — восстанавливает отложенный. ## Возврат - По чеку (номер или скан QR) — частичный или полный. diff --git a/src/food-market.pos.core/Logic/Cart.cs b/src/food-market.pos.core/Logic/Cart.cs index 1bcb7fc..703afe0 100644 --- a/src/food-market.pos.core/Logic/Cart.cs +++ b/src/food-market.pos.core/Logic/Cart.cs @@ -75,4 +75,24 @@ public CartTotals Totals() } public bool IsEmpty => _lines.Count == 0; + + /// Снимок чека для отложенного чека (park/resume). + public CartSnapshot Snapshot() => new(_lines.Select(Clone).ToList(), CartDiscount); + + /// Восстановить отложенный чек поверх текущего (замена содержимого). + public void Restore(CartSnapshot snapshot) + { + _lines.Clear(); + _lines.AddRange(snapshot.Lines.Select(Clone)); + CartDiscount = snapshot.CartDiscount; + } + + private static CartLine Clone(CartLine l) => new() + { + ProductId = l.ProductId, Name = l.Name, UnitCode = l.UnitCode, IsWeighed = l.IsWeighed, + UnitPrice = l.UnitPrice, Quantity = l.Quantity, Discount = l.Discount, VatPercent = l.VatPercent, + }; } + +/// Отложенный чек — независимая копия строк корзины и скидки на чек. +public sealed record CartSnapshot(IReadOnlyList Lines, decimal CartDiscount); diff --git a/src/food-market.pos/ViewModels/ViewModels.cs b/src/food-market.pos/ViewModels/ViewModels.cs index 1111155..2bbad76 100644 --- a/src/food-market.pos/ViewModels/ViewModels.cs +++ b/src/food-market.pos/ViewModels/ViewModels.cs @@ -214,6 +214,7 @@ public partial class SaleViewModel : ViewModelBase { private readonly AppServices? _svc; private readonly Cart _cart = new(); + private readonly Stack _parked = new(); public ObservableCollection Lines { get; } = new(); [ObservableProperty] private string _scanInput = ""; @@ -319,6 +320,40 @@ public async Task CommitPaymentAsync(decimal cash, decimal card, decimal change) [RelayCommand] private void Clear() { _cart.Clear(); Lines.Clear(); SelectedLine = null; Recalc(); Message = "Чек очищен"; } + public int ParkedCount => _parked.Count; + + /// Отложить текущий чек и начать новый (обслужить другого покупателя). + [RelayCommand] + private void Park() + { + if (_cart.IsEmpty) { Message = "Нечего откладывать"; return; } + _parked.Push(_cart.Snapshot()); + _cart.Clear(); Lines.Clear(); SelectedLine = null; Recalc(); + OnPropertyChanged(nameof(ParkedCount)); + Message = $"Чек отложен (отложенных: {_parked.Count})"; + } + + /// Вернуть последний отложенный чек (поверх пустого). + [RelayCommand] + private void Resume() + { + if (_parked.Count == 0) { Message = "Нет отложенных чеков"; return; } + if (!_cart.IsEmpty) { Message = "Сначала завершите или отложите текущий чек"; return; } + _cart.Restore(_parked.Pop()); + RebuildLines(); + OnPropertyChanged(nameof(ParkedCount)); + Message = "Отложенный чек возвращён"; + } + + private void RebuildLines() + { + Lines.Clear(); + foreach (var l in _cart.Lines) + Lines.Insert(0, new SaleLineVm { ProductId = l.ProductId, Name = l.Name, Unit = l.UnitCode, Price = l.UnitPrice, Quantity = l.Quantity, Discount = l.Discount, Model = l }); + SelectedLine = Lines.FirstOrDefault(); + Recalc(); + } + /// Пример заполненного чека для скриншотов/демо. public static SaleViewModel Sample() { diff --git a/src/food-market.pos/Views/SaleView.axaml b/src/food-market.pos/Views/SaleView.axaml index cf93750..0163342 100644 --- a/src/food-market.pos/Views/SaleView.axaml +++ b/src/food-market.pos/Views/SaleView.axaml @@ -6,13 +6,15 @@ - + +