feat(org-settings): галки «Услуга»/«Маркируемый» скрываются по умолчанию
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 24s
CI / Web (React + Vite) (push) Successful in 25s
Docker Images / API image (push) Successful in 38s
Docker Images / Web image (push) Successful in 25s
Docker Images / Deploy stage (push) Successful in 19s
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 24s
CI / Web (React + Vite) (push) Successful in 25s
Docker Images / API image (push) Successful in 38s
Docker Images / Web image (push) Successful in 25s
Docker Images / Deploy stage (push) Successful in 19s
Добавлены organizations.ShowServiceOnProduct и ShowMarkedOnProduct (оба default false). В UI карточки товара чекбоксы «Услуга» и «Маркируемый» рендерятся только если соответствующий флаг включен; в фильтрах списка товаров Tri-фильтры тоже прячутся. В БД поля IsService/IsMarked у Product сохраняются как обычно — просто UI их не показывает. Это параллель к ShowVatEnabledOnProduct: по умолчанию UI максимально простой, а нишевые фичи включаются через настройки магазина. Миграция Phase5c_ShowServiceMarkedOnProduct добавляет обе колонки. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e60cd928d2
commit
143f9d5330
|
|
@ -30,14 +30,18 @@ public record OrgSettingsDto(
|
|||
bool MultiCurrencyEnabled,
|
||||
// VAT read-only: из страны организации (countries.VatRate). Источник правды — справочник стран.
|
||||
decimal VatRate,
|
||||
bool ShowVatEnabledOnProduct);
|
||||
bool ShowVatEnabledOnProduct,
|
||||
bool ShowServiceOnProduct,
|
||||
bool ShowMarkedOnProduct);
|
||||
|
||||
// DefaultCurrencyId не принимается — он read-only, выводится из страны (Country.DefaultCurrencyId).
|
||||
public record OrgSettingsInput(
|
||||
string Name,
|
||||
string CountryCode,
|
||||
bool MultiCurrencyEnabled,
|
||||
bool ShowVatEnabledOnProduct);
|
||||
bool ShowVatEnabledOnProduct,
|
||||
bool ShowServiceOnProduct,
|
||||
bool ShowMarkedOnProduct);
|
||||
|
||||
[HttpGet("settings")]
|
||||
public async Task<ActionResult<OrgSettingsDto>> Get(CancellationToken ct)
|
||||
|
|
@ -69,6 +73,8 @@ public async Task<ActionResult<OrgSettingsDto>> Update([FromBody] OrgSettingsInp
|
|||
.FirstOrDefaultAsync(ct);
|
||||
o.MultiCurrencyEnabled = input.MultiCurrencyEnabled;
|
||||
o.ShowVatEnabledOnProduct = input.ShowVatEnabledOnProduct;
|
||||
o.ShowServiceOnProduct = input.ShowServiceOnProduct;
|
||||
o.ShowMarkedOnProduct = input.ShowMarkedOnProduct;
|
||||
await _db.SaveChangesAsync(ct);
|
||||
|
||||
await _db.Entry(o).Reference(x => x.DefaultCurrency).LoadAsync(ct);
|
||||
|
|
@ -92,5 +98,7 @@ private async Task<decimal> ReadVatRateAsync(string countryCode, CancellationTok
|
|||
o.DefaultCurrency?.Symbol,
|
||||
o.MultiCurrencyEnabled,
|
||||
vat,
|
||||
o.ShowVatEnabledOnProduct);
|
||||
o.ShowVatEnabledOnProduct,
|
||||
o.ShowServiceOnProduct,
|
||||
o.ShowMarkedOnProduct);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,4 +31,14 @@ public class Organization : Entity
|
|||
/// скрыта, все товары считаются с НДС. Если true — можно для отдельных товаров
|
||||
/// (хлеб, медикаменты) снимать галку.</summary>
|
||||
public bool ShowVatEnabledOnProduct { get; set; }
|
||||
|
||||
/// <summary>Показывать ли на форме товара и в фильтрах галку «Услуга».
|
||||
/// Большинство магазинов продают только физические товары — флаг выключен
|
||||
/// по умолчанию, чтобы не захламлять UI.</summary>
|
||||
public bool ShowServiceOnProduct { get; set; }
|
||||
|
||||
/// <summary>Показывать ли на форме товара и в фильтрах галку «Маркируемый».
|
||||
/// Маркировка требуется только в нишевых категориях (алкоголь, лекарства,
|
||||
/// табак) — по умолчанию выключено.</summary>
|
||||
public bool ShowMarkedOnProduct { get; set; }
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,29 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace foodmarket.Infrastructure.Persistence.Migrations
|
||||
{
|
||||
/// <summary>Добавляет organizations.ShowServiceOnProduct и ShowMarkedOnProduct —
|
||||
/// флаги видимости чекбоксов «Услуга» / «Маркируемый» на форме товара и
|
||||
/// в фильтрах списка. Оба по умолчанию false: большинство магазинов
|
||||
/// продают только физические товары без маркировки.</summary>
|
||||
public partial class Phase5c_ShowServiceMarkedOnProduct : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder b)
|
||||
{
|
||||
b.AddColumn<bool>(
|
||||
name: "ShowServiceOnProduct", schema: "public", table: "organizations",
|
||||
type: "boolean", nullable: false, defaultValue: false);
|
||||
b.AddColumn<bool>(
|
||||
name: "ShowMarkedOnProduct", schema: "public", table: "organizations",
|
||||
type: "boolean", nullable: false, defaultValue: false);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder b)
|
||||
{
|
||||
b.DropColumn(name: "ShowServiceOnProduct", schema: "public", table: "organizations");
|
||||
b.DropColumn(name: "ShowMarkedOnProduct", schema: "public", table: "organizations");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1092,6 +1092,12 @@ protected override void BuildModel(ModelBuilder modelBuilder)
|
|||
b.Property<bool>("MultiCurrencyEnabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("ShowMarkedOnProduct")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("ShowServiceOnProduct")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("ShowVatEnabledOnProduct")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ export interface OrgSettings {
|
|||
multiCurrencyEnabled: boolean
|
||||
vatRate: number
|
||||
showVatEnabledOnProduct: boolean
|
||||
showServiceOnProduct: boolean
|
||||
showMarkedOnProduct: boolean
|
||||
}
|
||||
|
||||
export function useOrgSettings() {
|
||||
|
|
|
|||
|
|
@ -38,6 +38,8 @@ export function OrganizationSettingsPage() {
|
|||
countryCode: form.countryCode,
|
||||
multiCurrencyEnabled: form.multiCurrencyEnabled,
|
||||
showVatEnabledOnProduct: form.showVatEnabledOnProduct,
|
||||
showServiceOnProduct: form.showServiceOnProduct,
|
||||
showMarkedOnProduct: form.showMarkedOnProduct,
|
||||
}
|
||||
return (await api.put<OrgSettings>('/api/organization/settings', payload)).data
|
||||
},
|
||||
|
|
@ -103,6 +105,26 @@ export function OrganizationSettingsPage() {
|
|||
все новые товары получают ставку из страны организации. Если включено — у каждого
|
||||
товара можно задать ставку вручную (хлеб = 0%, лекарства = 0% и т.п.).
|
||||
</p>
|
||||
|
||||
<Checkbox
|
||||
label='Показывать чекбокс «Услуга» на товаре'
|
||||
checked={form.showServiceOnProduct}
|
||||
onChange={(v) => setForm({ ...form, showServiceOnProduct: v })}
|
||||
/>
|
||||
<p className="text-xs text-slate-500 -mt-2">
|
||||
Нужно, если помимо физических товаров продаются услуги (доставка, сборка и т.п.).
|
||||
По умолчанию галка скрыта.
|
||||
</p>
|
||||
|
||||
<Checkbox
|
||||
label='Показывать чекбокс «Маркируемый» на товаре'
|
||||
checked={form.showMarkedOnProduct}
|
||||
onChange={(v) => setForm({ ...form, showMarkedOnProduct: v })}
|
||||
/>
|
||||
<p className="text-xs text-slate-500 -mt-2">
|
||||
Включать, только если продаётся маркируемая категория (алкоголь, табак, лекарства).
|
||||
По умолчанию галка скрыта.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<div className="mt-4 flex gap-3 items-center">
|
||||
|
|
|
|||
|
|
@ -285,8 +285,12 @@ export function ProductEditPage() {
|
|||
{org.data?.showVatEnabledOnProduct && (
|
||||
<Checkbox label="В том числе НДС" checked={form.vatEnabled} onChange={(v) => setForm({ ...form, vatEnabled: v })} />
|
||||
)}
|
||||
<Checkbox label="Услуга" checked={form.isService} onChange={(v) => setForm({ ...form, isService: v })} />
|
||||
<Checkbox label="Маркируемый (Честный знак / Datamatrix)" checked={form.isMarked} onChange={(v) => setForm({ ...form, isMarked: v })} />
|
||||
{org.data?.showServiceOnProduct && (
|
||||
<Checkbox label="Услуга" checked={form.isService} onChange={(v) => setForm({ ...form, isService: v })} />
|
||||
)}
|
||||
{org.data?.showMarkedOnProduct && (
|
||||
<Checkbox label="Маркируемый (Честный знак / Datamatrix)" checked={form.isMarked} onChange={(v) => setForm({ ...form, isMarked: v })} />
|
||||
)}
|
||||
<Checkbox label="Активен" checked={form.isActive} onChange={(v) => setForm({ ...form, isActive: v })} />
|
||||
</div>
|
||||
</Section>
|
||||
|
|
|
|||
|
|
@ -99,6 +99,8 @@ export function ProductsPage() {
|
|||
const { data, isLoading, page, setPage, search, setSearch, sortKey, sortOrder, setSort } = useCatalogList<Product>(URL, toExtra(filters))
|
||||
const org = useOrgSettings()
|
||||
const showVat = org.data?.showVatEnabledOnProduct ?? false
|
||||
const showService = org.data?.showServiceOnProduct ?? false
|
||||
const showMarked = org.data?.showMarkedOnProduct ?? false
|
||||
const activeCount = activeFilterCount(filters)
|
||||
|
||||
type Col = {
|
||||
|
|
@ -164,7 +166,9 @@ export function ProductsPage() {
|
|||
{filtersOpen && (
|
||||
<div className="px-6 py-3 border-b border-slate-200 dark:border-slate-800 bg-slate-50 dark:bg-slate-900/60 flex flex-wrap gap-4 items-center">
|
||||
<Tri label="Активные" value={filters.isActive} onChange={(v) => { setFilters({ ...filters, isActive: v }); setPage(1) }} />
|
||||
<Tri label="Услуга" value={filters.isService} onChange={(v) => { setFilters({ ...filters, isService: v }); setPage(1) }} />
|
||||
{showService && (
|
||||
<Tri label="Услуга" value={filters.isService} onChange={(v) => { setFilters({ ...filters, isService: v }); setPage(1) }} />
|
||||
)}
|
||||
<div className="flex items-center gap-2 text-xs">
|
||||
<span className="text-slate-500">Фасовка</span>
|
||||
<select
|
||||
|
|
@ -178,7 +182,9 @@ export function ProductsPage() {
|
|||
<option value="3">разливной</option>
|
||||
</select>
|
||||
</div>
|
||||
<Tri label="Маркируемый" value={filters.isMarked} onChange={(v) => { setFilters({ ...filters, isMarked: v }); setPage(1) }} />
|
||||
{showMarked && (
|
||||
<Tri label="Маркируемый" value={filters.isMarked} onChange={(v) => { setFilters({ ...filters, isMarked: v }); setPage(1) }} />
|
||||
)}
|
||||
<Tri label="Со штрихкодом" value={filters.hasBarcode} onChange={(v) => { setFilters({ ...filters, hasBarcode: v }); setPage(1) }} yesLabel="есть" noLabel="нет" />
|
||||
{activeCount > 0 && (
|
||||
<button
|
||||
|
|
|
|||
Loading…
Reference in a new issue