ui(products-list): колонки Штрихкод/Фасовка/Закупочная цена

В таблице товаров:
- «Ед.» → «Фасовка» (packagingLabel из типа товара, sort packaging).
- «Штрихкодов» (count) → «Штрихкод» — первый код монoshrift.
- Убраны колонки «Группа» и «Активен».
- Добавлена «Закупочная цена» с форматированием «305.00 ₸»
  (purchasePrice + purchaseCurrencyCode), sort purchasePrice.

На сервере ProductsController.List принимает новые sort keys
packaging и purchasePrice.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
nns 2026-04-24 19:03:45 +05:00
parent 9886b5dee1
commit 9c70de9b3d
2 changed files with 14 additions and 7 deletions

View file

@ -92,6 +92,10 @@ private async Task<decimal> ResolveDefaultVatAsync(CancellationToken ct)
("group", true) => q.OrderByDescending(p => p.ProductGroup != null ? p.ProductGroup.Name : null).ThenBy(p => p.Name), ("group", true) => q.OrderByDescending(p => p.ProductGroup != null ? p.ProductGroup.Name : null).ThenBy(p => p.Name),
("unit", false) => q.OrderBy(p => p.UnitOfMeasure!.Name).ThenBy(p => p.Name), ("unit", false) => q.OrderBy(p => p.UnitOfMeasure!.Name).ThenBy(p => p.Name),
("unit", true) => q.OrderByDescending(p => p.UnitOfMeasure!.Name).ThenBy(p => p.Name), ("unit", true) => q.OrderByDescending(p => p.UnitOfMeasure!.Name).ThenBy(p => p.Name),
("packaging", false) => q.OrderBy(p => p.Packaging).ThenBy(p => p.Name),
("packaging", true) => q.OrderByDescending(p => p.Packaging).ThenBy(p => p.Name),
("purchasePrice", false) => q.OrderBy(p => p.PurchasePrice).ThenBy(p => p.Name),
("purchasePrice", true) => q.OrderByDescending(p => p.PurchasePrice).ThenBy(p => p.Name),
("vat", false) => q.OrderBy(p => p.Vat).ThenBy(p => p.Name), ("vat", false) => q.OrderBy(p => p.Vat).ThenBy(p => p.Name),
("vat", true) => q.OrderByDescending(p => p.Vat).ThenBy(p => p.Name), ("vat", true) => q.OrderByDescending(p => p.Vat).ThenBy(p => p.Name),
("isActive", false) => q.OrderBy(p => p.IsActive).ThenBy(p => p.Name), ("isActive", false) => q.OrderBy(p => p.IsActive).ThenBy(p => p.Name),

View file

@ -8,7 +8,7 @@ import { Plus, Filter, X } from 'lucide-react'
import { useCatalogList } from '@/lib/useCatalog' import { useCatalogList } from '@/lib/useCatalog'
import { useOrgSettings } from '@/lib/useOrgSettings' import { useOrgSettings } from '@/lib/useOrgSettings'
import { ProductGroupTree } from '@/components/ProductGroupTree' import { ProductGroupTree } from '@/components/ProductGroupTree'
import type { Product } from '@/lib/types' import { packagingLabel, type Product } from '@/lib/types'
const URL = '/api/catalog/products' const URL = '/api/catalog/products'
@ -117,16 +117,19 @@ export function ProductsPage() {
{r.article && <div className="text-xs text-slate-400 font-mono">{r.article}</div>} {r.article && <div className="text-xs text-slate-400 font-mono">{r.article}</div>}
</div> </div>
)}, )},
{ header: 'Группа', width: '200px', sortKey: 'group', cell: (r) => r.productGroupName ?? '—' }, { header: 'Фасовка', width: '110px', sortKey: 'packaging', cell: (r) => packagingLabel[r.packaging] ?? '—' },
{ header: 'Ед.', width: '70px', sortKey: 'unit', cell: (r) => r.unitName }, { header: 'Штрихкод', width: '160px', cell: (r) => (
<span className="font-mono">{r.barcodes[0]?.code ?? '—'}</span>
)},
{ header: 'Закупочная цена', width: '160px', className: 'text-right font-mono', sortKey: 'purchasePrice', cell: (r) => (
r.purchasePrice != null
? `${r.purchasePrice.toLocaleString('ru', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} ${r.purchaseCurrencyCode ?? ''}`.trim()
: '—'
)},
] ]
if (showVat) { if (showVat) {
baseColumns.push({ header: 'НДС', width: '90px', className: 'text-right', sortKey: 'vat', cell: (r) => r.vatEnabled ? `${r.vat.toFixed(2)}%` : '—' }) baseColumns.push({ header: 'НДС', width: '90px', className: 'text-right', sortKey: 'vat', cell: (r) => r.vatEnabled ? `${r.vat.toFixed(2)}%` : '—' })
} }
baseColumns.push(
{ header: 'Штрихкодов', width: '120px', className: 'text-right', cell: (r) => r.barcodes.length },
{ header: 'Активен', width: '100px', sortKey: 'isActive', cell: (r) => r.isActive ? '✓' : '—' },
)
return ( return (
<div className="flex h-full min-h-0"> <div className="flex h-full min-h-0">