From bf536290927651c4ec52331b623d0944fe813835 Mon Sep 17 00:00:00 2001 From: nns <278048682+nurdotnet@users.noreply.github.com> Date: Fri, 8 May 2026 11:02:10 +0500 Subject: [PATCH] refactor(units): drop Description, hide Code from non-SuperAdmin UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Description у пяти канонических ОКЕИ-единиц никогда не заполнялось ни UI, ни импортом, ни сидером — выкидываем поле полностью (Domain → EF-config → DTO → Input → frontend types → Super-Admin форма). Migration Phase5d_DropUnitOfMeasureDescription дропает колонку. Code оставляем в БД (нужен для интеграций МойСклад/1С), но скрываем от org Admin'а: - /catalog/units-of-measure — только колонки Name + кнопка toggle, без Code и Description; поиск/сортировка только по Name. - /super-admin/units-of-measure — Code продолжает показываться в таблице и форме редактирования. Дропдаун единиц в ProductEditPage / ProductQuickCreateModal уже отдаёт только {u.name} в options, проверено. На SupplyEditPage/RetailSaleEditPage в строках документа отображается unitName, Code не показывался — без изменений. --- .../Catalog/UnitsOfMeasureController.cs | 4 +- .../SuperAdminUnitsOfMeasureController.cs | 8 ++-- .../Catalog/CatalogDtos.cs | 4 +- .../Catalog/UnitOfMeasure.cs | 1 - .../Configurations/CatalogConfigurations.cs | 1 - ...00_Phase5d_DropUnitOfMeasureDescription.cs | 37 +++++++++++++++++++ src/food-market.web/src/lib/types.ts | 2 +- .../pages/SuperAdminUnitsOfMeasurePage.tsx | 9 +---- .../src/pages/UnitsOfMeasurePage.tsx | 5 ++- 9 files changed, 50 insertions(+), 21 deletions(-) create mode 100644 src/food-market.infrastructure/Persistence/Migrations/20260508100000_Phase5d_DropUnitOfMeasureDescription.cs diff --git a/src/food-market.api/Controllers/Catalog/UnitsOfMeasureController.cs b/src/food-market.api/Controllers/Catalog/UnitsOfMeasureController.cs index 36193c1..e79522a 100644 --- a/src/food-market.api/Controllers/Catalog/UnitsOfMeasureController.cs +++ b/src/food-market.api/Controllers/Catalog/UnitsOfMeasureController.cs @@ -69,7 +69,7 @@ public UnitsOfMeasureController(AppDbContext db, ITenantContext tenant) var items = await q .Skip(req.Skip).Take(req.Take) .Select(u => new UnitOfMeasureDto( - u.Id, u.Code, u.Name, u.Description, u.OrganizationId, u.IsActive, true)) + u.Id, u.Code, u.Name, u.OrganizationId, u.IsActive, true)) .ToListAsync(ct); return new PagedResult { Items = items, Total = total, Page = req.Page, PageSize = req.Take }; } @@ -87,7 +87,7 @@ public async Task> Get(Guid id, CancellationToken var enabled = !orgId.HasValue || await _db.OrgUnitsOfMeasure .IgnoreQueryFilters() .AnyAsync(j => j.OrganizationId == orgId.Value && j.UnitOfMeasureId == id, ct); - return new UnitOfMeasureDto(u.Id, u.Code, u.Name, u.Description, u.OrganizationId, u.IsActive, enabled); + return new UnitOfMeasureDto(u.Id, u.Code, u.Name, u.OrganizationId, u.IsActive, enabled); } /// Включить global для текущей орги. Идемпотентно: повторный diff --git a/src/food-market.api/Controllers/SuperAdmin/SuperAdminUnitsOfMeasureController.cs b/src/food-market.api/Controllers/SuperAdmin/SuperAdminUnitsOfMeasureController.cs index 11156f6..1571ecd 100644 --- a/src/food-market.api/Controllers/SuperAdmin/SuperAdminUnitsOfMeasureController.cs +++ b/src/food-market.api/Controllers/SuperAdmin/SuperAdminUnitsOfMeasureController.cs @@ -47,7 +47,7 @@ public class SuperAdminUnitsOfMeasureController : ControllerBase var items = await q .Skip(req.Skip).Take(req.Take) .Select(u => new UnitOfMeasureDto( - u.Id, u.Code, u.Name, u.Description, u.OrganizationId, u.IsActive, true)) + u.Id, u.Code, u.Name, u.OrganizationId, u.IsActive, true)) .ToListAsync(ct); return new PagedResult { Items = items, Total = total, Page = req.Page, PageSize = req.Take }; } @@ -61,7 +61,7 @@ public async Task> Get(Guid id, CancellationToken .FirstOrDefaultAsync(x => x.Id == id && x.OrganizationId == null, ct); return u is null ? NotFound() - : new UnitOfMeasureDto(u.Id, u.Code, u.Name, u.Description, u.OrganizationId, u.IsActive, true); + : new UnitOfMeasureDto(u.Id, u.Code, u.Name, u.OrganizationId, u.IsActive, true); } [HttpPost] @@ -81,13 +81,12 @@ public async Task> Create([FromBody] UnitOfMeasur OrganizationId = null, Code = input.Code.Trim(), Name = input.Name.Trim(), - Description = input.Description, IsActive = true, }; _db.UnitsOfMeasure.Add(e); await _db.SaveChangesAsync(ct); return CreatedAtAction(nameof(Get), new { id = e.Id }, - new UnitOfMeasureDto(e.Id, e.Code, e.Name, e.Description, e.OrganizationId, e.IsActive, true)); + new UnitOfMeasureDto(e.Id, e.Code, e.Name, e.OrganizationId, e.IsActive, true)); } [HttpPut("{id:guid}")] @@ -110,7 +109,6 @@ public async Task Update(Guid id, [FromBody] UnitOfMeasureInput i e.Code = input.Code.Trim(); e.Name = input.Name.Trim(); - e.Description = input.Description; await _db.SaveChangesAsync(ct); return NoContent(); } diff --git a/src/food-market.application/Catalog/CatalogDtos.cs b/src/food-market.application/Catalog/CatalogDtos.cs index dc2d545..23debef 100644 --- a/src/food-market.application/Catalog/CatalogDtos.cs +++ b/src/food-market.application/Catalog/CatalogDtos.cs @@ -12,7 +12,7 @@ public record CountryDto( public record CurrencyDto(Guid Id, string Code, string Name, string Symbol); public record UnitOfMeasureDto( - Guid Id, string Code, string Name, string? Description, Guid? OrganizationId, + Guid Id, string Code, string Name, Guid? OrganizationId, bool IsActive = true, bool IsEnabledForOrg = true); public record PriceTypeDto( @@ -62,7 +62,7 @@ public record CountryInput( string Code, string Name, Guid? DefaultCurrencyId = null, decimal VatRate = 0m); public record CurrencyInput(string Code, string Name, string Symbol); -public record UnitOfMeasureInput(string Code, string Name, string? Description = null); +public record UnitOfMeasureInput(string Code, string Name); public record PriceTypeInput( string Name, bool IsRequired = false, bool IsRetail = false, int SortOrder = 0); diff --git a/src/food-market.domain/Catalog/UnitOfMeasure.cs b/src/food-market.domain/Catalog/UnitOfMeasure.cs index 579f168..3a96c35 100644 --- a/src/food-market.domain/Catalog/UnitOfMeasure.cs +++ b/src/food-market.domain/Catalog/UnitOfMeasure.cs @@ -13,6 +13,5 @@ public class UnitOfMeasure : Entity, IOptionalTenantEntity public Guid? OrganizationId { get; set; } public string Code { get; set; } = null!; // ОКЕИ код: "796" (шт), "166" (кг), "112" (л) public string Name { get; set; } = null!; // "штука", "килограмм", "литр" - public string? Description { get; set; } public bool IsActive { get; set; } = true; } diff --git a/src/food-market.infrastructure/Persistence/Configurations/CatalogConfigurations.cs b/src/food-market.infrastructure/Persistence/Configurations/CatalogConfigurations.cs index f004ee9..682495b 100644 --- a/src/food-market.infrastructure/Persistence/Configurations/CatalogConfigurations.cs +++ b/src/food-market.infrastructure/Persistence/Configurations/CatalogConfigurations.cs @@ -47,7 +47,6 @@ private static void ConfigureUnit(EntityTypeBuilder b) b.ToTable("units_of_measure"); b.Property(x => x.Code).HasMaxLength(10).IsRequired(); b.Property(x => x.Name).HasMaxLength(100).IsRequired(); - b.Property(x => x.Description).HasMaxLength(500); b.Property(x => x.IsActive).HasDefaultValue(true); // Phase5c: после миграции OrganizationId всегда NULL — глобальный справочник. // Уникальность по Code только среди активных, чтобы можно было diff --git a/src/food-market.infrastructure/Persistence/Migrations/20260508100000_Phase5d_DropUnitOfMeasureDescription.cs b/src/food-market.infrastructure/Persistence/Migrations/20260508100000_Phase5d_DropUnitOfMeasureDescription.cs new file mode 100644 index 0000000..ce557ee --- /dev/null +++ b/src/food-market.infrastructure/Persistence/Migrations/20260508100000_Phase5d_DropUnitOfMeasureDescription.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using foodmarket.Infrastructure.Persistence; + +#nullable disable + +namespace foodmarket.Infrastructure.Persistence.Migrations +{ + /// Phase5d — выкидываем UnitOfMeasure.Description: для пяти + /// канонических ОКЕИ-единиц («штука», «кг», ...) нечего описывать, + /// поле никогда не заполнялось ни UI, ни импортом, ни сидером. + /// Code остаётся (нужен для интеграций МойСклад/1С), но скрыт в UI + /// от org Admin'а. + [DbContext(typeof(AppDbContext))] + [Migration("20260508100000_Phase5d_DropUnitOfMeasureDescription")] + public partial class Phase5d_DropUnitOfMeasureDescription : Migration + { + protected override void Up(MigrationBuilder b) + { + b.DropColumn( + name: "Description", + schema: "public", + table: "units_of_measure"); + } + + protected override void Down(MigrationBuilder b) + { + b.AddColumn( + name: "Description", + schema: "public", + table: "units_of_measure", + type: "character varying(500)", + maxLength: 500, + nullable: true); + } + } +} diff --git a/src/food-market.web/src/lib/types.ts b/src/food-market.web/src/lib/types.ts index ffee482..a9f0a59 100644 --- a/src/food-market.web/src/lib/types.ts +++ b/src/food-market.web/src/lib/types.ts @@ -29,7 +29,7 @@ export interface Country { } export interface Currency { id: string; code: string; name: string; symbol: string } export interface UnitOfMeasure { - id: string; code: string; name: string; description: string | null; organizationId: string | null; + id: string; code: string; name: string; organizationId: string | null; isActive: boolean; isEnabledForOrg: boolean } export interface PriceType { id: string; name: string; isRequired: boolean; isSystem: boolean; isRetail: boolean; sortOrder: number } diff --git a/src/food-market.web/src/pages/SuperAdminUnitsOfMeasurePage.tsx b/src/food-market.web/src/pages/SuperAdminUnitsOfMeasurePage.tsx index 3bb3c27..a9edb62 100644 --- a/src/food-market.web/src/pages/SuperAdminUnitsOfMeasurePage.tsx +++ b/src/food-market.web/src/pages/SuperAdminUnitsOfMeasurePage.tsx @@ -16,10 +16,9 @@ interface Form { id?: string code: string name: string - description: string } -const blank: Form = { code: '', name: '', description: '' } +const blank: Form = { code: '', name: '' } export function SuperAdminUnitsOfMeasurePage() { const list = useCatalogList(URL) @@ -80,12 +79,11 @@ export function SuperAdminUnitsOfMeasurePage() { onSortChange={list.setSort} onRowClick={(r) => { setSubmitError(null) - setForm({ id: r.id, code: r.code, name: r.name, description: r.description ?? '' }) + setForm({ id: r.id, code: r.code, name: r.name }) }} columns={[ { header: 'Код', width: '90px', sortKey: 'code', cell: (r) => {r.code} }, { header: 'Название', sortKey: 'name', cell: (r) => r.name }, - { header: 'Описание', cell: (r) => r.description ?? '—' }, { header: 'Статус', width: '110px', @@ -121,9 +119,6 @@ export function SuperAdminUnitsOfMeasurePage() { setForm({ ...form, name: e.target.value })} /> - - setForm({ ...form, description: e.target.value })} /> - {submitError && (

{submitError}

)} diff --git a/src/food-market.web/src/pages/UnitsOfMeasurePage.tsx b/src/food-market.web/src/pages/UnitsOfMeasurePage.tsx index 6c8a48b..ecd97f3 100644 --- a/src/food-market.web/src/pages/UnitsOfMeasurePage.tsx +++ b/src/food-market.web/src/pages/UnitsOfMeasurePage.tsx @@ -62,9 +62,10 @@ export function UnitsOfMeasurePage() { sortOrder={list.sortOrder} onSortChange={list.setSort} columns={[ - { header: 'Код', width: '90px', sortKey: 'code', cell: (r) => {r.code} }, + // Колонку «Код» (ОКЕИ 796/166/...) не показываем org-юзеру: код нужен + // только для интеграций (МойСклад/1С) и виден SuperAdmin'у на + // /super-admin/units. Здесь админ орги выбирает по понятному имени. { header: 'Название', sortKey: 'name', cell: (r) => r.name }, - { header: 'Описание', cell: (r) => r.description ?? '—' }, { header: 'Для орги', width: '120px',