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> 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> 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 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); }