Country — глобальный справочник (Entity, не TenantEntity), магазины- клиенты выбирают страны из готового списка но не управляют ими. Управление переносится в SuperAdmin консоль. Изменения: - API: POST/PUT /api/catalog/countries теперь Authorize(Roles=SuperAdmin) (раньше был SuperAdmin,Admin). DELETE и так был SuperAdmin. - GET остаётся [Authorize] без роли — нужен tenant'у для селектов в формах создания орги/контрагентов/товаров. - Tenant AppLayout: убран блок «Справочники» с пунктом «Страны». Иконка Globe больше не импортируется в tenant-меню. - Tenant роут /catalog/countries удалён из App.tsx. - В OrganizationSettingsPage ссылка «откройте справочник Страны» заменена на текст «обратитесь к администратору платформы». - SuperAdminLayout: новый блок «Справочники» с пунктом «Страны» (/super-admin/countries). Иконка Globe. - Роут /super-admin/countries использует существующий CountriesPage — компонент unchanged, страница теперь рендерится в SuperAdminLayout. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
103 lines
4.2 KiB
C#
103 lines
4.2 KiB
C#
using foodmarket.Application.Catalog;
|
|
using foodmarket.Application.Common;
|
|
using foodmarket.Domain.Catalog;
|
|
using foodmarket.Infrastructure.Persistence;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace foodmarket.Api.Controllers.Catalog;
|
|
|
|
[ApiController]
|
|
[Authorize]
|
|
[Route("api/catalog/countries")]
|
|
public class CountriesController : ControllerBase
|
|
{
|
|
private readonly AppDbContext _db;
|
|
|
|
public CountriesController(AppDbContext db) => _db = db;
|
|
|
|
[HttpGet]
|
|
public async Task<ActionResult<PagedResult<CountryDto>>> List([FromQuery] PagedRequest req, CancellationToken ct)
|
|
{
|
|
var q = _db.Countries.Include(c => c.DefaultCurrency).AsNoTracking().AsQueryable();
|
|
if (!string.IsNullOrWhiteSpace(req.Search))
|
|
{
|
|
var s = req.Search.Trim().ToLower();
|
|
q = q.Where(c => c.Name.ToLower().Contains(s) || c.Code.ToLower().Contains(s));
|
|
}
|
|
var total = await q.CountAsync(ct);
|
|
q = (req.Sort, req.Desc) switch
|
|
{
|
|
("code", false) => q.OrderBy(c => c.Code),
|
|
("code", true) => q.OrderByDescending(c => c.Code),
|
|
("currency", false) => q.OrderBy(c => c.DefaultCurrency != null ? c.DefaultCurrency.Code : null).ThenBy(c => c.Name),
|
|
("currency", true) => q.OrderByDescending(c => c.DefaultCurrency != null ? c.DefaultCurrency.Code : null).ThenBy(c => c.Name),
|
|
("vatRate", false) => q.OrderBy(c => c.VatRate).ThenBy(c => c.Name),
|
|
("vatRate", true) => q.OrderByDescending(c => c.VatRate).ThenBy(c => c.Name),
|
|
("name", true) => q.OrderByDescending(c => c.Name),
|
|
_ => q.OrderBy(c => c.Name),
|
|
};
|
|
var items = await q
|
|
.Skip(req.Skip).Take(req.Take)
|
|
.Select(c => new CountryDto(
|
|
c.Id, c.Code, c.Name,
|
|
c.DefaultCurrencyId,
|
|
c.DefaultCurrency != null ? c.DefaultCurrency.Code : null,
|
|
c.DefaultCurrency != null ? c.DefaultCurrency.Symbol : null,
|
|
c.VatRate))
|
|
.ToListAsync(ct);
|
|
return new PagedResult<CountryDto> { Items = items, Total = total, Page = req.Page, PageSize = req.Take };
|
|
}
|
|
|
|
[HttpGet("{id:guid}")]
|
|
public async Task<ActionResult<CountryDto>> Get(Guid id, CancellationToken ct)
|
|
{
|
|
var c = await _db.Countries.Include(x => x.DefaultCurrency).AsNoTracking().FirstOrDefaultAsync(x => x.Id == id, ct);
|
|
return c is null ? NotFound() : Project(c);
|
|
}
|
|
|
|
[HttpPost, Authorize(Roles = "SuperAdmin")]
|
|
public async Task<ActionResult<CountryDto>> Create([FromBody] CountryInput input, CancellationToken ct)
|
|
{
|
|
var e = new Country
|
|
{
|
|
Code = input.Code.Trim().ToUpper(),
|
|
Name = input.Name,
|
|
DefaultCurrencyId = input.DefaultCurrencyId,
|
|
VatRate = input.VatRate,
|
|
};
|
|
_db.Countries.Add(e);
|
|
await _db.SaveChangesAsync(ct);
|
|
await _db.Entry(e).Reference(x => x.DefaultCurrency).LoadAsync(ct);
|
|
return CreatedAtAction(nameof(Get), new { id = e.Id }, Project(e));
|
|
}
|
|
|
|
[HttpPut("{id:guid}"), Authorize(Roles = "SuperAdmin")]
|
|
public async Task<IActionResult> Update(Guid id, [FromBody] CountryInput input, CancellationToken ct)
|
|
{
|
|
var e = await _db.Countries.FirstOrDefaultAsync(x => x.Id == id, ct);
|
|
if (e is null) return NotFound();
|
|
e.Code = input.Code.Trim().ToUpper();
|
|
e.Name = input.Name;
|
|
e.DefaultCurrencyId = input.DefaultCurrencyId;
|
|
e.VatRate = input.VatRate;
|
|
await _db.SaveChangesAsync(ct);
|
|
return NoContent();
|
|
}
|
|
|
|
[HttpDelete("{id:guid}"), Authorize(Roles = "SuperAdmin")]
|
|
public async Task<IActionResult> Delete(Guid id, CancellationToken ct)
|
|
{
|
|
var e = await _db.Countries.FirstOrDefaultAsync(x => x.Id == id, ct);
|
|
if (e is null) return NotFound();
|
|
_db.Countries.Remove(e);
|
|
await _db.SaveChangesAsync(ct);
|
|
return NoContent();
|
|
}
|
|
|
|
private static CountryDto Project(Country c) => new(
|
|
c.Id, c.Code, c.Name,
|
|
c.DefaultCurrencyId, c.DefaultCurrency?.Code, c.DefaultCurrency?.Symbol, c.VatRate);
|
|
}
|