feat(product): enum Packaging (штучный/весовой/разливной) вместо IsWeighed
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 30s
CI / Web (React + Vite) (push) Successful in 21s
Docker Images / API image (push) Successful in 46s
Docker Images / Web image (push) Successful in 23s
Docker Images / Deploy stage (push) Successful in 17s
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 30s
CI / Web (React + Vite) (push) Successful in 21s
Docker Images / API image (push) Successful in 46s
Docker Images / Web image (push) Successful in 23s
Docker Images / Deploy stage (push) Successful in 17s
Миграция Phase4b_ProductPackaging: products.IsWeighed (bool) → products.Packaging (int enum) 1=Piece (default), 2=Weight, 3=Liquid Backfill: прежние весовые товары → Weight. Domain/DTO/Input/Controller/Seeder/MoySkladImport — всё обновлено. Web: - Packaging enum в types.ts. - ProductEditPage: select "Фасовка" вместо checkbox "Весовой". - Подпись чекбокса НДС уточнена: "НДС применяется (ставка выше)" — ссылается на поле Vat на товаре. - Удалён IsMarked checkbox текст → "Маркируемый (Честный знак / Datamatrix)". - ProductsPage фильтр: select Packaging вместо Tri(IsWeighed).
This commit is contained in:
parent
773ecde6ba
commit
414d185765
|
|
@ -22,7 +22,7 @@ public class ProductsController : ControllerBase
|
||||||
[FromQuery] PagedRequest req,
|
[FromQuery] PagedRequest req,
|
||||||
[FromQuery] Guid? groupId,
|
[FromQuery] Guid? groupId,
|
||||||
[FromQuery] bool? isService,
|
[FromQuery] bool? isService,
|
||||||
[FromQuery] bool? isWeighed,
|
[FromQuery] Packaging? packaging,
|
||||||
[FromQuery] bool? isMarked,
|
[FromQuery] bool? isMarked,
|
||||||
[FromQuery] bool? isActive,
|
[FromQuery] bool? isActive,
|
||||||
[FromQuery] bool? hasBarcode,
|
[FromQuery] bool? hasBarcode,
|
||||||
|
|
@ -45,7 +45,7 @@ public class ProductsController : ControllerBase
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isService is not null) q = q.Where(p => p.IsService == isService);
|
if (isService is not null) q = q.Where(p => p.IsService == isService);
|
||||||
if (isWeighed is not null) q = q.Where(p => p.IsWeighed == isWeighed);
|
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 (isMarked is not null) q = q.Where(p => p.IsMarked == isMarked);
|
||||||
if (isActive is not null) q = q.Where(p => p.IsActive == isActive);
|
if (isActive is not null) q = q.Where(p => p.IsActive == isActive);
|
||||||
if (hasBarcode is not null)
|
if (hasBarcode is not null)
|
||||||
|
|
@ -149,7 +149,7 @@ public async Task<IActionResult> Delete(Guid id, CancellationToken ct)
|
||||||
p.ProductGroupId, p.ProductGroup != null ? p.ProductGroup.Name : null,
|
p.ProductGroupId, p.ProductGroup != null ? p.ProductGroup.Name : null,
|
||||||
p.DefaultSupplierId, p.DefaultSupplier != null ? p.DefaultSupplier.Name : null,
|
p.DefaultSupplierId, p.DefaultSupplier != null ? p.DefaultSupplier.Name : null,
|
||||||
p.CountryOfOriginId, p.CountryOfOrigin != null ? p.CountryOfOrigin.Name : null,
|
p.CountryOfOriginId, p.CountryOfOrigin != null ? p.CountryOfOrigin.Name : null,
|
||||||
p.IsService, p.IsWeighed, p.IsMarked,
|
p.IsService, p.Packaging, p.IsMarked,
|
||||||
p.MinStock, p.MaxStock,
|
p.MinStock, p.MaxStock,
|
||||||
p.PurchasePrice, p.PurchaseCurrencyId, p.PurchaseCurrency != null ? p.PurchaseCurrency.Code : null,
|
p.PurchasePrice, p.PurchaseCurrencyId, p.PurchaseCurrency != null ? p.PurchaseCurrency.Code : null,
|
||||||
p.ImageUrl, p.IsActive,
|
p.ImageUrl, p.IsActive,
|
||||||
|
|
@ -168,7 +168,7 @@ private static void Apply(Product e, ProductInput i)
|
||||||
e.DefaultSupplierId = i.DefaultSupplierId;
|
e.DefaultSupplierId = i.DefaultSupplierId;
|
||||||
e.CountryOfOriginId = i.CountryOfOriginId;
|
e.CountryOfOriginId = i.CountryOfOriginId;
|
||||||
e.IsService = i.IsService;
|
e.IsService = i.IsService;
|
||||||
e.IsWeighed = i.IsWeighed;
|
e.Packaging = i.Packaging;
|
||||||
e.IsMarked = i.IsMarked;
|
e.IsMarked = i.IsMarked;
|
||||||
e.MinStock = i.MinStock;
|
e.MinStock = i.MinStock;
|
||||||
e.MaxStock = i.MaxStock;
|
e.MaxStock = i.MaxStock;
|
||||||
|
|
|
||||||
|
|
@ -161,7 +161,7 @@ Guid AddGroup(string name, Guid? parentId)
|
||||||
VatEnabled = !(d.Name.Contains("Хлеб") || d.Name.Contains("Батон") || d.Name.Contains("Лепёшка")),
|
VatEnabled = !(d.Name.Contains("Хлеб") || d.Name.Contains("Батон") || d.Name.Contains("Лепёшка")),
|
||||||
ProductGroupId = d.Group,
|
ProductGroupId = d.Group,
|
||||||
CountryOfOriginId = d.Country,
|
CountryOfOriginId = d.Country,
|
||||||
IsWeighed = d.IsWeighed,
|
Packaging = d.IsWeighed ? Packaging.Weight : Packaging.Piece,
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
PurchasePrice = Math.Round(d.RetailPrice * 0.72m, 2),
|
PurchasePrice = Math.Round(d.RetailPrice * 0.72m, 2),
|
||||||
PurchaseCurrencyId = kzt.Id,
|
PurchaseCurrencyId = kzt.Id,
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ public record ProductDto(
|
||||||
Guid? ProductGroupId, string? ProductGroupName,
|
Guid? ProductGroupId, string? ProductGroupName,
|
||||||
Guid? DefaultSupplierId, string? DefaultSupplierName,
|
Guid? DefaultSupplierId, string? DefaultSupplierName,
|
||||||
Guid? CountryOfOriginId, string? CountryOfOriginName,
|
Guid? CountryOfOriginId, string? CountryOfOriginName,
|
||||||
bool IsService, bool IsWeighed, bool IsMarked,
|
bool IsService, Packaging Packaging, bool IsMarked,
|
||||||
decimal? MinStock, decimal? MaxStock,
|
decimal? MinStock, decimal? MaxStock,
|
||||||
decimal? PurchasePrice, Guid? PurchaseCurrencyId, string? PurchaseCurrencyCode,
|
decimal? PurchasePrice, Guid? PurchaseCurrencyId, string? PurchaseCurrencyCode,
|
||||||
string? ImageUrl, bool IsActive,
|
string? ImageUrl, bool IsActive,
|
||||||
|
|
@ -72,7 +72,7 @@ public record ProductInput(
|
||||||
string Name, string? Article, string? Description,
|
string Name, string? Article, string? Description,
|
||||||
Guid UnitOfMeasureId, int Vat, bool VatEnabled,
|
Guid UnitOfMeasureId, int Vat, bool VatEnabled,
|
||||||
Guid? ProductGroupId, Guid? DefaultSupplierId, Guid? CountryOfOriginId,
|
Guid? ProductGroupId, Guid? DefaultSupplierId, Guid? CountryOfOriginId,
|
||||||
bool IsService = false, bool IsWeighed = false, bool IsMarked = false,
|
bool IsService = false, Packaging Packaging = Packaging.Piece, bool IsMarked = false,
|
||||||
decimal? MinStock = null, decimal? MaxStock = null,
|
decimal? MinStock = null, decimal? MaxStock = null,
|
||||||
decimal? PurchasePrice = null, Guid? PurchaseCurrencyId = null,
|
decimal? PurchasePrice = null, Guid? PurchaseCurrencyId = null,
|
||||||
string? ImageUrl = null, bool IsActive = true,
|
string? ImageUrl = null, bool IsActive = true,
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,16 @@ public enum CounterpartyType
|
||||||
Individual = 2,
|
Individual = 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Фасовка товара: как продаётся и учитывается в остатках.
|
||||||
|
/// Piece — штучный товар (1 шт), по умолчанию. Weight — весовой (кг, г), продаётся с весов.
|
||||||
|
/// Liquid — разливной (л), продаётся из тары на разлив.</summary>
|
||||||
|
public enum Packaging
|
||||||
|
{
|
||||||
|
Piece = 1,
|
||||||
|
Weight = 2,
|
||||||
|
Liquid = 3,
|
||||||
|
}
|
||||||
|
|
||||||
public enum BarcodeType
|
public enum BarcodeType
|
||||||
{
|
{
|
||||||
Ean13 = 1,
|
Ean13 = 1,
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ public class Product : TenantEntity
|
||||||
public Country? CountryOfOrigin { get; set; }
|
public Country? CountryOfOrigin { get; set; }
|
||||||
|
|
||||||
public bool IsService { get; set; } // услуга, а не физический товар
|
public bool IsService { get; set; } // услуга, а не физический товар
|
||||||
public bool IsWeighed { get; set; } // весовой (продаётся с весов)
|
public Packaging Packaging { get; set; } = Packaging.Piece; // фасовка (штучный/весовой/разливной)
|
||||||
public bool IsMarked { get; set; } // маркируемый (Datamatrix)
|
public bool IsMarked { get; set; } // маркируемый (Datamatrix)
|
||||||
|
|
||||||
public decimal? MinStock { get; set; } // минимальный остаток (для уведомлений)
|
public decimal? MinStock { get; set; } // минимальный остаток (для уведомлений)
|
||||||
|
|
|
||||||
|
|
@ -240,7 +240,7 @@ await foreach (var p in _client.StreamProductsAsync(token, ct))
|
||||||
product.VatEnabled = vatEnabled;
|
product.VatEnabled = vatEnabled;
|
||||||
product.ProductGroupId = groupId ?? product.ProductGroupId;
|
product.ProductGroupId = groupId ?? product.ProductGroupId;
|
||||||
product.CountryOfOriginId = countryId ?? product.CountryOfOriginId;
|
product.CountryOfOriginId = countryId ?? product.CountryOfOriginId;
|
||||||
product.IsWeighed = p.Weighed;
|
product.Packaging = p.Weighed ? Packaging.Weight : Packaging.Piece;
|
||||||
product.IsMarked = !string.IsNullOrEmpty(p.TrackingType) && p.TrackingType != "NOT_TRACKED";
|
product.IsMarked = !string.IsNullOrEmpty(p.TrackingType) && p.TrackingType != "NOT_TRACKED";
|
||||||
product.IsActive = !p.Archived;
|
product.IsActive = !p.Archived;
|
||||||
product.PurchasePrice = p.BuyPrice is null ? product.PurchasePrice : p.BuyPrice.Value / 100m;
|
product.PurchasePrice = p.BuyPrice is null ? product.PurchasePrice : p.BuyPrice.Value / 100m;
|
||||||
|
|
@ -260,7 +260,7 @@ await foreach (var p in _client.StreamProductsAsync(token, ct))
|
||||||
VatEnabled = vatEnabled,
|
VatEnabled = vatEnabled,
|
||||||
ProductGroupId = groupId,
|
ProductGroupId = groupId,
|
||||||
CountryOfOriginId = countryId,
|
CountryOfOriginId = countryId,
|
||||||
IsWeighed = p.Weighed,
|
Packaging = p.Weighed ? Packaging.Weight : Packaging.Piece,
|
||||||
IsMarked = !string.IsNullOrEmpty(p.TrackingType) && p.TrackingType != "NOT_TRACKED",
|
IsMarked = !string.IsNullOrEmpty(p.TrackingType) && p.TrackingType != "NOT_TRACKED",
|
||||||
IsActive = !p.Archived,
|
IsActive = !p.Archived,
|
||||||
PurchasePrice = p.BuyPrice is null ? null : p.BuyPrice.Value / 100m,
|
PurchasePrice = p.BuyPrice is null ? null : p.BuyPrice.Value / 100m,
|
||||||
|
|
|
||||||
1875
src/food-market.infrastructure/Persistence/Migrations/20260424002000_Phase4b_ProductPackaging.Designer.cs
generated
Normal file
1875
src/food-market.infrastructure/Persistence/Migrations/20260424002000_Phase4b_ProductPackaging.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,32 @@
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace foodmarket.Infrastructure.Persistence.Migrations
|
||||||
|
{
|
||||||
|
/// <summary>Заменяем products.IsWeighed (bool) на products.Packaging (int enum):
|
||||||
|
/// 1=Piece, 2=Weight, 3=Liquid. Штучный по умолчанию.</summary>
|
||||||
|
public partial class Phase4b_ProductPackaging : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder b)
|
||||||
|
{
|
||||||
|
b.AddColumn<int>(
|
||||||
|
name: "Packaging", schema: "public", table: "products",
|
||||||
|
type: "integer", nullable: false, defaultValue: 1);
|
||||||
|
|
||||||
|
// Backfill: IsWeighed=true → Weight, иначе Piece.
|
||||||
|
b.Sql("""UPDATE public.products SET "Packaging" = CASE WHEN "IsWeighed" THEN 2 ELSE 1 END;""");
|
||||||
|
|
||||||
|
b.DropColumn(name: "IsWeighed", schema: "public", table: "products");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder b)
|
||||||
|
{
|
||||||
|
b.AddColumn<bool>(
|
||||||
|
name: "IsWeighed", schema: "public", table: "products",
|
||||||
|
type: "boolean", nullable: false, defaultValue: false);
|
||||||
|
b.Sql("""UPDATE public.products SET "IsWeighed" = ("Packaging" = 2);""");
|
||||||
|
b.DropColumn(name: "Packaging", schema: "public", table: "products");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -574,7 +574,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
|
||||||
b.Property<bool>("IsService")
|
b.Property<bool>("IsService")
|
||||||
.HasColumnType("boolean");
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
b.Property<bool>("IsWeighed")
|
b.Property<int>("Packaging")
|
||||||
.HasColumnType("boolean");
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
b.Property<decimal?>("MaxStock")
|
b.Property<decimal?>("MaxStock")
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,14 @@ export type CounterpartyType = (typeof CounterpartyType)[keyof typeof Counterpar
|
||||||
export const BarcodeType = { Ean13: 1, Ean8: 2, Code128: 3, Code39: 4, Upca: 5, Upce: 6, Other: 99 } as const
|
export const BarcodeType = { Ean13: 1, Ean8: 2, Code128: 3, Code39: 4, Upca: 5, Upce: 6, Other: 99 } as const
|
||||||
export type BarcodeType = (typeof BarcodeType)[keyof typeof BarcodeType]
|
export type BarcodeType = (typeof BarcodeType)[keyof typeof BarcodeType]
|
||||||
|
|
||||||
|
export const Packaging = { Piece: 1, Weight: 2, Liquid: 3 } as const
|
||||||
|
export type Packaging = (typeof Packaging)[keyof typeof Packaging]
|
||||||
|
export const packagingLabel: Record<Packaging, string> = {
|
||||||
|
[Packaging.Piece]: 'Штучный',
|
||||||
|
[Packaging.Weight]: 'Весовой',
|
||||||
|
[Packaging.Liquid]: 'Разливной',
|
||||||
|
}
|
||||||
|
|
||||||
export interface Country { id: string; code: string; name: string; sortOrder: number }
|
export interface Country { id: string; code: string; name: string; sortOrder: number }
|
||||||
export interface Currency { id: string; code: string; name: string; symbol: string; minorUnit: number; isActive: boolean }
|
export interface Currency { id: string; code: string; name: string; symbol: string; minorUnit: number; isActive: boolean }
|
||||||
export interface UnitOfMeasure { id: string; code: string; name: string; description: string | null; isActive: boolean }
|
export interface UnitOfMeasure { id: string; code: string; name: string; description: string | null; isActive: boolean }
|
||||||
|
|
@ -41,7 +49,7 @@ export interface Product {
|
||||||
productGroupId: string | null; productGroupName: string | null;
|
productGroupId: string | null; productGroupName: string | null;
|
||||||
defaultSupplierId: string | null; defaultSupplierName: string | null;
|
defaultSupplierId: string | null; defaultSupplierName: string | null;
|
||||||
countryOfOriginId: string | null; countryOfOriginName: string | null;
|
countryOfOriginId: string | null; countryOfOriginName: string | null;
|
||||||
isService: boolean; isWeighed: boolean; isMarked: boolean;
|
isService: boolean; packaging: Packaging; isMarked: boolean;
|
||||||
minStock: number | null; maxStock: number | null;
|
minStock: number | null; maxStock: number | null;
|
||||||
purchasePrice: number | null; purchaseCurrencyId: string | null; purchaseCurrencyCode: string | null;
|
purchasePrice: number | null; purchaseCurrencyId: string | null; purchaseCurrencyCode: string | null;
|
||||||
imageUrl: string | null; isActive: boolean;
|
imageUrl: string | null; isActive: boolean;
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import {
|
||||||
useUnits, useProductGroups, useCountries, useCurrencies, usePriceTypes, useSuppliers,
|
useUnits, useProductGroups, useCountries, useCurrencies, usePriceTypes, useSuppliers,
|
||||||
} from '@/lib/useLookups'
|
} from '@/lib/useLookups'
|
||||||
import { useOrgSettings } from '@/lib/useOrgSettings'
|
import { useOrgSettings } from '@/lib/useOrgSettings'
|
||||||
import { BarcodeType, type Product } from '@/lib/types'
|
import { BarcodeType, Packaging, type Product } from '@/lib/types'
|
||||||
|
|
||||||
interface PriceRow { id?: string; priceTypeId: string; amount: number; currencyId: string }
|
interface PriceRow { id?: string; priceTypeId: string; amount: number; currencyId: string }
|
||||||
interface BarcodeRow { id?: string; code: string; type: BarcodeType; isPrimary: boolean }
|
interface BarcodeRow { id?: string; code: string; type: BarcodeType; isPrimary: boolean }
|
||||||
|
|
@ -25,7 +25,7 @@ interface Form {
|
||||||
defaultSupplierId: string
|
defaultSupplierId: string
|
||||||
countryOfOriginId: string
|
countryOfOriginId: string
|
||||||
isService: boolean
|
isService: boolean
|
||||||
isWeighed: boolean
|
packaging: Packaging
|
||||||
isMarked: boolean
|
isMarked: boolean
|
||||||
isActive: boolean
|
isActive: boolean
|
||||||
minStock: string
|
minStock: string
|
||||||
|
|
@ -45,7 +45,7 @@ const emptyForm: Form = {
|
||||||
name: '', article: '', description: '',
|
name: '', article: '', description: '',
|
||||||
unitOfMeasureId: '', vat: defaultVat, vatEnabled: true,
|
unitOfMeasureId: '', vat: defaultVat, vatEnabled: true,
|
||||||
productGroupId: '', defaultSupplierId: '', countryOfOriginId: '',
|
productGroupId: '', defaultSupplierId: '', countryOfOriginId: '',
|
||||||
isService: false, isWeighed: false, isMarked: false, isActive: true,
|
isService: false, packaging: Packaging.Piece, isMarked: false, isActive: true,
|
||||||
minStock: '', maxStock: '',
|
minStock: '', maxStock: '',
|
||||||
purchasePrice: '', purchaseCurrencyId: '',
|
purchasePrice: '', purchaseCurrencyId: '',
|
||||||
imageUrl: '',
|
imageUrl: '',
|
||||||
|
|
@ -84,7 +84,7 @@ export function ProductEditPage() {
|
||||||
unitOfMeasureId: p.unitOfMeasureId, vat: p.vat, vatEnabled: p.vatEnabled,
|
unitOfMeasureId: p.unitOfMeasureId, vat: p.vat, vatEnabled: p.vatEnabled,
|
||||||
productGroupId: p.productGroupId ?? '', defaultSupplierId: p.defaultSupplierId ?? '',
|
productGroupId: p.productGroupId ?? '', defaultSupplierId: p.defaultSupplierId ?? '',
|
||||||
countryOfOriginId: p.countryOfOriginId ?? '',
|
countryOfOriginId: p.countryOfOriginId ?? '',
|
||||||
isService: p.isService, isWeighed: p.isWeighed, isMarked: p.isMarked,
|
isService: p.isService, packaging: p.packaging, isMarked: p.isMarked,
|
||||||
isActive: p.isActive,
|
isActive: p.isActive,
|
||||||
minStock: p.minStock?.toString() ?? '',
|
minStock: p.minStock?.toString() ?? '',
|
||||||
maxStock: p.maxStock?.toString() ?? '',
|
maxStock: p.maxStock?.toString() ?? '',
|
||||||
|
|
@ -126,7 +126,7 @@ export function ProductEditPage() {
|
||||||
defaultSupplierId: form.defaultSupplierId || null,
|
defaultSupplierId: form.defaultSupplierId || null,
|
||||||
countryOfOriginId: form.countryOfOriginId || null,
|
countryOfOriginId: form.countryOfOriginId || null,
|
||||||
isService: form.isService,
|
isService: form.isService,
|
||||||
isWeighed: form.isWeighed,
|
packaging: form.packaging,
|
||||||
isMarked: form.isMarked,
|
isMarked: form.isMarked,
|
||||||
isActive: form.isActive,
|
isActive: form.isActive,
|
||||||
minStock: form.minStock === '' ? null : Number(form.minStock),
|
minStock: form.minStock === '' ? null : Number(form.minStock),
|
||||||
|
|
@ -273,11 +273,19 @@ export function ProductEditPage() {
|
||||||
<TextInput value={form.imageUrl} onChange={(e) => setForm({ ...form, imageUrl: e.target.value })} />
|
<TextInput value={form.imageUrl} onChange={(e) => setForm({ ...form, imageUrl: e.target.value })} />
|
||||||
</Field>
|
</Field>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
<Grid cols={3}>
|
||||||
|
<Field label="Фасовка">
|
||||||
|
<Select value={form.packaging} onChange={(e) => setForm({ ...form, packaging: Number(e.target.value) as Packaging })}>
|
||||||
|
<option value={Packaging.Piece}>Штучный</option>
|
||||||
|
<option value={Packaging.Weight}>Весовой</option>
|
||||||
|
<option value={Packaging.Liquid}>Разливной</option>
|
||||||
|
</Select>
|
||||||
|
</Field>
|
||||||
|
</Grid>
|
||||||
<div className="pt-3 mt-3 border-t border-slate-100 dark:border-slate-800 flex flex-wrap gap-x-6 gap-y-2">
|
<div className="pt-3 mt-3 border-t border-slate-100 dark:border-slate-800 flex flex-wrap gap-x-6 gap-y-2">
|
||||||
<Checkbox label="НДС применяется" checked={form.vatEnabled} onChange={(v) => setForm({ ...form, vatEnabled: v })} />
|
<Checkbox label="НДС применяется (ставка выше)" checked={form.vatEnabled} onChange={(v) => setForm({ ...form, vatEnabled: v })} />
|
||||||
<Checkbox label="Услуга" checked={form.isService} onChange={(v) => setForm({ ...form, isService: v })} />
|
<Checkbox label="Услуга" checked={form.isService} onChange={(v) => setForm({ ...form, isService: v })} />
|
||||||
<Checkbox label="Весовой" checked={form.isWeighed} onChange={(v) => setForm({ ...form, isWeighed: v })} />
|
<Checkbox label="Маркируемый (Честный знак / Datamatrix)" checked={form.isMarked} onChange={(v) => setForm({ ...form, isMarked: v })} />
|
||||||
<Checkbox label="Маркируемый" checked={form.isMarked} onChange={(v) => setForm({ ...form, isMarked: v })} />
|
|
||||||
<Checkbox label="Активен" checked={form.isActive} onChange={(v) => setForm({ ...form, isActive: v })} />
|
<Checkbox label="Активен" checked={form.isActive} onChange={(v) => setForm({ ...form, isActive: v })} />
|
||||||
</div>
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ interface Filters {
|
||||||
groupId: string | null
|
groupId: string | null
|
||||||
isActive: TriFilter
|
isActive: TriFilter
|
||||||
isService: TriFilter
|
isService: TriFilter
|
||||||
isWeighed: TriFilter
|
packaging: number | null // null = все, 1/2/3 = Piece/Weight/Liquid
|
||||||
isMarked: TriFilter
|
isMarked: TriFilter
|
||||||
hasBarcode: TriFilter
|
hasBarcode: TriFilter
|
||||||
}
|
}
|
||||||
|
|
@ -26,7 +26,7 @@ const defaultFilters: Filters = {
|
||||||
groupId: null,
|
groupId: null,
|
||||||
isActive: 'yes',
|
isActive: 'yes',
|
||||||
isService: 'all',
|
isService: 'all',
|
||||||
isWeighed: 'all',
|
packaging: null,
|
||||||
isMarked: 'all',
|
isMarked: 'all',
|
||||||
hasBarcode: 'all',
|
hasBarcode: 'all',
|
||||||
}
|
}
|
||||||
|
|
@ -36,7 +36,7 @@ const toExtra = (f: Filters): Record<string, string | number | boolean | undefin
|
||||||
if (f.groupId) e.groupId = f.groupId
|
if (f.groupId) e.groupId = f.groupId
|
||||||
if (f.isActive !== 'all') e.isActive = f.isActive === 'yes'
|
if (f.isActive !== 'all') e.isActive = f.isActive === 'yes'
|
||||||
if (f.isService !== 'all') e.isService = f.isService === 'yes'
|
if (f.isService !== 'all') e.isService = f.isService === 'yes'
|
||||||
if (f.isWeighed !== 'all') e.isWeighed = f.isWeighed === 'yes'
|
if (f.packaging) e.packaging = f.packaging
|
||||||
if (f.isMarked !== 'all') e.isMarked = f.isMarked === 'yes'
|
if (f.isMarked !== 'all') e.isMarked = f.isMarked === 'yes'
|
||||||
if (f.hasBarcode !== 'all') e.hasBarcode = f.hasBarcode === 'yes'
|
if (f.hasBarcode !== 'all') e.hasBarcode = f.hasBarcode === 'yes'
|
||||||
return e
|
return e
|
||||||
|
|
@ -47,7 +47,7 @@ const activeFilterCount = (f: Filters) => {
|
||||||
if (f.groupId) n++
|
if (f.groupId) n++
|
||||||
if (f.isActive !== 'yes') n++ // 'yes' is default, count non-default
|
if (f.isActive !== 'yes') n++ // 'yes' is default, count non-default
|
||||||
if (f.isService !== 'all') n++
|
if (f.isService !== 'all') n++
|
||||||
if (f.isWeighed !== 'all') n++
|
if (f.packaging) n++
|
||||||
if (f.isMarked !== 'all') n++
|
if (f.isMarked !== 'all') n++
|
||||||
if (f.hasBarcode !== 'all') n++
|
if (f.hasBarcode !== 'all') n++
|
||||||
return n
|
return n
|
||||||
|
|
@ -137,7 +137,19 @@ export function ProductsPage() {
|
||||||
<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">
|
<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.isActive} onChange={(v) => { setFilters({ ...filters, isActive: v }); setPage(1) }} />
|
||||||
<Tri label="Услуга" value={filters.isService} onChange={(v) => { setFilters({ ...filters, isService: v }); setPage(1) }} />
|
<Tri label="Услуга" value={filters.isService} onChange={(v) => { setFilters({ ...filters, isService: v }); setPage(1) }} />
|
||||||
<Tri label="Весовой" value={filters.isWeighed} onChange={(v) => { setFilters({ ...filters, isWeighed: v }); setPage(1) }} />
|
<div className="flex items-center gap-2 text-xs">
|
||||||
|
<span className="text-slate-500">Фасовка</span>
|
||||||
|
<select
|
||||||
|
value={filters.packaging ?? ''}
|
||||||
|
onChange={(e) => { const v = e.target.value; setFilters({ ...filters, packaging: v ? Number(v) : null }); setPage(1) }}
|
||||||
|
className="rounded border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-900 px-2 py-0.5 text-xs"
|
||||||
|
>
|
||||||
|
<option value="">все</option>
|
||||||
|
<option value="1">штучный</option>
|
||||||
|
<option value="2">весовой</option>
|
||||||
|
<option value="3">разливной</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
<Tri label="Маркируемый" value={filters.isMarked} onChange={(v) => { setFilters({ ...filters, isMarked: v }); setPage(1) }} />
|
<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="нет" />
|
<Tri label="Со штрихкодом" value={filters.hasBarcode} onChange={(v) => { setFilters({ ...filters, hasBarcode: v }); setPage(1) }} yesLabel="есть" noLabel="нет" />
|
||||||
{activeCount > 0 && (
|
{activeCount > 0 && (
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue