Domain + миграция Phase3b_PricingCleanup: - DROP IsActive у products / product_groups / units_of_measure / counterparties / price_types (включая индекс IX_products_OrganizationId_IsActive). В этих сущностях концепт деактивации не оправдан — если товар/группа/единица/контрагент не нужны, их физически удаляют. - DROP organizations.MultiplePriceTypesEnabled — раздел «Типы цен» всегда виден, отдельной настройки больше не нужно. - ADD price_types.IsRequired bool default false — обязательность заполнения для каждого товара. - ADD price_types.IsSystem bool default false — защищённая запись, не удаляется и IsRequired всегда true; имя редактируется. В каждой организации гарантируется одна системная запись «Розничная цена» (создаётся миграцией если её нет). - ADD products.ShelfLifeDays integer NULL — срок годности. API: - ProductsController/UnitsOfMeasureController/ProductGroupsController/ CounterpartiesController/PriceTypesController: убраны параметры isActive в фильтрах, sort-keys, DTO, Apply, Создании. - Products проекция: вместо IsActive теперь ShelfLifeDays. - PriceTypesController: 400 при попытке удалить системную запись; IsRequired у системной — всегда true, не меняется через PUT. - recalc-retail / supply posting: дефолтный PriceType ищется по IsSystem → IsDefault → IsRetail → SortOrder → Name (без IsActive). - OrgSettingsDto/Input — без MultiplePriceTypesEnabled. Web: - types.ts: убраны isActive у Product/ProductGroup/UnitOfMeasure/ Counterparty/PriceType. PriceType пополнен isRequired/isSystem. Product получил shelfLifeDays. - useOrgSettings: убрано multiplePriceTypesEnabled. - AppLayout: меню «Типы цен» всегда видно. - Pages (Counterparties/Units/ProductGroups/PriceTypes/ProductEdit/ OrganizationSettings): сняты колонки/чекбоксы/поля «Активен»; удалён GroupMarkupsPage; в PriceTypesPage добавлен Lock-индикатор системной записи и блок-подсказка, кнопка удаления скрыта. - DemoCatalogSeeder и OtherSystem-импортёр: больше не пишут IsActive. UI-перекомпоновка карточки товара (Phase3b пп.6/9), Supply Posted-toggle, PercentInput, ShelfLifeDays-фильтр и редизайн прайс-секции — отдельными коммитами далее по плану. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
117 lines
4.4 KiB
C#
117 lines
4.4 KiB
C#
using foodmarket.Application.Common.Tenancy;
|
|
using foodmarket.Infrastructure.Persistence;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace foodmarket.Api.Controllers.Organizations;
|
|
|
|
[ApiController]
|
|
[Authorize]
|
|
[Route("api/organization")]
|
|
public class OrganizationSettingsController : ControllerBase
|
|
{
|
|
private readonly AppDbContext _db;
|
|
private readonly ITenantContext _tenant;
|
|
|
|
public OrganizationSettingsController(AppDbContext db, ITenantContext tenant)
|
|
{
|
|
_db = db;
|
|
_tenant = tenant;
|
|
}
|
|
|
|
public record OrgSettingsDto(
|
|
Guid Id,
|
|
string Name,
|
|
string CountryCode,
|
|
Guid? DefaultCurrencyId,
|
|
string? DefaultCurrencyCode,
|
|
string? DefaultCurrencySymbol,
|
|
bool MultiCurrencyEnabled,
|
|
// VAT read-only: из страны организации (countries.VatRate). Источник правды — справочник стран.
|
|
decimal VatRate,
|
|
bool ShowVatEnabledOnProduct,
|
|
bool ShowServiceOnProduct,
|
|
bool ShowMarkedOnProduct,
|
|
bool ShowMinMaxStock,
|
|
bool AllowFractionalPrices,
|
|
bool ShowReferencePriceOnProduct);
|
|
|
|
// DefaultCurrencyId не принимается — он read-only, выводится из страны (Country.DefaultCurrencyId).
|
|
public record OrgSettingsInput(
|
|
string Name,
|
|
string CountryCode,
|
|
bool MultiCurrencyEnabled,
|
|
bool ShowVatEnabledOnProduct,
|
|
bool ShowServiceOnProduct,
|
|
bool ShowMarkedOnProduct,
|
|
bool ShowMinMaxStock,
|
|
bool AllowFractionalPrices,
|
|
bool ShowReferencePriceOnProduct);
|
|
|
|
[HttpGet("settings")]
|
|
public async Task<ActionResult<OrgSettingsDto>> Get(CancellationToken ct)
|
|
{
|
|
var orgId = _tenant.OrganizationId ?? throw new InvalidOperationException("No tenant.");
|
|
var o = await _db.Organizations
|
|
.Include(o => o.DefaultCurrency)
|
|
.FirstOrDefaultAsync(o => o.Id == orgId, ct);
|
|
if (o is null) return NotFound();
|
|
var vat = await ReadVatRateAsync(o.CountryCode, ct);
|
|
return Project(o, vat);
|
|
}
|
|
|
|
[HttpPut("settings"), Authorize(Roles = "Admin,Manager")]
|
|
public async Task<ActionResult<OrgSettingsDto>> Update([FromBody] OrgSettingsInput input, CancellationToken ct)
|
|
{
|
|
var orgId = _tenant.OrganizationId ?? throw new InvalidOperationException("No tenant.");
|
|
var o = await _db.Organizations
|
|
.Include(o => o.DefaultCurrency)
|
|
.FirstOrDefaultAsync(o => o.Id == orgId, ct);
|
|
if (o is null) return NotFound();
|
|
|
|
o.Name = input.Name;
|
|
o.CountryCode = input.CountryCode;
|
|
// Валюта организации жёстко следует за страной — не принимается от клиента.
|
|
o.DefaultCurrencyId = await _db.Countries
|
|
.Where(c => c.Code == input.CountryCode)
|
|
.Select(c => c.DefaultCurrencyId)
|
|
.FirstOrDefaultAsync(ct);
|
|
o.MultiCurrencyEnabled = input.MultiCurrencyEnabled;
|
|
o.ShowVatEnabledOnProduct = input.ShowVatEnabledOnProduct;
|
|
o.ShowServiceOnProduct = input.ShowServiceOnProduct;
|
|
o.ShowMarkedOnProduct = input.ShowMarkedOnProduct;
|
|
o.ShowMinMaxStock = input.ShowMinMaxStock;
|
|
o.AllowFractionalPrices = input.AllowFractionalPrices;
|
|
o.ShowReferencePriceOnProduct = input.ShowReferencePriceOnProduct;
|
|
await _db.SaveChangesAsync(ct);
|
|
|
|
await _db.Entry(o).Reference(x => x.DefaultCurrency).LoadAsync(ct);
|
|
var vat = await ReadVatRateAsync(o.CountryCode, ct);
|
|
return Project(o, vat);
|
|
}
|
|
|
|
private async Task<decimal> ReadVatRateAsync(string countryCode, CancellationToken ct)
|
|
{
|
|
var rate = await _db.Countries
|
|
.Where(c => c.Code == countryCode)
|
|
.Select(c => (decimal?)c.VatRate)
|
|
.FirstOrDefaultAsync(ct);
|
|
return rate ?? 0m;
|
|
}
|
|
|
|
private static OrgSettingsDto Project(foodmarket.Domain.Organizations.Organization o, decimal vat) => new(
|
|
o.Id, o.Name, o.CountryCode,
|
|
o.DefaultCurrencyId,
|
|
o.DefaultCurrency?.Code,
|
|
o.DefaultCurrency?.Symbol,
|
|
o.MultiCurrencyEnabled,
|
|
vat,
|
|
o.ShowVatEnabledOnProduct,
|
|
o.ShowServiceOnProduct,
|
|
o.ShowMarkedOnProduct,
|
|
o.ShowMinMaxStock,
|
|
o.AllowFractionalPrices,
|
|
o.ShowReferencePriceOnProduct);
|
|
}
|