refactor(units): drop Description, hide Code from non-SuperAdmin UI
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 1m24s
CI / Web (React + Vite) (push) Successful in 42s
Docker API / Build + push API (push) Successful in 1m33s
Docker Web / Build + push Web (push) Successful in 37s
Docker API / Deploy API on stage (push) Successful in 18s
Docker Web / Deploy Web on stage (push) Successful in 12s
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 1m24s
CI / Web (React + Vite) (push) Successful in 42s
Docker API / Build + push API (push) Successful in 1m33s
Docker Web / Build + push Web (push) Successful in 37s
Docker API / Deploy API on stage (push) Successful in 18s
Docker Web / Deploy Web on stage (push) Successful in 12s
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 не показывался — без
изменений.
This commit is contained in:
parent
37cd9aa94b
commit
bf53629092
|
|
@ -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<UnitOfMeasureDto> { Items = items, Total = total, Page = req.Page, PageSize = req.Take };
|
||||
}
|
||||
|
|
@ -87,7 +87,7 @@ public async Task<ActionResult<UnitOfMeasureDto>> 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);
|
||||
}
|
||||
|
||||
/// <summary>Включить global для текущей орги. Идемпотентно: повторный
|
||||
|
|
|
|||
|
|
@ -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<UnitOfMeasureDto> { Items = items, Total = total, Page = req.Page, PageSize = req.Take };
|
||||
}
|
||||
|
|
@ -61,7 +61,7 @@ public async Task<ActionResult<UnitOfMeasureDto>> 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<ActionResult<UnitOfMeasureDto>> 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<IActionResult> 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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,6 @@ private static void ConfigureUnit(EntityTypeBuilder<UnitOfMeasure> 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 только среди активных, чтобы можно было
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using foodmarket.Infrastructure.Persistence;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace foodmarket.Infrastructure.Persistence.Migrations
|
||||
{
|
||||
/// <summary>Phase5d — выкидываем UnitOfMeasure.Description: для пяти
|
||||
/// канонических ОКЕИ-единиц («штука», «кг», ...) нечего описывать,
|
||||
/// поле никогда не заполнялось ни UI, ни импортом, ни сидером.
|
||||
/// Code остаётся (нужен для интеграций МойСклад/1С), но скрыт в UI
|
||||
/// от org Admin'а.</summary>
|
||||
[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<string>(
|
||||
name: "Description",
|
||||
schema: "public",
|
||||
table: "units_of_measure",
|
||||
type: "character varying(500)",
|
||||
maxLength: 500,
|
||||
nullable: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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<UnitOfMeasure>(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) => <span className="font-mono">{r.code}</span> },
|
||||
{ header: 'Название', sortKey: 'name', cell: (r) => r.name },
|
||||
{ header: 'Описание', cell: (r) => r.description ?? '—' },
|
||||
{
|
||||
header: 'Статус',
|
||||
width: '110px',
|
||||
|
|
@ -121,9 +119,6 @@ export function SuperAdminUnitsOfMeasurePage() {
|
|||
<Field label="Название">
|
||||
<TextInput value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })} />
|
||||
</Field>
|
||||
<Field label="Описание">
|
||||
<TextInput value={form.description} onChange={(e) => setForm({ ...form, description: e.target.value })} />
|
||||
</Field>
|
||||
{submitError && (
|
||||
<p className="text-sm text-red-600 dark:text-red-400">{submitError}</p>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -62,9 +62,10 @@ export function UnitsOfMeasurePage() {
|
|||
sortOrder={list.sortOrder}
|
||||
onSortChange={list.setSort}
|
||||
columns={[
|
||||
{ header: 'Код', width: '90px', sortKey: 'code', cell: (r) => <span className="font-mono">{r.code}</span> },
|
||||
// Колонку «Код» (ОКЕИ 796/166/...) не показываем org-юзеру: код нужен
|
||||
// только для интеграций (МойСклад/1С) и виден SuperAdmin'у на
|
||||
// /super-admin/units. Здесь админ орги выбирает по понятному имени.
|
||||
{ header: 'Название', sortKey: 'name', cell: (r) => r.name },
|
||||
{ header: 'Описание', cell: (r) => r.description ?? '—' },
|
||||
{
|
||||
header: 'Для орги',
|
||||
width: '120px',
|
||||
|
|
|
|||
Loading…
Reference in a new issue