ui(products-list): убрать фильтр «Со штрихкодом», добавить «Закупочная цена от/до»

Поскольку штрихкод теперь обязательный (минимум 1 у каждого товара),
фильтр «Со штрихкодом» бессмыслен — убран из UI и контроллера.

Вместо него — два MoneyInput «Закупочная цена от/до» в панели
фильтров. Использует символ валюты по умолчанию из настроек
организации и уважает AllowFractionalPrices.

Backend: ProductsController.List принимает purchasePriceFrom /
purchasePriceTo (decimal?), применяет ≥ / ≤ к PurchasePrice;
параметр hasBarcode удалён.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
nns 2026-04-25 12:23:45 +05:00
parent 4f4a751d26
commit d1a7e1e647
2 changed files with 36 additions and 8 deletions

View file

@ -79,7 +79,8 @@ private async Task<decimal> ResolveDefaultVatAsync(CancellationToken ct)
[FromQuery] Packaging? packaging,
[FromQuery] bool? isMarked,
[FromQuery] bool? isActive,
[FromQuery] bool? hasBarcode,
[FromQuery] decimal? purchasePriceFrom,
[FromQuery] decimal? purchasePriceTo,
CancellationToken ct)
{
var q = QueryIncludes().AsNoTracking();
@ -102,8 +103,8 @@ private async Task<decimal> ResolveDefaultVatAsync(CancellationToken ct)
if (packaging is not null) q = q.Where(p => p.Packaging == packaging);
if (isMarked is not null) q = q.Where(p => p.IsMarked == isMarked);
if (isActive is not null) q = q.Where(p => p.IsActive == isActive);
if (hasBarcode is not null)
q = hasBarcode == true ? q.Where(p => p.Barcodes.Any()) : q.Where(p => !p.Barcodes.Any());
if (purchasePriceFrom is not null) q = q.Where(p => p.PurchasePrice >= purchasePriceFrom);
if (purchasePriceTo is not null) q = q.Where(p => p.PurchasePrice <= purchasePriceTo);
if (!string.IsNullOrWhiteSpace(req.Search))
{

View file

@ -8,6 +8,7 @@ import { Plus, Filter, X, FolderTree } from 'lucide-react'
import { useCatalogList } from '@/lib/useCatalog'
import { useOrgSettings } from '@/lib/useOrgSettings'
import { ProductGroupTree } from '@/components/ProductGroupTree'
import { MoneyInput } from '@/components/Field'
import { packagingLabel, type Product } from '@/lib/types'
const URL = '/api/catalog/products'
@ -20,7 +21,8 @@ interface Filters {
isService: TriFilter
packaging: number | null // null = все, 1/2/3 = Piece/Weight/Liquid
isMarked: TriFilter
hasBarcode: TriFilter
purchasePriceFrom: number | null
purchasePriceTo: number | null
}
const defaultFilters: Filters = {
@ -29,7 +31,8 @@ const defaultFilters: Filters = {
isService: 'all',
packaging: null,
isMarked: 'all',
hasBarcode: 'all',
purchasePriceFrom: null,
purchasePriceTo: null,
}
const toExtra = (f: Filters): Record<string, string | number | boolean | undefined> => {
@ -39,7 +42,8 @@ const toExtra = (f: Filters): Record<string, string | number | boolean | undefin
if (f.isService !== 'all') e.isService = f.isService === 'yes'
if (f.packaging) e.packaging = f.packaging
if (f.isMarked !== 'all') e.isMarked = f.isMarked === 'yes'
if (f.hasBarcode !== 'all') e.hasBarcode = f.hasBarcode === 'yes'
if (f.purchasePriceFrom != null) e.purchasePriceFrom = f.purchasePriceFrom
if (f.purchasePriceTo != null) e.purchasePriceTo = f.purchasePriceTo
return e
}
@ -50,7 +54,8 @@ const activeFilterCount = (f: Filters) => {
if (f.isService !== 'all') n++
if (f.packaging) n++
if (f.isMarked !== 'all') n++
if (f.hasBarcode !== 'all') n++
if (f.purchasePriceFrom != null) n++
if (f.purchasePriceTo != null) n++
return n
}
@ -217,7 +222,29 @@ export function ProductsPage() {
{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="нет" />
<div className="flex items-center gap-2 text-xs">
<span className="text-slate-500">Закупочная цена</span>
<div className="w-32">
<MoneyInput
value={filters.purchasePriceFrom}
onChange={(n) => { setFilters({ ...filters, purchasePriceFrom: n }); setPage(1) }}
currencyCode={org.data?.defaultCurrencyCode ?? undefined}
currencySymbol={org.data?.defaultCurrencySymbol ?? undefined}
allowFractional={org.data?.allowFractionalPrices ?? false}
placeholder="от"
/>
</div>
<div className="w-32">
<MoneyInput
value={filters.purchasePriceTo}
onChange={(n) => { setFilters({ ...filters, purchasePriceTo: n }); setPage(1) }}
currencyCode={org.data?.defaultCurrencyCode ?? undefined}
currencySymbol={org.data?.defaultCurrencySymbol ?? undefined}
allowFractional={org.data?.allowFractionalPrices ?? false}
placeholder="до"
/>
</div>
</div>
{activeCount > 0 && (
<button
type="button"