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 @@
-
+
+
+
diff --git a/tests/food-market.UnitTests/PosCoreTests.cs b/tests/food-market.UnitTests/PosCoreTests.cs
index a6cd834..63e7a13 100644
--- a/tests/food-market.UnitTests/PosCoreTests.cs
+++ b/tests/food-market.UnitTests/PosCoreTests.cs
@@ -375,3 +375,31 @@ public class PosUpdateCheckTests
public void Version_comparison(string? local, string? remote, bool expected)
=> UpdateCheck.IsNewer(local, remote).Should().Be(expected);
}
+
+public class PosParkedCartTests
+{
+ private static PosProduct P(decimal price = 300m) => new()
+ { Id = Guid.NewGuid(), Name = "Товар", Barcodes = new[] { "1" }, UnitCode = "шт", Packaging = PosPackaging.Piece, VatPercent = 12m, VatEnabled = true, Price = price };
+
+ [Fact]
+ public void Snapshot_is_independent_copy()
+ {
+ var cart = new Cart();
+ var line = cart.AddProduct(P(300m), 2);
+ cart.SetCartDiscount(100m);
+ var snap = cart.Snapshot();
+
+ // Меняем исходную корзину — снимок не должен измениться.
+ cart.SetQuantity(line, 5m);
+ cart.Clear();
+ snap.Lines.Should().HaveCount(1);
+ snap.Lines[0].Quantity.Should().Be(2m);
+ snap.CartDiscount.Should().Be(100m);
+
+ // Восстановление возвращает отложенный чек.
+ cart.Restore(snap);
+ cart.Lines.Should().HaveCount(1);
+ cart.Lines[0].Quantity.Should().Be(2m);
+ cart.Totals().Total.Should().Be(2 * 300m - 100m);
+ }
+}