docs(sprint2): P1-16 done — все 7 пунктов выполнены, итог

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
nns 2026-05-28 10:07:53 +05:00
parent a74fa114d8
commit a7b82eea86

View file

@ -56,9 +56,62 @@ query filter. Stock-инвариант: после каждого Post/Unpost
что reference указывает на проведённую приёмку того же поставщика. Защита что reference указывает на проведённую приёмку того же поставщика. Защита
от ухода в минус. Permissions переиспользуют `SuppliesEdit/Post/Delete`. от ухода в минус. Permissions переиспользуют `SuppliesEdit/Post/Delete`.
Тесты: 4 интеграционных. Тесты: 4 интеграционных.
7. [ ] **P1-16 Hangfire dashboard + cleanup**`Hangfire.Dashboard` с 7. [x] **P1-16 Hangfire dashboard + cleanup**`Hangfire.Dashboard` с
авторизацией только для SuperAdmin. Scheduled: ежедневный cleanup авторизацией только для SuperAdmin. Scheduled: ежедневный cleanup
`StockMovement` старше 2 лет, audit-log старше 90 дней. `StockMovement` старше 2 лет, audit-log старше 90 дней.
`Hangfire.PostgreSql` storage на ConnectionStrings:Default. Сервер
стартует только если `Hangfire:Enabled=true` (по умолчанию). Dashboard
`/hangfire` гейтит `SuperAdminHangfireFilter`. Recurring: `prune-stock-movements`
(03:30 UTC, 730 дней) и `prune-audit-log` (03:45 UTC, 90 дней) —
`HousekeepingJobs` с `IgnoreQueryFilters` (межтенантно). Тесты:
1 unit + 1 интеграционный.
## Итог
**Все 7 пунктов выполнены.** Спринт 2 (складские документы P1) завершён 2026-05-28.
Сводка:
- **P1-1 Enter** — оприходование без поставщика (`/api/inventory/enters`, миграция
`Phase6a_Enters`); 4 интеграционных теста.
- **P1-2 Loss** — списание с enum `LossReason` (`/api/inventory/losses`,
`Phase6b_Losses`); 3 интеграционных.
- **P1-3 Transfer** — атомарное перемещение пара TransferOut + TransferIn
(`/api/inventory/transfers`, `Phase6c_Transfers`); 4 интеграционных, включая
проверку «после Post ровно 2 движения, после Unpost ровно 4».
- **P1-4 Inventory** — пересчёт с auto-load остатков (`/api/inventory/inventories`,
`Phase6d_Inventories`); 3 интеграционных, импорт CSV в UI.
- **P1-6 CustomerReturn**`RetailSale.IsReturn` + `ReferenceSaleId` +
`RetailSaleLine.QtyReturned`, эндпоинт `POST /create-return`
(`Phase6e_RetailSaleReturns`); 3 интеграционных.
- **P1-7 SupplierReturn** — зеркало Supply (`/api/purchases/supplier-returns`,
`Phase6f_SupplierReturns`); 4 интеграционных, валидация совпадения поставщика
при ссылке на приёмку.
- **P1-16 Hangfire**`Hangfire.PostgreSql` storage, dashboard `/hangfire`
с `SuperAdminHangfireFilter`, recurring jobs `prune-stock-movements` (730 дней)
и `prune-audit-log` (90 дней); 1 unit + 1 интеграционный.
**Сборка:** зелёная (`dotnet build src/food-market.api`).
**Тесты:** 24 unit + 32 integration = **56 зелёных**.
**Web:** `pnpm build` зелёный (5 новых пар list+edit страниц + расширение RetailSale).
### Новые таблицы
`enters`, `enter_lines`, `losses`, `loss_lines`, `transfers`, `transfer_lines`,
`inventories`, `inventory_lines`, `supplier_returns`, `supplier_return_lines`
+ колонки `retail_sales.IsReturn/ReferenceSaleId`, `retail_sale_lines.QtyReturned`.
### Новые permissions
`TransferEdit` добавлен в `RolePermissions` (Enter/Loss/Inventory/Supplies* —
переиспользованы существующие). `All()` обновлён.
### Stock-инвариант
Каждый документ при Post создаёт явные `StockMovement` через `IStockService`
в Serializable-транзакции. Post→Unpost — обратные движения тем же документ-id
(reversal-маркер в `DocumentType`). Проверка «не уйти в минус» на:
- Enter Unpost (товар уже мог быть продан),
- Loss Post (нельзя списать сверх остатка),
- Transfer Post (FromStore) и Unpost (ToStore),
- Inventory Unpost (излишек мог уйти),
- SupplierReturn Post (нельзя вернуть сверх остатка).
## Лог ## Лог