Перерахунок

Перерахунок — це інвентаризація: оператор ТСД зіставляє фактичні залишки з обліковими. Повний цикл на одній сторінці: ви надсилаєте завдання одним запитом, стежите за прогресом і забираєте результат назад.

Завдання — звичайний документ вашої облікової системи: список товарів із плановими кількостями. Для приймання товару є окремий вид документа — Надходження, влаштований точно так само.

Приклад. Зранку облікова система вивантажує завдання «Перерахунок залу №1» на 120 позицій. Комірник обходить зал зі сканером, до обіду завдання виконано — система забирає результат і бачить: 117 позицій збігаються, по трьох є розбіжності.

Основи взаємодії

Базова адреса API: https://combopocket.online/v2/exchange.

Усі операції виконуються методом POST з JSON-тілом.

Кожен запит несе заголовок X-API-Token — входу в систему й сесій немає, система перевіряє дані за токеном.

Відповідь завжди несе поле ok у тілі: true — успіх, false — відмова з причиною в error.

HTTP-код — рівень транспорту: 200 — запит дійшов і оброблений (результат — в ok), 4xx — запит до обробки не дійшов, 5xx — збій на нашому боці.

Докладно конверт відповіді, таблиця кодів і авторизація розібрані в розділі «З чого почати» — тут вони працюють точно так само.

Ключові поняття

Зовнішній ідентифікатор (external_id) — ідентифікатор документа у вашій обліковій системі. Це головний ключ усього обміну: за ним ви оновлюєте завдання, опитуєте статус, забираєте результат і видаляєте документ.

Ідентифікатор унікальний у межах вашої бази — другого документа з таким самим external_id не буде.

Виконавець (assignee_token) — токен оператора, якому призначається завдання. Кожен оператор ТСД має власний токен, і завдання з'явиться на терміналі саме того оператора, чий токен ви вказали.

Токен виконавця потрібен лише під час надсилання — решта операцій працює за external_id.

Версія документа (docversion) — ваша мітка версії, будь-який рядок. Вона захищає від зайвої роботи: якщо ви повторно надішлете документ із тією самою версією, сервер відповість unchanged і нічого не перезапише.

Змінюєте вміст — змінюйте версію, і сервер замінить завдання цілком.

Життєвий цикл

Ви
upload
передали завдання → статус NotStarted
Оператор
сканує
перше сканування → InProgress
Оператор
завершує
перерахунок виконано → Done
Ви
download
забрали результат
статуси змінює тільки робота оператора — API їх не торкається Done — фінальний стан: виконане завдання не відновлюється

Поки завдання у статусі NotStarted, його можна вільно перезаписувати повторними upload — сервер замінить вміст цілком.

Щойно оператор почав роботу (InProgress) або завершив її (Done), перезапис заборонено: сервер відповість відмовою conflict, щоб робота оператора не була втрачена.

Надсилання завдання

POST /recounts/upload передати завдання на перерахунок

Створює нове завдання або цілком замінює наявне (поки воно NotStarted).

Операція виконується атомарно: або завдання записано повністю, або не записано взагалі — часткових станів не буває.

ПолеТипПризначення
external_idstring ≤100обов. Ідентифікатор документа у вашій системі — ключ усіх подальших операцій.
assignee_tokenstring ≤36обов. Токен оператора-виконавця. Завдання з'явиться на його терміналі.
namestring ≤200обов. Назва завдання — її бачить оператор у списку на ТСД.
datedatetimeобов. Дата документа у форматі ISO 8601 зі зсувом часового поясу: 2026-07-10T12:00:00+03:00. Форма без зсуву теж приймається й означає київський час.
docversionstring ≤100 Ваша мітка версії. Збіглася зі збереженою — повтор ігнорується (unchanged).
commentstring ≤500 Коментар-інструкція оператору.
store_namestring ≤200 Назва магазину. Допомагає оператору відрізняти завдання, коли на ТСД вивантажені документи кількох магазинів.
settingsobject Налаштування поведінки завдання на терміналі — див. нижче. Не є обов'язковими цілком.
rowsarrayобов. Рядки завдання. Може бути порожнім [] — завдання «перерахуй усе, що знайдеш»: оператор сканує вільно, результат повернеться в extra_rows.

Налаштування поведінки на терміналі settings

Необов'язковий об'єкт — задає, що оператор бачить на екрані завдання і що йому дозволено.

Передавати settings цілком не обов'язково; будь-яке відсутнє поле (як і весь об'єкт) трактується як true.

Налаштування зберігаються в документі й завжди повертаються назад у status і download — навіть якщо ви їх не надсилали.

ПолеТипПризначення (типово — увімкнено)
show_fact_qtybool Показувати оператору фактичну кількість за позицією.
show_progressbool Показувати прогрес-бари перебігу переобліку.
allow_add_unlistedbool Дозволяти додавати товари поза завданням — знайдені в базі штрих-кодів, але відсутні в рядках документа. За false оператор працює строго за списком.
show_book_qtybool Показувати облікову кількість (плановий залишок) за позицією.

Кожен рядок rows[] — одна товарна позиція:

ПолеТипПризначення
item_idstring ≤100обов. Ідентифікатор товару у вашій системі. Унікальний у межах документа. Якщо товар обліковується з характеристиками (колір, розмір), включіть характеристику в ідентифікатор — наприклад, склейте коди номенклатури й характеристики: кожна характеристика стає окремою позицією зі своїм item_id і своїми штрих-кодами.
namestring ≤150 Назва товару — її бачить оператор.
charstring ≤150 Характеристика (колір, розмір, об'єм).
qty_plannumberобов. Обліковий залишок: скільки товару значиться за даними вашої системи. Від'ємне значення допустиме — оператор бачить його на терміналі як є.
qty_factnumber Стартовий факт. Зазвичай не передається (0) — його наповнює оператор; потрібен для переносу частково виконаних перерахунків.
line_numint Номер рядка у вашому документі — у цьому порядку рядки повернуться під час вивантаження результату.
qty_int_onlybool Товар вважається лише цілими числами (штучний).
bc_arrayarray Штрих-коди позиції: {"code": "…", "is_sku": false}. Формат коду єдиний у всьому API: is_sku: true — ваговий код (СКЮ). Позиція без штрих-кодів припустима.
ЗапитPOST /v2/exchange/recounts/upload
{
  "external_id": "INV-2026-0042",
  "assignee_token": "22222222-3333-4444-5555-666666666666",
  "name": "Перерахунок залу №1",
  "date": "2026-07-10T12:00:00+03:00",
  "docversion": "v1",
  "store_name": "Магазин на Хрещатику",
  "settings": { "show_fact_qty": true, "show_progress": true,
                "allow_add_unlisted": false, "show_book_qty": true },
  "rows": [
    { "item_id": "MLK-001", "name": "Молоко 2,5%", "char": "0,9 л",
      "qty_plan": 10, "line_num": 1, "qty_int_only": true,
      "bc_array": [ { "code": "4820000001234", "is_sku": false },
                    { "code": "25", "is_sku": true } ] },
    { "item_id": "BRD-05", "name": "Хліб Бородинський",
      "qty_plan": 5, "line_num": 2, "qty_int_only": true }
  ]
}
Відповідь — завдання створено
{ "ok": true, "doc_status": "created" }

Поле doc_status каже, що саме сталося: created — завдання створено, updated — наявне замінено цілком, unchanged — версія збіглася, нічого не змінювалося.

Відмова — оператор уже почав перерахунок
{ "ok": false, "error": "conflict", "status": "InProgress" }
Відмова — помилка в даних
{
  "ok": false,
  "error": "validation_failed",
  "recommendation": "штрих-код 4820000001234 указано в різних номенклатур: MLK-001 і KEF-002"
}

Перевірка даних

Сервер перевіряє завдання до запису — або приймається все, або ніщо:

ПравилоНавіщо
item_id унікальний у документі Один рядок — одна позиція; повтор — майже завжди помилка вивантаження.
Один штрих-код — один товар Код, прив'язаний до двох різних item_id, зробив би сканування неоднозначним.
item_id ≤ 100, code ≤ 50 символів Ключові поля не обрізаються мовчки — перевищення це відмова.
name, char ≤ 150 Представницькі поля м'яко усікаються — відмова не потрібна.
Кількість рядків ≤ ліміту бази Захист від аномально великих завдань; ліміт налаштовується для вашої бази (типово 1000 рядків).
date у форматі ISO 8601 Рекомендовано зі зсувом поясу (+03:00); приймається також Z і форма без зсуву (= київський час).

Отримання результату

Далі працюють три читальні операції та видалення. Токен виконавця їм не потрібен — тільки external_id.

POST /recounts/list усі перерахунки бази одним списком

Повертає всі перерахунки вашої бази — тіло запиту не потрібне (порожній {}). Зручний як огляд: що вивантажено, що в роботі, що готове до забирання.

ВідповідьPOST /v2/exchange/recounts/list
{
  "ok": true,
  "truncated": false,
  "docs": [
    { "name": "Перерахунок залу №1",
      "external_id": "INV-2026-0042",
      "status": "Done",
      "docversion": "v1",
      "assignee_token": "22222222-3333-4444-5555-666666666666",
      "created_on_tsd": false,
      "started_at": "2026-07-10T09:15:00+03:00",
      "completed_at": "2026-07-10T11:40:00+03:00" }
  ]
}

Зверніть увагу на два поля.

created_on_tsd: true позначає перерахунки, які оператор створив просто на терміналі — вашій системі вони поки невідомі, і ви самі вирішуєте, чи забирати їх.

truncated: true означає, що список уперся в запобіжний ліміт (налаштовується для бази, типово 1000) і показано лише його частину — ознака того, що виконані документи пора видаляти.

POST /recounts/status статус і прогрес, не вивантажуючи рядків

Легка операція для опитування перебігу роботи: замість вивантаження всіх рядків сервер віддає готові лічильники. Саме її варто викликати за розкладом.

Запит
{ "external_id": "INV-2026-0042" }
Відповідь
{
  "ok": true,
  "status": "InProgress",
  "started_at": "2026-07-10T09:15:00+03:00",
  "completed_at": null,
  "items_total": 120,
  "items_counted": 45,
  "items_extra": 3,
  "progress_percent": 37,
  "discrepancy_count": 7,
  "settings": { "show_fact_qty": true, "show_progress": true,
                "allow_add_unlisted": false, "show_book_qty": true }
}
ПолеЩо означає
status NotStarted — оператор ще не почав, InProgress — у роботі, Done — виконано.
items_totalУсього позицій у перерахунку.
items_counted Позиції, які оператор уже обробив. Враховується й «порахований нуль» — коли оператор підтвердив, що товару немає.
items_extraПозиції, додані оператором понад завдання.
progress_percentВідсоток виконання: counted / total.
discrepancy_count Позиції, де факт не збігся з обліковим залишком, — заздалегідь видно обсяг недостач і надлишків.
settings Налаштування поведінки на терміналі — рівно ті, що ви надіслали під час надсилання, або типові значення. Склад полів — у розділі «Надсилання завдання».
POST /recounts/download вивантажити результат по рядках

Повний результат перерахунку. Рядки повертаються в порядку line_num — тому самому, у якому ви їх надсилали, тому зіставлення з вашим документом тривіальне.

Поля рядків симетричні надсиланню: що ви надіслали в upload, під тими самими іменами повернеться тут.

У шапці відповіді повертаються й налаштування документа (settings).

Запит
{ "external_id": "INV-2026-0042" }
Відповідь
{
  "ok": true,
  "external_id": "INV-2026-0042",
  "status": "Done",
  "docversion": "v1",
  "created_at": "2026-07-10T08:00:00+03:00",
  "started_at": "2026-07-10T09:15:00+03:00",
  "completed_at": "2026-07-10T11:40:00+03:00",
  "comment_initial": "Перерахувати до обіду",
  "comment_user": "Стелаж 4 перекрито, перераховано частково",
  "settings": { "show_fact_qty": true, "show_progress": true,
                "allow_add_unlisted": false, "show_book_qty": true },
  "rows": [
    { "item_id": "MLK-001", "name": "Молоко 2,5%", "char": "0,9 л",
      "qty_plan": 10, "qty_fact": 8, "line_num": 1, "is_counted": true,
      "bc_array": [ { "code": "4820000001234", "is_sku": false } ] }
  ],
  "extra_rows": [
    { "item_id": "SOK-11", "name": "Сік яблучний",
      "qty_plan": 0, "qty_fact": 4, "line_num": -1, "is_counted": true,
      "bc_array": [ { "code": "4820000009999", "is_sku": false } ] }
  ],
  "photos": [
    { "name": "181467df-d4ca-4e4b-b344-1aca7bbb9fb4",
      "comment": "Стелаж 4, розкрита упаковка" }
  ]
}

Поділ rows / extra_rows відповідає на головне питання інвентаризації.

rows — доля позицій вашого завдання: за різницею qty_plan і qty_fact видно недостачі й надлишки.

extra_rows — товар, який оператор знайшов у залі, хоча в завданні його не було.

Прапорець is_counted вирішує класичну неоднозначність нуля: qty_fact: 0 з is_counted: true означає «оператор перевірив — товару немає», а з is_counted: false — «до позиції не дійшли».

Без цього прапорця недостачу неможливо відрізнити від непорахованого залишку.

Масив photos — метадані фотографій, знятих оператором: name (ідентифікатор знімка) і comment (підпис). Самі зображення в документ не вкладаються — download залишається легким.

Показати знімки можна, взагалі не вивантажуючи документ (photos_thumbnails), а отримати оригінал — за іменем (download_photo).

Обидві операції нижче; усе про фото зібрано й на окремій сторінці «Фотографії».

POST /recounts/photos_thumbnails мініатюри всіх фото документа

Повертає за external_id масив мініатюр — для прев'ю без вивантаження документа.

У кожного знімка: ім'я оригіналу, ім'я мініатюри, підпис і сама мініатюра в base64.

Запит
{ "external_id": "INV-2026-0042" }
Відповідь
{
  "ok": true,
  "photos": [
    { "name": "181467df-d4ca-4e4b-b344-1aca7bbb9fb4",
      "thumb_name": "181467df-d4ca-4e4b-b344-1aca7bbb9fb4.thumb",
      "comment": "Стелаж 4, розкрита упаковка",
      "thumb_b64": "/9j/4AAQSkZJRgABAQ…" }
  ]
}
POST /recounts/download_photo отримати одне фото документа

Повертає оригінал однієї фотографії в base64. Ім'я беріть із відповіді photos_thumbnails або з масиву photos в download.

Фото віддається по одному — так відповідь не розростається, і ви тягнете тільки те, що справді потрібно.

Запит
{ "name": "181467df-d4ca-4e4b-b344-1aca7bbb9fb4" }
Відповідь
{
  "ok": true,
  "name": "181467df-d4ca-4e4b-b344-1aca7bbb9fb4",
  "comment": "Стелаж 4, розкрита упаковка",
  "size": 148213,
  "data_b64": "/9j/4AAQSkZJRgABAQ…"
}
Відмова — фото не знайдено
{ "ok": false, "error": "not_found" }
Ендпойнт прив'язаний до виду документа так само, як вивантаження: фото перерахунку доступне лише через /recounts/download_photo. Те саме ім'я, подане в /receipts/download_photo, поверне not_found — як і документ чужого виду. Той самий not_found приходить, якщо знімок уже видалено з сервера.
POST /recounts/delete видалити перерахунок із термінала

Видаляє документ і всі його дані. Викликайте після того, як результат успішно забрано й проведено у вашій системі, — так список на терміналі не заростає виконаними завданнями.

Операція ідемпотентна: видалення неіснуючого документа — теж успіх, повтор запиту безпечний.

Відповідьі для наявного, і для вже видаленого
{ "ok": true, "doc_status": "deleted" }
Видалення діє й на документ, з яким оператор працює просто зараз, — сервер цьому не перешкоджає. Правило просте: видаляйте те, що забрали, і не видаляйте те, що ще в роботі.

Час у відповідях

Усі дати й час (created_at, started_at, completed_at) сервер віддає зі зсувом часового поясу: 2026-07-10T08:00:00+03:00. Незаповнений час — null.

Надсилаючи date, вказуйте власний зсув — момент буде зрозуміло однозначно, навіть якщо ваша система працює в іншому поясі. Форма без зсуву теж приймається й означає київський час.

Подробиці контракту часу — на сторінці «З чого почати».

Довідник відмов

ПомилкаКоли виникаєЩо робити
conflict upload: оператор уже працює з документом (у відповіді status: InProgress або Done) Дочекайтеся завершення й заберіть результат; змінити документ у роботі не можна.
validation_failed Помилка в даних запиту Виправте за текстом recommendation і повторіть.
not_found download/status: документа з таким external_id немає Перевірте ідентифікатор; можливо, документ уже видалено.
unauthorized Токен бази не передано або він невідомий Перевірте заголовок X-API-Token.

Рекомендований алгоритм

  1. Сформуйте завдання в обліковій системі й надішліть його upload-ом із новим docversion. Повтор запиту безпечний: завдяки версії сервер не зробить подвійної роботи.
  2. Отримали created або updated — завдання на терміналі оператора. Отримали validation_failed — виправте дані за текстом recommendation і повторіть.
  3. За розкладом опитуйте status (або оглядово — list).
  4. Статус став Done — викличте download і проведіть результат: rows — по рядках вашого документа, extra_rows — як надлишки.
  5. Результат проведено — викличте delete, щоб убрати виконане завдання з термінала.
  6. Періодично проглядайте list на предмет created_on_tsd: true — перерахунків, створених оператором вручну.