Чистая логика вынесена в Application для тестируемости и используется контроллерами: - MovingAverageCost.Compute (скользящее среднее себестоимости) ← SuppliesController.Post - RetailPaymentValidator.IsSufficient (достаточность оплаты) ← RetailSalesController.Post Тесты: - MovingAverageCost: первая приёмка, средневзвешенное, округление до 4 знаков, totalQty=0. - RetailPaymentValidator: ровно/переплата/недоплата, округление до 2 знаков. - StockService.ApplyMovement (SQLite in-memory): материализация Stock+движение, инкремент, отрицательное списание, throw без tenant. - Мультитенантный query-filter AppDbContext: tenant видит своё; чужой не видит; SuperAdmin без override — всё; с override — только выбранную оргу. Все 23 зелёные. EF8 SQLite поддерживает ToJson (EmployeeRole.Permissions). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
26 lines
1.5 KiB
C#
26 lines
1.5 KiB
C#
namespace foodmarket.Application.Inventory;
|
||
|
||
/// <summary>Скользящее средневзвешенное себестоимости товара при приёмке.
|
||
///
|
||
/// Себестоимость пересчитывается так, чтобы отражать средневзвешенную цену
|
||
/// всех закупленных единиц: <c>(остаток·текущаяСебестоимость + приход·ценаЗакупки)
|
||
/// / (остаток + приход)</c>. На первой приёмке (нет остатка и себестоимости)
|
||
/// берётся цена закупки. Результат округляется до 4 знаков.
|
||
///
|
||
/// Вынесено из <c>SuppliesController.Post</c> для юнит-тестируемости —
|
||
/// контроллер вызывает этот метод.</summary>
|
||
public static class MovingAverageCost
|
||
{
|
||
public static decimal Compute(
|
||
decimal currentQty, decimal currentCost, decimal incomingQty, decimal incomingUnitPrice)
|
||
{
|
||
var totalQty = currentQty + incomingQty;
|
||
// Порядок важен: && связывает крепче ||, т.е.
|
||
// (totalQty==0) || ((currentCost==0) && (currentQty==0)).
|
||
var newCost = totalQty == 0m || currentCost == 0m && currentQty == 0m
|
||
? incomingUnitPrice
|
||
: (currentQty * currentCost + incomingQty * incomingUnitPrice) / totalQty;
|
||
return Math.Round(newCost, 4, MidpointRounding.AwayFromZero);
|
||
}
|
||
}
|