- ImportRetailReturnsAsync (retailsalesreturn): связь с чеком по externalId demand, +остаток
- ImportInventoriesAsync (inventory): Completed без движений, BookQty/ActualQty из calculated/quantity
- Все 7 импортёров принимают updatedFromMsk → filter=updated>= (МСК) для дельты
- Контроллер: ?updatedFrom=<МСК> прокинут во все типы
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
МойСклад отдаёт sum/cashSum/price как число с точкой (900000.0) → long не парсился
(«JSON value could not be converted to System.Int64»). Поля DTO → decimal. Плюс
GET /api/admin/moysklad/jobs/{id} для наблюдения за прогрессом/ошибками переноса.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Part 2 (документы) — проверенный шаблон на приёмках:
- Phase41: MoySkladExternalId у counterparties/stores/units/retail_points/employees и
документов (supplies/enters/losses/inventories/supplier_returns/retail_sales/
cash_shifts/money_transactions) + индексы. Свойства на доменных сущностях.
- MoySkladClient.StreamDocumentsAsync: документы в хронологии (order=moment,asc), expand
позиций/связей (limit=100), обработка 429/сбоев.
- MoySkladDocumentImportService.ImportSuppliesAsync: идемпотентно по msId, резюмируемо
(skip перенесённых), moment МСК→UTC, суммы из тиынов, поставщик по externalId контрагента,
товары по externalId, проведение → движения остатков (StockService). Журнал ошибок.
- Импорт контрагентов теперь пишет MoySkladExternalId (нужно для ссылок в документах).
- Эндпоинт POST /api/admin/moysklad/import-supplies (фон, job). Остальные типы — по шаблону.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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).
OtherSystem:
- MsCounterparty DTO (name, legalTitle, inn, kpp, companyType, tags...).
- OtherSystemClient.StreamCounterpartiesAsync — paginated like products.
- OtherSystemImportService.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/other-system/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).
- OtherSystem 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 447ac65 — sticky top bar, sticky
table header, only the body scrolls.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
OtherSystem returns minPrice.value and salePrices[*].value as fractional numbers
for some accounts/products (not always pure kopecks). Deserializing as long
failed with "out of bounds for an Int64" → 500 on import. Switch MsMoney.Value
and MsSalePrice.Value to decimal, which accepts both integer and decimal
representations. Division by 100m already happens when mapping to local
Product, so semantics are unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Infrastructure (foodmarket.Infrastructure.Integrations.OtherSystem):
- OtherSystemDtos: minimal shapes of products, folders, uom, prices, barcodes from JSON-API 1.2
- OtherSystemClient: HttpClient wrapper with Bearer auth per call
- WhoAmIAsync (GET entity/organization) for connection test
- StreamProductsAsync (paginated 1000/page, IAsyncEnumerable)
- GetAllFoldersAsync (all product folders in one go)
- OtherSystemImportService: orchestrates the full import
- Creates missing product folders with Path preserved
- Maps OtherSystem VAT percent → local VatRate (fallback to default)
- Maps barcodes: ean13/ean8/code128/gtin/upca/upce → our BarcodeType enum
- Extracts retail price from salePrices (prefers "Розничная"), divides kopeck→major
- Extracts buyPrice → PurchasePrice
- Skips existing products by article OR primary barcode (unless overwrite flag set)
- Batch SaveChanges every 500 items to keep EF tracker light
- Returns counts + per-item error list
API: POST /api/admin/other-system/test — returns org name if token valid
API: POST /api/admin/other-system/import-products { token, overwriteExisting }
— Authorize(Roles = "Admin,SuperAdmin")
Web: /admin/import/other-system page
- Amber notice: token is not persisted (request-scope only), how to create
a service token in other-system.ru with read-only rights
- Test connection button + result banner
- Import button with "overwrite existing" checkbox
- Result panel with 4 counters + collapsible error list
Sidebar adds "Импорт" section with OtherSystem link.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>