- Currency.IsActive удалён полностью (domain/DTO/API/web/миграция).
Валюты — глобальный справочник; «архивировать» USD глобально
бессмысленно, а per-tenant видимости у валют нет.
- MinorUnit остаётся в БД (нужен для форматирования цен), но скрыт
из UI: убран CurrencyDto.MinorUnit, CurrencyInput.MinorUnit,
колонка «Знаки» из списка.
- Форма валюты — 3 поля: Код / Название / Символ.
- Миграция Phase5e_DropCurrencyIsActive дропает колонку.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Единственная роль галки «В том числе НДС» на товаре — показать/скрыть
поле «Ставка НДС %». Никакой семантики «в том числе/сверху» на товаре
не живёт — это логика документа (продажи/поставки).
- Product.Vat: int → decimal (миграция Phase5d_ProductVatDecimal меняет
тип колонки на numeric(5,2)).
- ProductDto/ProductInput: decimal? Vat.
- ResolveDefaultVatAsync, seeders, MoySklad import — decimal.
- MoySklad import: если vatEnabled пришёл — уважаем, иначе прежний
fallback «vat=0 → без НДС».
- UI: вместо жёсткого Select [0,10,12,16,20] — TextInput number step=0.01;
поле рендерится только когда form.vatEnabled=true; дефолт для нового
товара подставляется из Country.VatRate организации.
- В таблице товаров ставка печатается с 2 знаками (16.00%).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Во всех таблицах можно сортировать по клику на заголовок столбца:
первый клик — по возрастанию (↑), второй — по убыванию (↓),
смена колонки сбрасывает предыдущую. Без активной сортировки —
серверный default (обычно по Name ASC).
Реализация:
- PagedRequest: добавлены Sort (ключ колонки) и Order ("asc"/"desc"),
плюс удобное свойство Desc.
- DataTable: Column.sortKey + props sortKey/sortOrder/onSortChange,
в заголовке появляется иконка (ArrowUpDown/ArrowUp/ArrowDown).
- useCatalogList: хранит sortKey/sortOrder, отдаёт setSort, шлёт
?sort=&order= в query-string.
- Все 10 List-эндпоинтов (Countries, Currencies, UnitsOfMeasure,
PriceTypes, Stores, RetailPoints, Counterparties, ProductGroups,
Products, Supplies, RetailSales + Stock/Movements) принимают
параметры и применяют switch-based OrderBy по whitelisted ключам.
- Все страницы со списками прокидывают sort state и sortKey на
колонках, где сортировка имеет смысл (тексты/числа/даты).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Country.SortOrder удалено из домена/DTO/API/seeder/web/UI.
- Миграция Phase5b_DropCountrySortOrder дропает колонку.
- Список стран сортируется по Name ASC.
- В форме: поле «Порядок» убрано.
- В таблице: убрана колонка «Порядок», ширины колонок сжаты по
содержимому (Код 80px, Валюта 120px, НДС 100px, Название flex).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase5_VatAsCountryProperty:
- countries.VatRate (numeric(5,2)) — ставка страны, источник правды.
Seed: KZ=16, RU=20, BY=20, DE=19, CN=13, TR=18, UZ=12, KG=12, KR=10,
IT=22, PL=23, US=0.
- organizations.ShowVatEnabledOnProduct (bool, default false) — флаг
отображения на карточке товара.
- organizations.DefaultVat удалён (заменён страной).
- products.Vat ОСТАЁТСЯ: для KZ есть льготные категории (хлеб/молоко =
0%) и фискальный чек требует ставку на каждой позиции.
Country domain: + DefaultCurrency / VatRate (уже было DefaultCurrencyId
из Phase4, сейчас дополнено).
Organization domain: DefaultVat убран, ShowVatEnabledOnProduct добавлен.
Backend:
- ProductInput.Vat теперь int? — если UI скрывает поле и прислал null,
ProductsController берёт дефолт из страны организации (Country.VatRate
при создании; при update сохраняет прежнее значение).
- CountriesController.List/Get/Create/Update возвращает/принимает
DefaultCurrency и VatRate.
- MoySklad импорт: дефолт Vat загружается из страны организации.
- SystemReferenceSeeder: новые валюты BYN/UZS/KGS/TRY/KRW/PLN, seed
country-currency-vat для всех 12 стран.
- OrganizationSettingsController: VatRate read-only из страны,
ShowVatEnabledOnProduct редактируется.
Web:
- Country type + CountriesPage форма редактирования (валюта, ставка НДС).
- OrganizationSettingsPage: "Ставка НДС" read-only
(берётся из страны, ссылка на /catalog/countries), галочка
"Указывать ставку НДС на товаре".
- ProductEditPage: блок Ставка НДС % + галка "В том числе НДС" теперь
показываются только если showVatEnabledOnProduct=true. В payload
при save.mutate отправляется vat=null если скрыто.
- ProductsPage: колонка НДС показывается только при включённом флаге.
Galleries/products/settings других этапов — не задеты.
Main расходился с БД стейджа (Phase2c3_MsStrict в history, но код ещё ссылался на VatRate etc.) — деплой ломался. Реплицирую удаление сущностей вручную, чтобы код совпадал с таблицами.
Убрано (нет в MoySklad — не выдумываем):
- Domain: VatRate сущность целиком; Counterparty.Kind + enum CounterpartyKind; Store.Kind + enum StoreKind; Product.IsAlcohol; UnitOfMeasure.Symbol/DecimalPlaces/IsBase.
- EF: DbSet<VatRate>, ConfigureVatRate, Product.VatRate navigation, индекс Counterparty.Kind.
- DTO/Input: соответствующие поля и VatRateDto/Input.
- API: VatRatesController удалён; references в Products/Counterparties/Stores/UoM/Supplies/Retail/Stock.
Добавлено как в MoySklad:
- Product.Vat (int) + Product.VatEnabled — MoySklad держит НДС числом на товаре.
- KZ default VAT 16% — applied в сидерах и в MoySkladImportService когда товар не принёс свой vat.
MoySkladImportService:
- ResolveKind убран; CompanyType=entrepreneur→Individual (как и было).
- VatRates lookup → прямой p.Vat ?? 16 + p.Vat > 0 для VatEnabled.
- baseUnit ищется по code="796" вместо IsBase.
Web:
- types.ts: убраны CounterpartyKind/StoreKind/VatRate/Product.vatRateId/vatPercent/isAlcohol/UoM.symbol/decimalPlaces/isBase; добавлено Product.vat/vatEnabled; унифицировано unitSymbol→unitName.
- VatRatesPage удалён, роут из App.tsx тоже.
- CounterpartiesPage/StoresPage/UnitsOfMeasurePage: убраны соответствующие поля в формах.
- ProductEditPage: select "Ставка НДС" теперь с фиксированными 0/10/12/16/20 + чекбокс VatEnabled.
- Stock/RetailSale/Supply pages: unitSymbol → unitName.
deploy-stage unguarded — теперь код соответствует DB, авто-deploy безопасен.
Domain:
- foodmarket.Domain.Inventory.Stock — materialized aggregate per (Product, Store)
with Quantity, ReservedQuantity, computed Available. Unique index on tenant+
product+store.
- foodmarket.Domain.Inventory.StockMovement — append-only journal with signed
quantity, optional UnitCost, MovementType enum (Initial, Supply, RetailSale,
WholesaleSale, CustomerReturn, SupplierReturn, TransferOut, TransferIn,
WriteOff, Enter, InventoryAdjustment), document linkage (type, id, number),
OccurredAt, CreatedBy, Notes.
Application:
- IStockService.ApplyMovementAsync draft — appends movement row + upserts
materialized Stock row in the same unit of work. Callers control SaveChanges
so a posting doc can bundle all lines atomically.
Infrastructure:
- StockService implementation over AppDbContext.
- InventoryConfigurations EF mapping (precision 18,4 on quantities/costs;
indexes for product+time, store+time, document lookup).
- Migration Phase2a_Stock applied to dev DB (tables stocks, stock_movements).
API (GET, read-only for now):
- /api/inventory/stock — filter by store, product, includeZero; joins product +
unit + store names; server-side pagination.
- /api/inventory/movements — journal filtered by store/product/date range;
movement type as string enum for UI labels.
- Both [Authorize] (any authenticated user).
MoySklad:
- MsCounterparty DTO (name, legalTitle, inn, kpp, companyType, tags...).
- MoySkladClient.StreamCounterpartiesAsync — paginated like products.
- MoySkladImportService.ImportCounterpartiesAsync — maps tags → Kind (supplier /
customer / both), companyType → LegalEntity/Individual; dedup by Name;
defensive trim on all string fields; per-item try/catch; batches of 500.
- /api/admin/moysklad/import-counterparties endpoint (Admin policy).
Web:
- /inventory/stock list page (store filter, include-zero toggle, search; shows
quantity/reserved/available with red-on-negative, grey-on-zero accents).
- /inventory/movements list page (store filter; colored quantity +/-, Russian
labels for each movement type).
- MoySklad import page restructured: single token test + two import buttons
(Товары, Контрагенты) + reusable ImportResult panel that handles both.
- Sidebar: new "Остатки" group with Остатки + Движения; icons Boxes + History.
Uses the ListPageShell pattern introduced in d3aa13d — sticky top bar, sticky
table header, only the body scrolls.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Application layer:
- PagedRequest/PagedResult<T> with sane defaults (pageSize 50, max 500)
- CatalogDtos: read DTOs with joined names + input DTOs for upsert
- Product input supports nested Prices[] and Barcodes[] for atomic save
API controllers (api/catalog/…):
- countries, currencies (global, write requires SuperAdmin)
- vat-rates, units-of-measure, price-types, stores (write: Admin/Manager)
- retail-points (references Store, Admin/Manager write)
- product-groups: hierarchy with auto-computed Path, delete guarded against children/products
- counterparties: filter by kind (Supplier/Customer/Both), full join with Country
- products: includes joined lookups, filter by group/isService/isWeighed/isActive,
search by name/article/barcode, write replaces Prices+Barcodes atomically
Role semantics:
- SuperAdmin: mutates global references only
- Admin: mutates/deletes tenant references
- Manager: mutates tenant references (no delete on some)
- Storekeeper: can manage counterparties and products (but not delete)
All endpoints guarded by [Authorize]. Multi-tenant isolation via EF query filter.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>