food-market/docs/mobile-api.md
nns b4bd0e41d4
Some checks are pending
Auto-tag / Create date-tag (push) Waiting to run
CI / Backend (.NET 8) (push) Waiting to run
CI / Web (React + Vite) (push) Waiting to run
CI / POS (WPF, Windows) (push) Waiting to run
Docker API / Build + push API (push) Waiting to run
Docker API / Deploy API on stage (push) Blocked by required conditions
Docker Web / Build + push Web (push) Waiting to run
Docker Web / Deploy Web on stage (push) Blocked by required conditions
feat(inventory): веб-UI по модели МойСклада + доки
- InventoryEditPage: «Завершить подсчёт» вместо «Провести»; на завершённой — кнопки
  «Создать оприходование излишков» / «Создать списание недостачи» (блокируются, если нет
  разницы или документ уже создан) + блок «Связанные документы» + «Вернуть в черновик».
- InventoriesPage: статус «Завершена»; колонка «Результат» (опр./спис. № или «не применено»).
- Enter/LossEditPage: «На основании инвентаризации №…» со ссылкой.
- types.ts: InventoryStatus Draft/Completed; related в InventoryDto; результат в списке;
  inventoryDocId/Number в Enter/Loss DTO.
- docs/inventory-moysklad-reference.md (сверка), inventory-model-progress.md,
  supply-inventory.md («Как провести инвентаризацию», 5 шагов), mobile-api.md (compat post/unpost).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 20:34:18 +05:00

7.3 KiB
Raw Blame History

API для мобильного «FM Склад»

Серверные возможности, добавленные для iOS-приложения (стенд api/*). Авторизация — OpenIddict password/refresh grant, client_id=food-market-web, Bearer-токен. Все ответы и ошибки — JSON; бизнес-ошибки отдают { error, field?, code? }.

1. Остатки по группе товаров (с вложенными)

GET /api/inventory/stock?groupId={guid}&storeId=&search=&includeZero=&page=&pageSize=&sort=&order=

  • groupId — фильтр по группе товаров включая все вложенные подгруппы (по денормализованному Path). Комбинируется с storeId, search, сортировкой.
  • Ответ: PagedResult<StockRow> — { items:[{ productId, productName, article, unitSymbol, storeId, storeName, quantity, reservedQuantity, available }], total, page, pageSize }.

2. Добавить штрихкод к товару

POST /api/catalog/products/{id}/barcodes — право ProductsEdit.

Тело: { "code": "4870001112223", "kind": "ean13" }. kind ∈ ean13|ean8|code128|code39|upca|upce|other (регистр не важен); если не задан — тип определяется по длине/цифрам кода.

Ответы:

  • 201 Created → { id, productId, code, type, isPrimary } (первый штрихкод товара становится primary).
  • 200 OK — тот же код уже у этого товара (идемпотентно), тело как у 201.
  • 409 Conflict → { error:"Штрихкод уже у товара «…».", code:"barcode-duplicate", conflictProduct:"…", field:"code" } — код занят другим товаром организации.
  • 400 — пустой/слишком длинный код; 404 — товар не найден.

Уникальность проверяется в пределах организации ((OrganizationId, Code)).

3. Права текущего пользователя

GET /api/me теперь возвращает permissions — эффективные права (имя флага → bool) с учётом роли и простого/расширенного режима. Мобильное приложение и веб гейтят UI по одному источнику (скрывают одно и то же).

{
  "sub":"…","name":"…","email":"…","roles":["Admin"],"orgId":"…",
  "hasLiveOrg":true,"hasActiveEmployee":true,"advancedMode":false,
  "permissions":{ "ProductsView":true,"ProductsEdit":true,"StocksView":true,
                  "DemandsView":false,"TransferEdit":false, … }
}

Правила: SuperAdmin — все права без ограничений; Admin — все, но в простом режиме advanced-only скрыты; обычная роль — её флаги (в простом режиме advanced-only тоже обнуляются). Advanced-only: DemandsView/Edit/Post, TransferEdit, LoyaltyManage, PromotionsManage, PriceTypesManage (зеркалит серверный [RequiresAdvancedMode]). Имена флагов — из RolePermissions. Веб использует тот же источник (useCan()).

4. Весовой признак в офлайн-каталоге

GET /api/pos/v1/sync (каталог для сканирования/офлайна) в ProductSyncDto отдаёт:

  • packaging (1=штучный, 2=весовой, 3=разливной) и isWeighed (явный bool packaging==2),
  • unitCode — единица измерения (шт/кг/л…).

5. Весовые этикетки (внутримагазинные EAN-13)

Как в МойСкладе: внутримагазинные EAN-13 с префиксом из диапазона 20–29, где зашиты «код товара» (PLU, N цифр) и величина (вес в граммах → кг, либо цена).

Настройка организации — GET/PUT /api/organization/settings (PUT — право Admin):

  • weightBarcodeEnabled (bool, по умолчанию true),
  • weightBarcodeWeightPrefixes (CSV префиксов веса, null → 20,21,22,23,24),
  • weightBarcodePricePrefixes (CSV префиксов цены, null → 25,26,27,28,29),
  • weightBarcodeCodeDigits (длина PLU, по умолчанию 5). Блок применяется, только если в PUT передан weightBarcodeEnabled (иначе настройки не трогаются).

Разбор при поиске — GET /api/catalog/products/by-barcode/{value}:

  • Обычный штрихкод → товар (или { items:[…] } при нескольких совпадениях), 404 если нет.
  • Весовая этикетка (префикс весовой/ценовой) → { product:{…}, weighted:true, valueKind:"WeightKg"|"Price", quantity:1.5, embeddedPrice:null }. Товар ищется по встроенному PLU (как короткий штрихкод товара либо как артикул); quantity — вес (кг), embeddedPrice — зашитая цена (для ценовых этикеток).

Пример: этикетка 2112345015007 при дефолтных настройках → PLU 12345, вес 1.500 кг.

Разбор единый для сервера и кассы — foodmarket.Shared.Barcodes.WeightedBarcode.

Инвентаризация: post/unpost (временная совместимость)

С переходом на модель МойСклада инвентаризация сама остатки не двигает (см. docs/inventory-moysklad-reference.md). Веб использует: complete → create-enter / create-loss, затем проводит документы.

Мобильное приложение «FM Склад» исторически вызывает post/unpost инвентаризации. Чтобы не ломать установленные приложения, эндпоинты оставлены как совместимые:

  • POST /api/inventory/inventories/{id}/post — = завершить + создать и ПРОВЕСТИ Оприходование (излишки) и Списание (недостача) одним вызовом. Идемпотентно: если связанные документы уже созданы — не дублирует.
  • POST /api/inventory/inventories/{id}/unpost — отменить результаты: отменить проведение и удалить связанные Оприходование/Списание (остатки вернутся), вернуть инвентаризацию в черновик.

Статус: временное. Рекомендуемый путь для новых версий мобильного приложения — complete + create-enter/create-loss (как в вебе), чтобы кладовщик мог проверить документы перед проведением. post/unpost будут помечены deprecated после обновления iOS.