Минимальный контракт API для продаж и склада: сущности и события
Минимальный контракт API для продаж и склада: набор объектов и событий, который ускоряет подключение новых сервисов без переделок и потери данных.

Зачем нужен минимальный контракт и что он решает
Интеграции между продажами и складом часто тормозят не из-за API как такового, а из-за разнобоя в смыслах. В одной системе это «клиент», в другой - «контрагент». Где-то статус «Отгружено» означает «выехало со склада», а где-то - «передано перевозчику». Добавьте разные идентификаторы: один сервис хранит свой номер заказа, другой генерирует новый, и через пару недель уже непонятно, что с чем связано.
«Быстрые» интеграции без общего контракта выглядят дешевле, но позже становятся источником потерь: дубли, неверные остатки, повторные списания, ручные сверки и срочные правки при каждом новом сервисе. Особенно больно, когда подключается еще один канал продаж или новая WMS: старые точечные договоренности начинают конфликтовать.
Минимальный контракт - это общий набор объектов и событий, который нужен всем системам, чтобы одинаково понимать одну и ту же операцию. Он не пытается описать все нюансы ERP, CRM и склада. Он фиксирует базовую общую часть: единые идентификаторы, ключевые статусы и обязательные поля.
Обычно такой контракт покрывает простую цепочку: продажа (создание заказа и изменения), резерв (что зарезервировано и почему), отгрузка (что физически ушло со склада), приемка (что пришло обратно или поступило на склад), корректировки (инвентаризация, списания, пересортица).
Простой пример: новый сервис доставки получает событие «Отгрузка создана» и не гадает, что значит статус, где брать адрес и как сопоставить позиции. Он читает один формат - с теми же ID и правилами, что и остальные участники интеграции.
Принципы: границы, источники данных и идентификаторы
Чтобы контракт для продаж и склада реально ускорял интеграции, важно договориться не о списке полей, а о правилах. Они отвечают на четыре вопроса: где проходит граница домена, кто владелец данных, как выглядят идентификаторы, и что считать справочником, а что транзакцией.
Границы доменов: не смешивать ответственность
Продажи, склад, финансы и доставка часто используют одни и те же слова («статус», «отгрузка», «оплата»), но вкладывают разные смыслы. Если смешать ответственность, любая правка превращается в цепную реакцию.
Ориентир простой: каждый домен хранит и меняет только то, чем управляет. Продажи управляют заказом и его жизненным циклом, склад - движениями и резервом, финансы - платежами и сверкой, доставка - маршрутом и трекингом.
Один источник правды для каждого объекта
Для каждой сущности выбирается один мастер: система, которая создает и изменяет объект. Остальные получают копию и могут добавлять свои атрибуты, но не переписывают чужую часть.
Например, карточка товара (наименование, штрихкод, единица измерения) принадлежит PIM или ERP. Склад не должен «исправлять» название товара, а продажи - менять базовую упаковку, даже если так удобнее в интерфейсе.
Идентификаторы: неизменяемость и сопоставление
Сразу заложите два типа ID: внутренний (в системе-владельце) и внешний (для интеграции). Внешний ID должен быть стабильным и не меняться при переносах, слияниях и миграциях.
Практичное правило: любой сервис принимает запросы с внешним ID и возвращает оба. Так проще строить соответствия и чинить расхождения без ручных таблиц.
Справочники и транзакции: разные модели жизни
Справочники описывают «что это» (товар, склад, контрагент, валюта) и меняются редко. Транзакции описывают «что произошло» (заказ, отгрузка, возврат, приход, перемещение) и фиксируются как факты.
Если сомневаетесь, задайте вопрос: объект можно переименовать без последствий или любое изменение должно быть видно как событие? Во втором случае это транзакция, и ее лучше хранить как отдельную сущность с датой, автором и причиной.
Базовые справочные сущности: что должно быть всегда
Если вы хотите, чтобы новый сервис подключался без долгих согласований, начните с общего словаря. Контракт для продаж и склада чаще ломается не на заказах и остатках, а на том, что разные системы по-разному понимают, кто продает, где лежит товар и что именно считается одной позицией.
Хорошая база справочников - это не «полные мастер-данные», а короткий набор сущностей, которые дают контекст любой операции и позволяют сопоставлять документы между системами.
Минимальный набор, который почти всегда нужен:
- Организация и подразделение: кто владелец операции (юрлицо) и где она произошла (филиал/точка). Часто хватает
company_id,branch_id, названия и статуса активности. - Склад и место хранения: склад нужен почти всем, а зона или ячейка - когда важна точность (например, адресное хранение). Даже если ячеек пока нет, полезно зарезервировать поле
location_id. - Товар и SKU (вариант): товар - «что продаем» (модель), SKU - «в каком виде» (цвет, комплектация, серийность). Добавьте единицу измерения и коэффициенты (штука, короб, кг), чтобы не спорить о пересчетах.
- Контрагент: минимальная карточка покупателя или поставщика для документов. Не тяните сразу все реквизиты, но храните стабильный
counterparty_id, тип (customer/supplier), имя и, при необходимости, налоговый идентификатор. - Actor (пользователь или сервис): кто создал, изменил, подтвердил. Это помогает в аудите и разборе инцидентов.
Пример из практики: заказ оформлен в интернет-магазине на филиал А, отгрузка делается со склада B, а сборка идет из конкретной зоны. Если в справочниках нет единых branch, warehouse и location, то два документа уже не получится надежно склеить, даже при одинаковых номерах.
Держите эти сущности небольшими, с понятными идентификаторами и признаком архивности. Остальное можно наращивать позже, не меняя основу.
Складские сущности: остатки, резервы и движения
Склад ломает интеграции не столько сложной логикой, сколько разными трактовками простых слов: «остаток», «резерв», «движение». В контракт важно заложить эти сущности так, чтобы любой новый сервис понимал: сколько товара доступно, что уже обещано клиентам, и почему цифры изменились.
Остаток (stock balance) - снимок «сколько есть сейчас» в конкретном месте. Его нужно раскладывать как минимум по складу (warehouse), месту (location) и SKU. Важно фиксировать статус количества: например, «доступно», «в карантине/брак», «в пути», чтобы сервисы не списывали то, чем нельзя торговать.
Резерв (reservation) отвечает на вопрос «что уже занято». Резерв всегда ссылается на причину (обычно заказ или строку заказа), имеет автора (система/пользователь), срок действия и явную отмену. Так вы избегаете вечных резервов, когда заказ уже отменен, а склад так и не освободился.
Движение (stock movement) - источник правды о том, почему поменялся остаток: приход, расход или перемещение. Когда движения есть, спор «товар пропал» быстро превращается в «вот операция, вот причина».
Минимальные поля, которые стоит закрепить
Лучше договориться о небольшом наборе, который одинаков во всех сервисах:
warehouse_id,location_id,sku_id(где и что)quantityи единица измерения (сколько)stock_status(доступный, брак, в пути)reservation_idилиreference(на что зарезервировано/списано)movement_typeиreason_code(что произошло и почему)
Инвентаризация (inventory count) добавляет «факт»: сколько реально нашли, какая разница с учетом и кто подтвердил результат.
Партии/серии/срок годности подключайте только если это влияет на продажи или списания (например, лекарства, питание, гарантийная техника). Тогда эти атрибуты должны появляться одинаково в остатках, резервах и движениях. Если они живут только в одном месте, учет быстро начнет расходиться.
Продажи: заказ, отгрузка, возврат и платежи
Чтобы новый сервис быстро подключался к продажам, ему обычно достаточно четырех сущностей: заказ, отгрузка, возврат и платеж.
Заказ: что покупатель попросил
Заказ фиксирует намерение, а не факт отгрузки. Его должно быть можно прочитать без походов в другие сервисы: посчитать сумму, понять состав и сроки.
Минимальный набор, который чаще всего спасает от переделок:
- Идентификаторы:
order_id, номер заказа, дата/время создания, клиент (customer_id) - Строки:
item_id/sku, количество, цена, скидка, ставка/сумма налога, итог по строке - Деньги: валюта, суммы до/после налогов, округления
- Адреса и контакт: доставка и биллинг (как минимум страна/город/строка адреса)
- Сроки: обещанная дата отгрузки/доставки, комментарий по условиям
Статусы заказа должны быть простыми и однозначными. Рабочий минимум: draft (черновик), confirmed (подтвержден), canceled (отменен), closed (закрыт). Важнее не названия, а правило: статус меняется только по понятному событию (подтвердили, отменили, полностью отгрузили и закрыли).
Отгрузка и возврат: что реально произошло
Отгрузка (shipment/fulfillment) описывает факт: что именно уехало, когда и откуда. Важно хранить связь с заказом и состав отгрузки по строкам, потому что частичные отгрузки - норма.
Возврат (return) должен ссылаться на конкретную отгрузку или ее строки, чтобы не возникало споров, что именно вернули. Добавьте причину возврата и состояние товара (например, новый/вскрыт/поврежден).
Платеж можно держать минимальным: payment_id, order_id, сумма, валюта, способ, статус (создан, авторизован, оплачен, отменен, возврат), дата операции. Если заказ подтвержден, но платеж только авторизован, сервисы доставки и поддержки не должны считать его «оплаченным» по умолчанию.
События интеграции: какие и зачем
Интеграционные события нужны, чтобы сервисы продаж и склада узнавали о фактах, которые уже произошли, и реагировали без прямых вызовов друг друга. Один сервис публикует факт, остальные подписываются и действуют.
Важно различать событие и команду. Команда - это запрос «сделай, пожалуйста» (например, зарезервируй товар). Событие - уведомление «это уже случилось» (например, резерв создан). Команды обычно требуют ответ и могут быть отклонены. События не спрашивают разрешения и должны быть правдой.
По продажам минимальный набор событий часто сводится к OrderCreated, OrderConfirmed, OrderCanceled. Этого хватает, чтобы подключать антифрод, бонусы, уведомления, печать документов и аналитику.
Для склада обычно нужны факты о резерве и изменении остатков: StockReserved, StockReleased, StockMoved, StockAdjusted (корректировка по инвентаризации/браку). Они помогают опираться на историю, а не угадывать состояние.
Логистика добавляет жизненный цикл отгрузки и возврата: ShipmentCreated, ShipmentShipped, ShipmentDelivered, ReturnReceived. Тогда доставка, CRM и поддержка видят статус одинаково, без ручных сверок.
Чтобы события были полезны и безопасны, в каждом сообщении держите обязательный минимум:
eventIdtypeoccurredAtsourceversion
Если есть цепочка действий (например, заказ создал резерв, затем создалась отгрузка), добавляйте correlationId. Он связывает события в одну историю и сильно упрощает поиск проблем в интеграции.
Правила контракта: статусы, версии, идемпотентность
Чтобы контракт не развалился через месяц, нужны правила вокруг сущностей и событий. Они делают интеграции предсказуемыми, даже когда сервисы развиваются независимо.
У каждого объекта должны быть общие базовые поля, чтобы его можно было хранить, обновлять и сверять между системами:
id(стабильный идентификатор объекта)createdAtиupdatedAt(когда появился и когда менялся)status(состояние объекта сейчас)sourceилиoriginSystem(кто создал запись, если это важно для расследований)
Со статусами лучше быть скучными: короткий список и четкие правила переходов. Главное - запрет на неожиданные прыжки. Если заказ уже shipped, он не должен внезапно стать draft. Когда нужен откат, используйте отдельное событие (например, возврат), а не переписывание истории.
Идемпотентность нужна в двух местах: в запросах и в событиях.
- Для команд (создать заказ, подтвердить резерв) используйте
idempotencyKey: при повторе сервер обязан вернуть тот же результат, а не создавать дубликат. - Для событий закладывайте повторную доставку как норму: потребитель должен уметь обработать одно и то же событие два раза без побочных эффектов, опираясь на
eventIdи хранение факта обработки.
Версионирование схем делайте простым: совместимость назад обязательна. Новые поля можно добавлять, старые нельзя переименовывать и менять смысл. Если поле устарело, помечайте его как deprecated и задавайте срок вывода.
Ошибки должны быть удобны для поддержки: код, понятное сообщение и детали по полям. Если не прошла валидация, возвращайте общий код (например, VALIDATION_ERROR) и список проблемных полей с причинами - так интегратор исправит запрос без переписки и догадок.
Как собрать контракт за 1-2 недели: пошаговый план
Чтобы уложиться в 1-2 недели, не пытайтесь описать весь бизнес сразу. Начните с минимума, который дает ценность: продать, отгрузить, принять поставку, пересчитать склад. Эти сценарии быстро покажут, каких объектов и событий не хватает.
План работ (без лишней бюрократии)
Соберите 3-4 минимальных сценария и запишите их как короткие истории: кто что делает и какой результат должен увидеть (например, заказ создан, товар зарезервирован, отгрузка подтверждена).
Дальше нарисуйте на одном листе сущности и связи: товар, склад, контрагент, заказ, отгрузка, приход, движение, остаток, резерв. На этом шаге важно договориться об идентификаторах: где мастер, где внешний ключ, что считается «одним и тем же объектом».
Затем зафиксируйте статусы и переходы для заказа и отгрузки. Не нужно много: 4-6 статусов, но с понятными правилами, что можно менять и что является финальным.
После этого определите события и их payload. Для каждого события ответьте на три вопроса: что случилось, какие ключи нужны получателю, как понять, что событие уже обработано (через eventId, версию и правила дедупликации).
И отдельно согласуйте «мелочи», которые ломают интеграции: время и часовой пояс, формат дат, округления, валюта, единицы измерения, правила пересчета (штуки, коробки, кг).
После согласования сделайте тестовые данные и эталонные примеры сообщений. Это экономит дни обсуждений: все смотрят на один и тот же образец.
Что должно быть готово на выходе
- Одна схема сущностей и связей (читаемая без пояснений).
- Таблица статусов и переходов для заказа и отгрузки.
- Перечень событий с кратким описанием и примером payload.
- Набор тестовых объектов и 5-10 эталонных сообщений для проверок в разных системах.
Частые ошибки и ловушки при проектировании
Частая проблема - когда справочник и транзакция живут в одном объекте. Например, в «Товаре» начинают хранить последнюю цену продажи, остаток и дату последней отгрузки. В итоге справочник постоянно меняется, кеши ломаются, а новые сервисы не понимают, что считать источником правды.
Вторая ловушка - изменяемые идентификаторы и слабые связи между документами. Если номер заказа можно «переименовать», а отгрузка не хранит ссылку на заказ, то через месяц никто не сможет уверенно ответить, что именно было отгружено и за что вернули деньги.
Еще больнее, когда резервы отсутствуют или «прячутся» в остатке. Тогда продажи видят «остаток 10» и продают, а склад уже отложил 8 под другой заказ. Начинаются споры и ручные сверки.
События интеграции часто делают без версии и без correlationId. Тогда поиск причины расхождения превращается в гадание: какое событие было первым, где потерялось подтверждение, к какому заказу относится движение.
Чтобы контракт помогал, а не пугал, не перегружайте модель с первого дня. Обычно достаточно закрыть базовые сценарии и оставить место для расширения.
Чаще всего забывают:
- частичные отгрузки (один заказ уехал в 2-3 машины)
- частичные возвраты (вернули 1 из 5 позиций)
- раздельные статусы «создано», «подтверждено», «отменено» для каждого документа
- идемпотентность на событиях и командах
Пример: сервис доставки прислал событие «Отгрузка создана», а потом «Отгрузка обновлена» без версии. Склад пересчитал движение дважды, и остатки «уплыли». Версия события и correlationId быстро показали бы, где произошло повторение.
Быстрая проверка: чеклист минимального контракта
Перед тем как отдавать контракт новым командам и внешним сервисам, полезно сделать короткую проверку. Цель простая: интеграция не должна держаться на догадках и не должна ломаться при повторной доставке событий.
Пробегитесь по пунктам и честно ответьте «да/нет»:
- Единые идентификаторы: для
order,shipment,sku,warehouse,locationесть стабильные ID, которые не меняются со временем и не зависят от конкретной системы-источника. - Статусы и переходы: для заказа и отгрузки определены статусы и разрешенные переходы. Понятно, что делать при отмене, частичной отгрузке и возврате.
- Склад как отдельная модель: есть сущности для резерва (reservation) и движения (stock movement), а не только «остаток числом». Можно объяснить, откуда взялся остаток и почему он изменился.
- События не пустые: каждое событие несет
eventId, время,source,versionиcorrelationId, чтобы связать цепочку «заказ -> отгрузка -> платеж» и разбирать инциденты. - Повторы и точность: описаны правила идемпотентности и повторной доставки (что считается дублем, по чему дедупликация). Согласованы единицы измерения, округления и часовой пояс.
Если по двум и более пунктам ответ «нет», сюрпризы почти гарантированы. Типичный случай: сервис доставки присылает повтор ShipmentCreated, и без идемпотентности вы получаете двойное списание со склада или второй раз отправленный чек. Такая проверка занимает 10 минут, но экономит дни на разборе ошибок в продакшене.
Пример сценария: подключаем новый сервис без переделки ядра
Интернет-магазин растет и решает сразу две задачи: подключить новый сервис доставки (для расчета сроков и трекинга) и внедрить новую WMS на складе. Раньше многое держалось на ручных сверках: менеджер сравнивал «что в заказе», «что собрано» и «что уехало», а остатки часто не совпадали.
Минимальный контракт снимает главную боль: все системы говорят одними и теми же объектами и событиями. Ядро продаж не знает детали WMS и доставки, оно публикует и принимает стандартные факты: заказ подтвержден, резерв создан, отгрузка отправлена.
Типовой поток выглядит так: OrderConfirmed -> StockReserved -> ShipmentShipped -> StockMoved. Благодаря этому новую WMS можно подключить как исполнителя складских событий, а сервис доставки - как подписчика на события по отгрузке, не ломая существующие процессы.
Исключения тоже становятся понятными, потому что у них есть место в контракте:
- отмена заказа:
OrderCanceledснимает резервы и прекращает сборку - недостача:
StockReservationFailedилиStockAdjustedфиксирует факт и причину - частичная отгрузка:
ShipmentPartiallyShippedи корректное уменьшение резерва - возврат:
ReturnRegistered->StockMoved(в отдельную зону или на основной склад) - ошибка повтора: идемпотентность не дает создать дубль резерва или движения
Чтобы оценить результат, заранее договоритесь о метриках: сколько дней заняла интеграция, сколько инцидентов было в первые 2 недели, и как часто возникают расхождения по остаткам между продажами и складом. Если контракт сделан нормально, эти цифры падают заметно и без героических разборов.
Следующие шаги: как внедрять и где пригодится помощь
Начните с инвентаризации. Соберите список объектов и событий, которые уже есть в ваших системах (ERP, WMS, касса, маркетплейсы, CRM), и сопоставьте их с тем, что вы хотите закрепить как минимум. Быстро выяснится, где у вас разные названия для одного и того же, где не хватает идентификаторов, а где статусы существуют только «в голове» у команды.
Дальше договоритесь о владельцах данных. Без этого интеграции превращаются в споры «чья правда». Зафиксируйте ответственность по ключевым зонам: кто ведет товары и справочники, какой источник считается главным по остаткам и резервам, где рождается заказ и кто подтверждает отгрузку и возврат, какой сервис сообщает об оплате и по каким правилам.
Пилот планируйте так, чтобы он дал эффект, но не утонул в деталях. Хороший вариант - один склад и один канал продаж (например, интернет-магазин), плюс четкий набор сценариев: заказ, резерв, отгрузка, возврат. После пилота масштабируйте на остальные склады и каналы, не меняя контракт, а добавляя новые источники.
Инфраструктуру тоже стоит продумать заранее: очередь или шина событий, единые журналы, алерты и метрики доставки, повторные попытки и дедупликация. Простое правило: если событие нельзя найти и объяснить через неделю, оно не помогает бизнесу.
Если нужна помощь на стороне интеграции и инфраструктуры, GSE.kz может выступить системным интегратором: помочь собрать архитектуру, настроить мониторинг и отказоустойчивость, а также подобрать и поставить серверную платформу под шину и сервисы (например, на базе серверов S200) с поддержкой 24/7."}