Some checks are pending
Auto-tag / Create date-tag (push) Waiting to run
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
80 lines
8.5 KiB
Markdown
80 lines
8.5 KiB
Markdown
# Деньги смены → остатки счетов (прогресс)
|
||
|
||
Автономная задача владельца (08.10.2026, нашёл Mac-Claude на стенде). Деньги смены
|
||
(продажи нал/карта, возвраты, внесения/выплаты, расхождение) НЕ попадают в остаток
|
||
счёта. Делать ДО кассы — касса опирается на эти остатки.
|
||
|
||
## Факты со стенда (Демо после B)
|
||
- «Касса (e2e)»: нач. 50 000, текущий −98 812 (= 50 000 − 148 812 оплата приёмки нал).
|
||
Смена С-2026-000001: продажи нал 3 775, возвраты нал 1 500, внесение 10 000, выплата 3 000,
|
||
факт 14 775 — НИЧЕГО не попало в остаток счёта. Эквайринг: 0 при продажах картой 2 325.
|
||
- Оплата 148 812 нал при остатке 50 000 → касса в минус, никто не остановил.
|
||
- Инвариант теста «счёт ≡ начальный + Σ операций» прошёл только потому, что движения смены
|
||
не считаются операциями счёта — дыра в тесте.
|
||
|
||
## Текущая модель (как есть)
|
||
- Остаток счёта = `OpeningBalance + Σ(PaymentIn/CashIn/TransferIn) − Σ(PaymentOut/CashOut/TransferOut)`
|
||
по Posted `money_transactions` (MoneyAccountsController.List + MoneyReportsController.Balances).
|
||
- Смена (`CashShift`) хранит SalesCash/SalesCard/ReturnsCash/ReturnsCard/DrawerIn/DrawerOut/
|
||
ExpectedCash/ActualCash/CashDifference — считаются ПРИ ЗАКРЫТИИ из связанных retail_sales +
|
||
cash_drawer_operations. В `money_transactions` НЕ пишутся → в остаток счёта не входят.
|
||
|
||
## Выбранная модель (материализация движений смены при закрытии)
|
||
Единый реестр — `money_transactions`. При закрытии смены создаём Posted-транзакции на счета смены,
|
||
и существующая формула остатка автоматически их учитывает; отчёт «Движение» показывает их по типам.
|
||
- Касса смены (CashAccount): `+SalesCash` (Розничные продажи), `−ReturnsCash` (Возвраты),
|
||
`+DrawerIn` (Внесение), `−DrawerOut` (Выплата), `±CashDifference` (Излишек/Недостача).
|
||
- Эквайринг смены (AcquiringAccount): `+SalesCard` (Продажи картой), `−ReturnsCard` (Возвраты по карте).
|
||
- Размен (OpeningFloat) — НЕ движение (деньги уже в кассе). Итог: остаток кассы после закрытия =
|
||
старый остаток + (SalesCash−ReturnsCash+DrawerIn−DrawerOut+Diff) = ActualCash, если до смены на
|
||
кассе был только размен. Σ по типам = ActualCash − OpeningFloat.
|
||
Движения помечаем `Category` (enum) + `CashShiftId` (идемпотентность). Создаются только ненулевые.
|
||
|
||
## Отрицательный остаток
|
||
- Расход (CashOut/PaymentOut/TransferOut при post money-tx) и Выплата из смены (drawer Out) больше
|
||
доступных денег → 409 «На счёте «…» недостаточно денег: остаток X ₸, нужно Y ₸».
|
||
- Настройка орг `AllowNegativeBalance` (по умолч. выкл., менять только админ).
|
||
- Выплата из смены сверяется с ЖИВЫМ налом смены (OpeningFloat + SalesCash−ReturnsCash+DrawerIn−DrawerOut
|
||
на текущий момент), расход money-tx — с остатком счёта.
|
||
|
||
## План
|
||
1. [x] МойСклад reference → `docs/money-moysklad-reference.md`.
|
||
2. [x] Миграция `Phase34_ShiftMoneyMovements` (manual, idempotent): `money_transactions.Category`,
|
||
`.CashShiftId`, `organizations.AllowNegativeBalance`. Применилась на стенде (в history + колонки есть).
|
||
3. [x] `MoneyService.CloseShiftAsync` → `MaterializeShiftMovementsAsync` (Posted-движения по типам, link на смену, идемпотентно).
|
||
4. [x] Guard: `PostAsync` (расход > остатка счёта → InsufficientFunds 409) + `AddDrawerOperationAsync` (выплата > нала смены);
|
||
настройка `AllowNegativeBalance` в OrgSettings (PUT только Admin); InsufficientFunds→409 в MapResult/Drawer.
|
||
5. [x] Остатки/отчёты правильные автоматически (всё читает money_transactions); `Category` в DTO для типов.
|
||
6. [x] `docs/money.md` — раздел «Как деньги смены попадают в остатки» + «Отрицательный остаток».
|
||
7. [x] Остаток вычисляемый → пересчёт не нужен; новые смены материализуются при закрытии.
|
||
(Историч. закрытые смены прошлых тест-орг не бэкфиллим — чистятся prune'ом; демо пересоздан прогоном B.)
|
||
8. [x] full-chain: `indepBalance()` — независимый расчёт (нач + платежи + смены: касса += факт−размен,
|
||
эквайринг += картой−возвраты), сверяется с API /balances; шаг 8 — оплата ≤ остатка + отказ при превышении.
|
||
Unit (MoneyMathTests, +1 = 26 ✓) + integration (MoneyModuleTests, +2 = 10 ✓).
|
||
9. [x] deploy-stage; post-deploy C 16/16 зелёный; репро до деплоя — 4 шага красные (подтверждено).
|
||
10. [x] Коммиты в main + push origin main: `1e6d7fb` fix(money) · `005c4ac` test(money) · `0977c63` docs(money).
|
||
|
||
## Найденное/исправленное
|
||
1. **Движения смены не попадали в остаток счёта** (касса/эквайринг игнорировали продажи/возвраты/
|
||
внесения/выплаты/расхождение). Фикс: материализация при закрытии смены в money_transactions по типам.
|
||
2. **Касса уходила в минус без предупреждения** (расход больше остатка). Фикс: guard InsufficientFunds (409)
|
||
на расход money-tx и выплату из смены; настройка орг «Разрешить отрицательные остатки» (по умолч. выкл., Admin).
|
||
3. **Дыра в тесте**: инвариант сверял остаток с суммой из того же API. Фикс: независимый расчёт в тесте.
|
||
|
||
## Демо после фикса (прогон B, проверено руками)
|
||
- Касса: нач. 50 000 → текущий **31 275** (= 50 000 − 30 000 оплата приёмки нал
|
||
+ 9 775 смена С-001 (факт14775−размен5000) + 1 500 смена С-003). Раньше было −98 812.
|
||
- Касса-2: 3 000 → **4 550** (+1 550 смена С-004). Банк: 200 000 → **181 610** (−18 390 оплата №2).
|
||
- Эквайринг: 0 → **2 325** (продажи картой смены С-001). Раньше 0. Эквайринг-2: 0.
|
||
- Отрицательных остатков нет. 8 движений смен материализованы по типам (продажи нал/карта,
|
||
возвраты, внесение, выплата, излишек). Отказ при оплате больше остатка кассы — 409.
|
||
|
||
## Статус: ГОТОВО ✅
|
||
Движения смены попадают в остатки; отрицательный остаток запрещён (с настройкой). B 16/16 (36 скринов),
|
||
2× C подряд зелёные (16/16). unit 26 ✓, integration money 10 ✓.
|
||
|
||
## Журнал
|
||
- 08.10 — старт; модель (материализация при закрытии); миграция+код+guard+настройка+отчёт; docs.
|
||
Репро до деплоя: 4 шага красные. После деплоя: C 16/16 зелёный; unit 26 ✓, integration money 10 ✓.
|
||
Демо B зелёный, остатки сверены (см. выше). 2× C подряд зелёные.
|