food-market/docs/audit-moysklad.md
nurdotnet 55a63a6446 docs: audit of our domain entities vs. live OtherSystem API
Cross-checked every entity (Product, Counterparty, Supply, RetailSale,
Stock, Store, RetailPoint, Organization, ProductGroup, Barcode, Price,
PriceType, Country, Currency, VatRate, UoM) against real responses from
OtherSystem's API — a flat list of:
 - fields we have and MS doesn't (to justify or drop)
 - fields MS has and we don't (to add)
 - semantic mismatches (e.g. MS holds prices in kopecks, our decimal)

Report only, no code changes — to be discussed with the user before
touching models/migrations. Priorities are split into P1 (import
parity: ExternalCode, Code, TrackingType enum, PaymentItemType, KZ
entrepreneur type), P2 (semantic fixes: RetailSale payment sums,
Overhead on supply, legal fields on Organization), P3 (nice-to-have),
and a list of deliberate divergences (why our VatRate/StockMovement
exist even though MS doesn't model them that way).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 12:57:06 +05:00

33 KiB
Raw Blame History

Аудит наших доменных сущностей vs. MoySklad API

Источник правды — живой MoySklad API /api/remap/1.2/. Проверялись ключи на реальных ответах (?limit=2 на нашем аккаунте). Цель: каждая наша сущность должна либо повторять MoySklad, либо иметь явно оправданное отличие. Никаких «выдуманных» полей.

Условные обозначения:

  • ⛔ — у нас есть поле, которого нет у MoySklad → либо оправдать комментарием, либо удалить.
  • ➕ — у MoySklad есть поле, которого нет у нас → потенциально добавить.
  • ⚠️ — важный нюанс (тип, семантика, обязательность).

Counterparty → entity/counterparty

Ключи MoySklad (из ответа API, верхний уровень): accountId, accounts, archived, bonusPoints, bonusProgram, companyType, created, externalCode, files, group, id, meta, name, notes, owner, salesAmount, shared, state, tags, updated + расширяемые: legalTitle, legalAddress, inn, kpp, ogrn, ogrnip, certificateNumber, certificateDate, phone, email, actualAddress, description, discountCardNumber, priceType, sex, salesChannel.

Наше поле MoySklad Комментарий
Name name ОК
LegalName legalTitle rename? или доп. комментарий-алиас
Kind (CounterpartyKind) нет ⛔ уже исправили enum (Unspecified/Supplier/Customer/Both), но MoySklad не имеет этого поля вообще — он различает контрагентов через tags или через state (статус в пайплайне продаж/закупок). TODO: либо оставить Kind только как UI-фильтр (не импортировать из MoySklad), либо перейти на теги
Type (LegalEntity/Individual) companyType ⚠️ у MoySklad 3 значения: legal, individual, entrepreneur (ИП!). У нас ИП отсутствует — добавить IndividualEntrepreneur в enum (для РК актуально)
Bin (БИН, РК) inn (12-значный БИН пишется туда) ⚠️ MoySklad для всех рынков использует inn — 12 цифр это ИИН РФ, 12 цифр РК — БИН. Мы вынесли Bin отдельно, при импорте MoySklad кладёт в inn. TODO: документировать маппинг Bin ↔ inn
Iin (ИИН, РК) inn (тот же) ⚠️ same — MoySklad не различает
TaxNumber inn дубль
CountryId country (extended, по meta) ⚠️ MoySklad не на верхнем уровне — тянется при ?expand=country
Address actualAddress ОК
Phone phone ОК
Email email ОК
BankName, BankAccount, Bik accounts (массив объектов) ⚠️ у MoySklad это коллекция счетов (до нескольких банков). У нас одиночные поля — либо сделать коллекцию Accounts, либо документировать "берём первый"
ContactPerson contactpersons (sub-endpoint) ⚠️ у MoySklad это отдельный endpoint counterparty/{id}/contactpersons — массив. У нас скалярное поле
Notes description (или notes разные в разных версиях API?) ⚠️ в ответе API было notes — ОК
IsActive archived (inverse) ОК
— tags (массив) ➕ добавить — удобно для классификации (в том числе заменой Kind)
— state (ссылка на состояние в пайплайне) ➕ отложить до Phase N (CRM)
— bonusPoints, bonusProgram, discountCardNumber ➕ отложить до дисконтных карт
— salesAmount (вычисляемое) не храним
— priceType (персональный тип цены) ➕ полезно для опта; добавить Guid? DefaultPriceTypeId

TODO:

  1. Enum CounterpartyType: добавить IndividualEntrepreneur = 3.
  2. Коллекция CounterpartyAccount (BankName/BankAccount/Bik/IsDefault) — или явный комментарий «храним только основной».
  3. Коллекция CounterpartyTag (string) — для классификации при импорте из MoySklad.
  4. Поле DefaultPriceTypeId → PriceType (для опта/персональной цены).
  5. Комментарий на Bin/Iin/TaxNumber: при импорте из MoySklad все три могут прилететь из одного поля inn — логика различения по длине (12 цифр РК-формат) / по companyType.

Organization → entity/organization

Ключи MS: accountId, accounts, archived, bonusPoints, bonusProgram, companyType, companyVat__ru, created, email, externalCode, group, id, isEgaisEnable, meta, name, owner, payerVat, shared, updated + extended: legalTitle, legalAddress, actualAddress, inn, kpp, ogrn, ogrnip, okpo, director, chiefAccountant, phone, fax, utmUrl.

Наше MS Комментарий
Name name ОК
CountryCode нет ⛔ у MoySklad нет — у них multi-tenant через account. У нас — multi-tenant через Organization, но CountryCode неочевиден. Оставить как есть, документировать почему (нам нужно для налоговых/локальных настроек)
Bin inn то же что и Counterparty
Address actualAddress ОК
Phone phone ОК
Email email ОК
IsActive archived inverse ОК
— legalTitle, legalAddress ➕ для офиц. документов
— kpp, ogrn, ogrnip, okpo РФ-специфично, пропускаем для РК
— payerVat (bool, плательщик НДС) ➕ полезно — есть ли НДС у нашей организации
— director, chiefAccountant ➕ для подписей на накладных
— accounts (банковские) ➕ аналогично Counterparty
— isEgaisEnable РФ, пропускаем

TODO:

  1. LegalName, LegalAddress, PayerVat (bool), DirectorName, ChiefAccountantName — для накладных/счетов.
  2. CountryCode оставить + <see langword="…"/> комментарий почему у нас есть, а у MS нет.

Product → entity/product

Ключи MS: accountId, archived, barcodes, buyPrice, code, discountProhibited, externalCode, files, group, id, images, isSerialTrackable, meta, minPrice, name, owner, pathName, paymentItemType, productFolder, salePrices, shared, supplier, trackingType, uom, updated, useParentVat, variantsCount, volume, weight + optional: article, country, description, effectiveVat, minPrice.currency, taxSystem, vat, tnved, syncId, modifications.

Наше MS Комментарий
Name name ОК
Article article ОК
Description description ОК
UnitOfMeasureId uom.meta ОК
VatRateId vat (число) + useParentVat ⚠️ у MS НДС хранится как число (20, 10, 12, 0) прямо на товаре. Мы отдельная сущность VatRate. Обоснование: нам нужно хранить локализованные названия ("НДС 12%", "Без НДС"), is-default, и позволять разным организациям иметь разные ставки. НО — при импорте надо резолвить число в VatRate по organization_id
ProductGroupId productFolder.meta ОК
DefaultSupplierId supplier.meta ОК (у MS тоже одиночная ссылка)
CountryOfOriginId country.meta ОК
IsService paymentItemType (одно из значений = "SERVICE") ⚠️ у MS это enum с ~10 значений; у нас bool. TODO: либо enum, либо документировать что мы учитываем только IsService
IsWeighed нет ⛔ у MS этого нет; характеристика ритейла, нам нужно для касс с весами. Оставить, документировать.
IsAlcohol tnved (класс товара) или через group ⚠️ у MS через tnved-код или through type классификаторы. Наше bool — упрощение. Оставить с комментарием.
IsMarked trackingType (enum: NOT_TRACKED, BEER_ALCOHOL, …) ⚠️ У MS это enum из 10+ вариантов маркировки. Наш IsMarked: bool — потеря информации. TODO: заменить на enum TrackingType (NOT_TRACKED/TOBACCO/ALCOHOL/SHOES/MEDICINE/…)
MinStock, MaxStock minimumBalance (число), stock (runtime) ⚠️ у MS есть только minimumBalance (нижняя граница). MaxStock — наш
PurchasePrice, PurchaseCurrencyId buyPrice.value, buyPrice.currency.meta ОК (MS упаковывает в объект, мы разнесли — одно и то же)
ImageUrl images (массив через sub-endpoint) ⚠️ у MS images коллекция, у нас одна + отдельная ProductImage. ОК, двойная запись для UX
IsActive archived inverse ОК
Prices (collection) salePrices (массив inline в MS) ⚠️ у MS цены — массив внутри товара, у нас — отдельная таблица. Оба норм; просто маппинг при sync
Barcodes (collection) barcodes (массив inline) ОК
Images (collection) images (sub-endpoint) ОК
— code ➕ внутренний код (отличается от article). Добавить Code
— externalCode ➕ используется при импорте/ERP-интеграциях. Добавить ExternalCode (актуально для импорта из MoySklad, 1C)
— discountProhibited ➕ «запрет скидок» — полезно на кассе
— minPrice.value/currency ➕ минимальная отпускная цена. Добавить MinPrice + MinPriceCurrencyId
— paymentItemType ➕ для фискализации: «товар/услуга/работа/подарочная карта/…». Добавить enum PaymentItemType (нужно для 54-ФЗ / КZ fiscal receipts)
— tnved ➕ код ТН ВЭД для трансграничной торговли
— volume, weight ➕ для логистики (доставка)
— variantsCount runtime агрегат, не храним
— files ➕ вложения (паспорта качества, фото упаковки) — отложить

TODO:

  1. Добавить Code, ExternalCode на Product.
  2. Заменить IsMarked на enum TrackingType.
  3. Добавить MinPrice, MinPriceCurrencyId.
  4. Добавить enum PaymentItemType + поле.
  5. Поля Volume, Weight, DiscountProhibited.
  6. Запомнить маппинг: useParentVat → наследовать НДС от ProductGroup (у нас сейчас не реализовано, надо подумать).

ProductGroup → entity/productfolder

Ключи MS: accountId, archived, externalCode, group, id, meta, name, owner, pathName, shared, updated, useParentVat + vat, effectiveVat, productFolder (родитель).

Наше MS Комментарий
Name name ОК
ParentId productFolder.meta ОК (MS использует то же имя для родителя что и для самой сущности)
Path pathName ОК
SortOrder нет ⛔ у MS нет сортировки групп. Оставить, это UX
IsActive archived inverse ОК
— externalCode ➕ для импорта
— vat, useParentVat ➕ ставка НДС по умолчанию для товаров группы

TODO:

  1. Добавить ExternalCode.
  2. Добавить VatRateId? + UseParentVat: bool (для наследования).

ProductBarcode → product.barcodes[]

У MS barcode — объект внутри product: {type: 'ean13'|'ean8'|'code128'|'upc'|'gtin', value: '...'}. Отдельной сущности нет.

Наше MS Комментарий
Code value ОК
Type type ⚠️ MS использует строки ('ean13', 'gtin', …) — мы уже enum
IsPrimary нет ⛔ у MS нет — первый считается основным. Оставить с комментарием — у нас явная пометка.

OK, расхождений существенных нет.


ProductPrice → product.salePrices[]

У MS цены — массив объектов в product: {value, currency: {meta}, priceType: {meta}}. Отдельной сущности нет.

Наше — отдельная таблица. Это нормализованный вариант — оправдано если цен много и есть выборки по PriceType. TODO: маппинг при импорте — проитерировать salePrices и создать ProductPrice per PriceType.


PriceType → entity/pricetype

Ключи MS (из context): id, name, externalCode.

Наше MS Комментарий
Name name ОК
IsDefault нет ⛔ у MS — default определяется порядком или отдельно в настройках аккаунта. Оставить
IsRetail нет ⛔ наш флаг «используется на кассе». Оставить
SortOrder нет ⛔ UX. Оставить
— externalCode ➕ для импорта

TODO:

  1. ExternalCode.

Country → entity/country

Ключи MS: code, description, externalCode, id, meta, name, updated.

Наше MS Комментарий
Code code ⚠️ у MS формат ISO3166-1 alpha-2 или числовой — у нас alpha-2
Name name ОК
SortOrder нет ⛔ UX
— description ➕
— externalCode ➕

OK, мелочь.


Currency → entity/currency

Ключи MS: archived, code, default, fullName, id, indirect, isoCode, majorUnit, meta, minorUnit, multiplicity, name, rate, rateUpdateType, system.

Наше MS Комментарий
Code isoCode или code ⚠️ у MS isoCode (строка "KZT") и code (цифровой "398") — у нас Code = строка ISO
Name name ОК
Symbol нет ⛔ у MS нет символа "₸" — но это UX. Оставить
MinorUnit minorUnit ОК
IsActive archived inverse ОК
— default (валюта аккаунта) ➕
— rate, rateUpdateType ➕ курс к базовой валюте (при мульти-валютности)
— multiplicity, indirect конвертация; если не мульти-валютные — не надо
— fullName ➕ «Тенге Казахстана» vs «KZT»

TODO:

  1. Добавить IsDefault: bool (ровно одна валюта = true per tenant, или глобально).
  2. Rate, RateUpdateType + FullName — отложить до мульти-валютности.

VatRate — у MoySklad нет entity/vatrate

⚠️ У MS ставки НДС хранятся как числовое поле на товаре (vat). Отдельной таблицы нет — набор значений {0, 5, 7, 10, 12, 18, 20} и «без НДС» встроен в систему.

Наше VatRate — отдельная сущность. Обоснование сохранить:

  1. Локализованное название ("НДС 12%", "Без НДС").
  2. IsDefault per organization.
  3. Разные организации в разных налоговых режимах (с НДС / УСН).
  4. При добавлении новой ставки (например, на случай гипотетического увеличения в РК) — не править перечисление в коде.

Но: следите, чтобы у товара хранился VatRateId, а не отдельно vat: decimal. При импорте из MS мапим число в запись VatRate.

Комментарий в коде нужен — явно сказать, что мы отклонились от MoySklad сознательно.


UnitOfMeasure → entity/uom

Ключи MS: code, description, externalCode, id, meta, name, updated.

Наше MS Комментарий
Code (ОКЕИ) code ОК, MS использует ОКЕИ-коды (796, 166, 112)
Symbol нет ⛔ у MS только name ("штука"). Мы вынесли "шт" отдельно для коротких надписей на ценниках/кассовых чеках. Оставить.
Name name ОК
DecimalPlaces нет ⛔ у MS на уровне продукта (variantsCount?), а не UoM. Наш DecimalPlaces определяет можно ли дробные количества (0=штучный, 3=весовой). Оставить — важно для UX касс.
IsBase нет ⛔ наше «базовая единица организации». Мелочь, оставить
IsActive archived inverse (у MS есть archived в uom? перепроверить) ⚠️ в нашем ответе API archived не было — у MS uom этого поля может не быть, потому что единицы системные
— description ➕
— externalCode ➕

TODO:

  1. ExternalCode.

Store → entity/store

Ключи MS: accountId, address, archived, externalCode, group, id, meta, name, owner, pathName, shared, slots, updated, zones.

Наше MS Комментарий
Name name ОК
Code externalCode? или отдельно? ⚠️ у MS только externalCode. Добавить ExternalCode или rename Code→ExternalCode
Kind (Warehouse/RetailFloor) нет ⛔ у MS такого деления нет. Обоснование: нам нужно отличать «склад» от «торгового зала» для UI и настроек касс. Оставить с комментарием
Address address ОК
Phone нет ⛔ у MS нет. Оставить
ManagerName нет ⛔ у MS нет. Оставить
IsMain нет (но можно проставить через default) ⛔ Оставить
IsActive archived inverse ОК
— pathName ➕ (если будут иерархические склады)
— slots (ячейки склада) ➕ отложить
— zones (зоны склада) ➕ отложить

TODO:

  1. ExternalCode (или переименовать Code → ExternalCode).

RetailPoint → entity/retailstore

У MS это «Точка продаж» / кассовое место. Огромное количество полей (~60) — в основном фискальные настройки.

Наше MS Комментарий
Name name ОК
Code externalCode rename or add
StoreId store.meta ОК
Address нет (возможно organization.actualAddress) ⛔ адрес не у точки, а у организации/склада. Пересмотреть, куда класть
Phone нет ⛔
FiscalSerial нет такого поля; есть fiscalType, fiscalMemoryNumber?, ofdEnabled ⚠️ у MS фискальные настройки множественные. У нас один скаляр — упрощение. TODO: уточнить по мере подключения ККМ
FiscalRegNumber ofdEnabled + ofdSettings same
IsActive active, archived MS различает active и archived — у нас только IsActive
— priceType.meta ➕ тип цены для этой точки — важно
— allowCustomPrice ➕ разрешить ручную цену на кассе
— allowCreateProducts ➕ создать товар прямо на кассе
— discountEnable, discountMaxPercent ➕ скидки на кассе
— cashiers (коллекция) ➕ кто может работать за кассой
— sellReserves ➕ продавать резерв
— receiptTemplate ➕ шаблон чека
— returnFromClosedShiftEnabled ➕ возврат из закрытой смены
— requiredBirthdate/Email/Phone/Fio/Sex/DiscountCardNumber ➕ обязательные поля при продаже
— markingSellingMode, marksCheckMode, sendMarksForCheck ➕ маркировка товаров

TODO:

  1. Обязательно: DefaultPriceTypeId (ссылка на PriceType).
  2. Настройки кассы (скоп Phase 3 — касса): AllowCustomPrice, AllowCreateProducts, SellReserves, DiscountMaxPercent, RequireCustomer... — добавлять по мере реализации POS.
  3. Коллекция RetailPointCashier (user_id, может ли работать).

Supply → entity/supply + supply/{id}/positions

Document keys: accountId, agent, applicable, created, externalCode, files, group, id, meta, moment, name, organization, owner, payedSum, positions, printed, published, rate, shared, store, sum, updated, vatEnabled, vatIncluded, vatSum.

Наше MS Комментарий
Number name ⚠️ у MS «номер документа» = name. У нас Number — семантически то же
Date moment ОК
Status (Draft/Posted) applicable (bool) ⚠️ у MS это bool «проведён или нет». У нас enum Draft/Posted → эквивалентно
SupplierId agent.meta ⚠️ у MS вместо supplier общее слово agent (контрагент)
StoreId store.meta ОК
CurrencyId rate.currency.meta ⚠️ MS упаковывает в rate объект с курсом
SupplierInvoiceNumber нет на верхнем уровне; есть в attributes ⛔ у MS через custom attributes. Оставить
SupplierInvoiceDate same same
Notes description rename или комментарий
Total sum ОК
PostedAt updated (когда applicable ставится true) ⚠️ у MS нет выделенного поля; мы отдельно фиксируем
PostedByUserId owner.meta условно
— vatEnabled ➕
— vatIncluded ➕ НДС включён в цену
— vatSum ➕ суммарный НДС документа
— payedSum ➕ сколько оплачено
— organization.meta ⚠️ у MS документ привязан к организации. У нас TenantEntity несёт OrganizationId — уже есть
— printed, published ➕ распечатан/опубликован
— overhead (доп.расходы) ➕ доставка/таможня — важно для фактической себестоимости

Supply.Positions (SupplyLine) → supply/{id}/positions:

Ключи MS: accountId, assortment, discount, id, meta, overhead, price, quantity, vat, vatEnabled.

Наше (SupplyLine) MS position Комментарий
ProductId assortment.meta ⚠️ у MS assortment = может быть product ИЛИ variant ИЛИ service ИЛИ bundle. Мы только продукт
Quantity quantity ОК
UnitPrice price ⚠️ у MS price — в копейках (integer 100 = 1.00). У нас decimal. Маппинг при импорте: делить на 100
LineTotal нет (вычисляется) ⛔ у MS не хранится
SortOrder нет ⛔ наш UX
— discount ➕ строковая скидка
— vat ➕ ставка НДС на позицию
— vatEnabled ➕
— overhead ➕ доля накладных (для себестоимости)

TODO Supply:

  1. Поля: VatEnabled, VatIncluded, VatSum, PayedSum, Overhead.
  2. Lines: Discount (decimal), VatPercent (snapshot, уже подобное есть в RetailSaleLine), VatEnabled.
  3. Комментарий: MS price в копейках — при импорте делить.

RetailSale → entity/retaildemand + retaildemand/{id}/positions

Document keys: огромный список, ключевое: agent, applicable, cashSum, noCashSum, qrSum, prepaymentCashSum, prepaymentNoCashSum, prepaymentQrSum, advancePaymentSum, fiscal, retailShift, retailStore, store, positions, rate, sum, vatEnabled, vatIncluded, vatSum, name, moment, organization, syncId.

Наше MS Комментарий
Number name ОК
Date moment ОК
Status applicable ⚠️ bool vs enum
StoreId store.meta ОК
RetailPointId retailStore.meta ОК
CustomerId agent.meta ОК (nullable если не знаем покупателя)
CashierUserId нет напрямую; retailShift → cashier ⚠️
CurrencyId rate.currency.meta ОК
Subtotal, DiscountTotal, Total sum (= Total) ⚠️ MS не хранит subtotal и discount total отдельно — только total. Но цена в позиции уже после скидки? Нет — positions[].discount хранится, total = sum(price*qty - discount)
Payment (PaymentMethod enum) cashSum + noCashSum + qrSum ⚠️ MS — не enum, а суммы по видам оплаты. Т.е. при mixed-оплате можно часть наличными + часть картой. Наш enum Payment + PaidCash + PaidCard — неполный. TODO: добавить PaidQr + убрать enum в пользу «сколько чем заплачено»
PaidCash cashSum ⚠️ у MS в копейках
PaidCard noCashSum ⚠️ в копейках
Notes description ОК
PostedAt — наш
PostedByUserId owner.meta условно
— qrSum ➕ добавить PaidQr (QR-оплата актуальна для КZ)
— retailShift.meta ➕ кассовая смена (отложить)
— fiscal ➕ пробит ли фискально
— syncId ➕ идентификатор для офлайн-касс (при резинхроне)
— prepaymentCashSum/NoCashSum/QrSum, advancePaymentSum ➕ предоплаты

RetailSale.Positions (RetailSaleLine) → retaildemand/{id}/positions:

Ключи: accountId, assortment, discount, id, meta, price, quantity, vat, vatEnabled.

Наше (RetailSaleLine) MS Комментарий
ProductId assortment.meta ОК
Quantity quantity ОК
UnitPrice price ⚠️ копейки
Discount discount ОК
LineTotal вычисляется наш
VatPercent vat ОК (snapshot)
SortOrder — наш UX
— vatEnabled ➕

TODO RetailSale:

  1. Добавить PaidQr: decimal.
  2. Убрать PaymentMethod enum в пользу денормализованных PaidCash, PaidCard, PaidQr, PaidBonus + computed Method (если все кроме одного = 0 → Cash/Card/QR, иначе Mixed). Либо оставить enum + PayHint, но быть готовым к частичной оплате.
  3. VatEnabled, VatIncluded, VatSum (сумма НДС на документ — вычисляется).
  4. Комментарий: MS price/cashSum/noCashSum в копейках при импорте.

Stock → report/stock/bystore

У MS нет отдельной сущности "Stock" — это отчёт. Ответ report/stock/bystore содержит:

{ "meta": {...}, "stockByStore": [ { "name": "Склад №1", "meta": {...}, "stock": 10.0, "reserve": 2.0, "inTransit": 0.0, "quantity": 12.0 } ] }

Т.е. по каждому (product, store) — stock (сколько есть), reserve (резерв), inTransit (в пути), quantity = stock+inTransit.

У нас Stock — материализованный агрегат (Quantity, ReservedQuantity, computed Available). Это технически наше решение, не требование бизнеса.

TODO:

  1. Комментарий в Stock.cs: объяснить, что это материализация (у MS динамический репорт).
  2. Добавить InTransit: decimal — товар в пути (между складами при перемещении).

StockMovement — у MoySklad такой сущности нет

⚠️ MS не хранит journal движений в явном виде — остатки рассчитываются в реальном времени из документов (supply, retaildemand, loss, enter, move и т.д.). Поэтому на вопрос «почему на складе минус 5» MS возвращает историю документов, а не journal.

Наше StockMovement — явный immutable journal. Обоснование:

  1. Мгновенный ответ на «почему такой остаток» без пробегания по всем документам.
  2. Атомарные корректировки при баг-фиксах миграций.
  3. Упрощённая репликация в офлайн-кассы.

Это сознательное отклонение от MS — должно быть задокументировано в коде и в docs/. TODO: комментарий в StockMovement.cs + упоминание в CLAUDE.md.


Свод по приоритетам

Приоритет 1 — базовая совместимость импорта (на этой неделе):

  • Product: Code, ExternalCode, TrackingType (enum) вместо IsMarked, MinPrice/MinPriceCurrencyId, PaymentItemType (enum)
  • Counterparty: CounterpartyType.IndividualEntrepreneur, ExternalCode, tags (коллекция)
  • ProductGroup: ExternalCode
  • PriceType: ExternalCode
  • Country, Currency, UnitOfMeasure, Store: ExternalCode
  • RetailPoint: DefaultPriceTypeId

Приоритет 2 — смысловые (следующая итерация):

  • RetailSale: PaidQr, убрать enum PaymentMethod в пользу суммовых полей
  • Supply: Overhead, VatSum, VatEnabled, VatIncluded
  • Organization: LegalName, LegalAddress, PayerVat, DirectorName
  • Product: Volume, Weight, DiscountProhibited
  • Stock: InTransit

Приоритет 3 — при необходимости:

  • Counterparty: коллекция Account, DefaultPriceType
  • ProductGroup: VatRateId? + UseParentVat
  • RetailPoint: кассовые настройки (allowCustomPrice, discountMaxPercent, cashiers...)
  • Store: slots, zones

Сознательно не копируем MS:

  • CounterpartyKind (Supplier/Customer/Both) — у нас enum, у MS теги. Оставляем для UX/фильтрации.
  • Store.Kind (Warehouse vs RetailFloor) — у MS нет, нам нужно.
  • VatRate как отдельная сущность — у MS число на товаре. У нас справочник ради локализации.
  • StockMovement journal — у MS нет. Выбор архитектуры.
  • Product.IsWeighed / IsAlcohol — упрощения под ритейл.
  • UnitOfMeasure.Symbol, DecimalPlaces — UX.