food-market/src/food-market.infrastructure
nns ea29ab63c7 feat(returns): возврат от покупателя (CustomerReturn) (P1-6)
Расширение RetailSale: IsReturn (bool) + ReferenceSaleId (Guid?, self-FK) +
RetailSaleLine.QtyReturned (агрегация для защиты от over-return).
Миграция Phase6e_RetailSaleReturns (IF NOT EXISTS, можно гонять на стейдже).

Контроллер api/sales/retail:
• Create принимает isReturn/referenceSaleId; для возврата с reference —
  валидация что reference существует, не сам возврат, и проведён.
• POST /{id}/create-return — создаёт Draft-возврат от проведённого чека,
  копируя строки с qty = (Quantity - QtyReturned).
• Post с IsReturn=true идёт через PostReturnAsync: проверяет лимит
  возврата по reference (Quantity - QtyReturned), создаёт StockMovement
  тип CustomerReturn с +Quantity, инкрементит QtyReturned на источнике.
• Unpost для возврата зеркально откатывает.
• Запрещён unpost исходного чека пока есть проведённые возвраты на него.

Web: кнопка «Создать возврат» на странице проведённой продажи. DTO
расширены полями isReturn/referenceSale*/qtyReturned.

Тесты: 3 интеграционных (полный цикл sale→create-return→post,
standalone-return без reference, блокировка unpost при активных возвратах).
Полный прогон: 27 зелёных интеграционных, 23 зелёных unit.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 09:51:04 +05:00
..
Email feat(platform): IEmailSender + MailKit + PlatformSettingsController 2026-05-06 12:39:18 +05:00
Identity feat(roles): три системные роли — Admin/Cashier/Storekeeper 2026-05-06 11:31:15 +05:00
Integrations/MoySklad feat(phase3b): drop IsActive, add ShelfLifeDays, restore PriceType IsSystem/IsRequired 2026-04-25 22:46:34 +05:00
Inventory phase2a: stock foundation (Stock + StockMovement) + OtherSystem counterparty import 2026-04-22 00:51:07 +05:00
Persistence feat(returns): возврат от покупателя (CustomerReturn) (P1-6) 2026-05-28 09:51:04 +05:00
food-market.infrastructure.csproj feat(platform): IEmailSender + MailKit + PlatformSettingsController 2026-05-06 12:39:18 +05:00