feat(org-settings): настройка ShowMinMaxStock для мин/макс остатков

Добавлена Organization.ShowMinMaxStock (bool, default false) — флаг
видимости полей «Минимальный / Максимальный остаток» на карточке
товара. В UI настроек магазина появилась соответствующая галка
с подсказкой. По умолчанию выключено — большинству магазинов
эти поля не нужны.

Миграция Phase5f_ShowMinMaxStock добавляет колонку.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
nns 2026-04-24 19:02:53 +05:00
parent ed00e85140
commit a94c38d074
7 changed files with 1936 additions and 6 deletions

View file

@ -32,7 +32,8 @@ public record OrgSettingsDto(
decimal VatRate, decimal VatRate,
bool ShowVatEnabledOnProduct, bool ShowVatEnabledOnProduct,
bool ShowServiceOnProduct, bool ShowServiceOnProduct,
bool ShowMarkedOnProduct); bool ShowMarkedOnProduct,
bool ShowMinMaxStock);
// DefaultCurrencyId не принимается — он read-only, выводится из страны (Country.DefaultCurrencyId). // DefaultCurrencyId не принимается — он read-only, выводится из страны (Country.DefaultCurrencyId).
public record OrgSettingsInput( public record OrgSettingsInput(
@ -41,7 +42,8 @@ public record OrgSettingsInput(
bool MultiCurrencyEnabled, bool MultiCurrencyEnabled,
bool ShowVatEnabledOnProduct, bool ShowVatEnabledOnProduct,
bool ShowServiceOnProduct, bool ShowServiceOnProduct,
bool ShowMarkedOnProduct); bool ShowMarkedOnProduct,
bool ShowMinMaxStock);
[HttpGet("settings")] [HttpGet("settings")]
public async Task<ActionResult<OrgSettingsDto>> Get(CancellationToken ct) public async Task<ActionResult<OrgSettingsDto>> Get(CancellationToken ct)
@ -75,6 +77,7 @@ public async Task<ActionResult<OrgSettingsDto>> Update([FromBody] OrgSettingsInp
o.ShowVatEnabledOnProduct = input.ShowVatEnabledOnProduct; o.ShowVatEnabledOnProduct = input.ShowVatEnabledOnProduct;
o.ShowServiceOnProduct = input.ShowServiceOnProduct; o.ShowServiceOnProduct = input.ShowServiceOnProduct;
o.ShowMarkedOnProduct = input.ShowMarkedOnProduct; o.ShowMarkedOnProduct = input.ShowMarkedOnProduct;
o.ShowMinMaxStock = input.ShowMinMaxStock;
await _db.SaveChangesAsync(ct); await _db.SaveChangesAsync(ct);
await _db.Entry(o).Reference(x => x.DefaultCurrency).LoadAsync(ct); await _db.Entry(o).Reference(x => x.DefaultCurrency).LoadAsync(ct);
@ -100,5 +103,6 @@ private async Task<decimal> ReadVatRateAsync(string countryCode, CancellationTok
vat, vat,
o.ShowVatEnabledOnProduct, o.ShowVatEnabledOnProduct,
o.ShowServiceOnProduct, o.ShowServiceOnProduct,
o.ShowMarkedOnProduct); o.ShowMarkedOnProduct,
o.ShowMinMaxStock);
} }

View file

@ -41,4 +41,9 @@ public class Organization : Entity
/// Маркировка требуется только в нишевых категориях (алкоголь, лекарства, /// Маркировка требуется только в нишевых категориях (алкоголь, лекарства,
/// табак) — по умолчанию выключено.</summary> /// табак) — по умолчанию выключено.</summary>
public bool ShowMarkedOnProduct { get; set; } public bool ShowMarkedOnProduct { get; set; }
/// <summary>Показывать ли поля «Минимальный остаток» / «Максимальный остаток»
/// на карточке товара и одноимённую колонку в списке. Нужно в основном
/// торговым сетям со свободным местом на полке — по умолчанию выключено.</summary>
public bool ShowMinMaxStock { get; set; }
} }

View file

@ -0,0 +1,24 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace foodmarket.Infrastructure.Persistence.Migrations
{
/// <summary>Добавляет organizations.ShowMinMaxStock — флаг видимости
/// полей мин/макс остатка на карточке товара и одноимённой колонки
/// в списке. По умолчанию false.</summary>
public partial class Phase5f_ShowMinMaxStock : Migration
{
protected override void Up(MigrationBuilder b)
{
b.AddColumn<bool>(
name: "ShowMinMaxStock", schema: "public", table: "organizations",
type: "boolean", nullable: false, defaultValue: false);
}
protected override void Down(MigrationBuilder b)
{
b.DropColumn(name: "ShowMinMaxStock", schema: "public", table: "organizations");
}
}
}

View file

@ -473,9 +473,6 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.Property<DateTime>("CreatedAt") b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone"); .HasColumnType("timestamp with time zone");
b.Property<bool>("IsActive")
.HasColumnType("boolean");
b.Property<int>("MinorUnit") b.Property<int>("MinorUnit")
.HasColumnType("integer"); .HasColumnType("integer");
@ -1096,6 +1093,9 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.Property<bool>("ShowMarkedOnProduct") b.Property<bool>("ShowMarkedOnProduct")
.HasColumnType("boolean"); .HasColumnType("boolean");
b.Property<bool>("ShowMinMaxStock")
.HasColumnType("boolean");
b.Property<bool>("ShowServiceOnProduct") b.Property<bool>("ShowServiceOnProduct")
.HasColumnType("boolean"); .HasColumnType("boolean");

View file

@ -13,6 +13,7 @@ export interface OrgSettings {
showVatEnabledOnProduct: boolean showVatEnabledOnProduct: boolean
showServiceOnProduct: boolean showServiceOnProduct: boolean
showMarkedOnProduct: boolean showMarkedOnProduct: boolean
showMinMaxStock: boolean
} }
export function useOrgSettings() { export function useOrgSettings() {

View file

@ -40,6 +40,7 @@ export function OrganizationSettingsPage() {
showVatEnabledOnProduct: form.showVatEnabledOnProduct, showVatEnabledOnProduct: form.showVatEnabledOnProduct,
showServiceOnProduct: form.showServiceOnProduct, showServiceOnProduct: form.showServiceOnProduct,
showMarkedOnProduct: form.showMarkedOnProduct, showMarkedOnProduct: form.showMarkedOnProduct,
showMinMaxStock: form.showMinMaxStock,
} }
return (await api.put<OrgSettings>('/api/organization/settings', payload)).data return (await api.put<OrgSettings>('/api/organization/settings', payload)).data
}, },
@ -125,6 +126,16 @@ export function OrganizationSettingsPage() {
Включать, только если продаётся маркируемая категория (алкоголь, табак, лекарства). Включать, только если продаётся маркируемая категория (алкоголь, табак, лекарства).
По умолчанию галка скрыта. По умолчанию галка скрыта.
</p> </p>
<Checkbox
label='Показывать мин/макс остатки на товаре'
checked={form.showMinMaxStock}
onChange={(v) => setForm({ ...form, showMinMaxStock: v })}
/>
<p className="text-xs text-slate-500 -mt-2">
Если включено на карточке товара есть поля «Минимальный / Максимальный остаток»
для автозаказа. По умолчанию скрыто.
</p>
</section> </section>
<div className="mt-4 flex gap-3 items-center"> <div className="mt-4 flex gap-3 items-center">