18 мая 2025 г.·7 мин

Минимальный контракт API для продаж и склада: сущности и события

Минимальный контракт 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 и поддержка видят статус одинаково, без ручных сверок.

Чтобы события были полезны и безопасны, в каждом сообщении держите обязательный минимум:

  • eventId
  • type
  • occurredAt
  • source
  • version

Если есть цепочка действий (например, заказ создал резерв, затем создалась отгрузка), добавляйте correlationId. Он связывает события в одну историю и сильно упрощает поиск проблем в интеграции.

Правила контракта: статусы, версии, идемпотентность

Чтобы контракт не развалился через месяц, нужны правила вокруг сущностей и событий. Они делают интеграции предсказуемыми, даже когда сервисы развиваются независимо.

У каждого объекта должны быть общие базовые поля, чтобы его можно было хранить, обновлять и сверять между системами:

  • id (стабильный идентификатор объекта)
  • createdAt и updatedAt (когда появился и когда менялся)
  • status (состояние объекта сейчас)
  • source или originSystem (кто создал запись, если это важно для расследований)

Со статусами лучше быть скучными: короткий список и четкие правила переходов. Главное - запрет на неожиданные прыжки. Если заказ уже shipped, он не должен внезапно стать draft. Когда нужен откат, используйте отдельное событие (например, возврат), а не переписывание истории.

Идемпотентность нужна в двух местах: в запросах и в событиях.

  • Для команд (создать заказ, подтвердить резерв) используйте idempotencyKey: при повторе сервер обязан вернуть тот же результат, а не создавать дубликат.
  • Для событий закладывайте повторную доставку как норму: потребитель должен уметь обработать одно и то же событие два раза без побочных эффектов, опираясь на eventId и хранение факта обработки.

Версионирование схем делайте простым: совместимость назад обязательна. Новые поля можно добавлять, старые нельзя переименовывать и менять смысл. Если поле устарело, помечайте его как deprecated и задавайте срок вывода.

Ошибки должны быть удобны для поддержки: код, понятное сообщение и детали по полям. Если не прошла валидация, возвращайте общий код (например, VALIDATION_ERROR) и список проблемных полей с причинами - так интегратор исправит запрос без переписки и догадок.

Как собрать контракт за 1-2 недели: пошаговый план

Поддержка 24/7 для критичных систем
Обеспечим сопровождение и 24/7 техническую поддержку с сервисной сетью по Казахстану.
Подключить поддержку

Чтобы уложиться в 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 минут, но экономит дни на разборе ошибок в продакшене.

Пример сценария: подключаем новый сервис без переделки ядра

Приведите идентификаторы к порядку
Согласуем формат ID, correlationId и идемпотентность, чтобы убрать ручные сверки.
Получить рекомендации

Интернет-магазин растет и решает сразу две задачи: подключить новый сервис доставки (для расчета сроков и трекинга) и внедрить новую WMS на складе. Раньше многое держалось на ручных сверках: менеджер сравнивал «что в заказе», «что собрано» и «что уехало», а остатки часто не совпадали.

Минимальный контракт снимает главную боль: все системы говорят одними и теми же объектами и событиями. Ядро продаж не знает детали WMS и доставки, оно публикует и принимает стандартные факты: заказ подтвержден, резерв создан, отгрузка отправлена.

Типовой поток выглядит так: OrderConfirmed -> StockReserved -> ShipmentShipped -> StockMoved. Благодаря этому новую WMS можно подключить как исполнителя складских событий, а сервис доставки - как подписчика на события по отгрузке, не ломая существующие процессы.

Исключения тоже становятся понятными, потому что у них есть место в контракте:

  • отмена заказа: OrderCanceled снимает резервы и прекращает сборку
  • недостача: StockReservationFailed или StockAdjusted фиксирует факт и причину
  • частичная отгрузка: ShipmentPartiallyShipped и корректное уменьшение резерва
  • возврат: ReturnRegistered -> StockMoved (в отдельную зону или на основной склад)
  • ошибка повтора: идемпотентность не дает создать дубль резерва или движения

Чтобы оценить результат, заранее договоритесь о метриках: сколько дней заняла интеграция, сколько инцидентов было в первые 2 недели, и как часто возникают расхождения по остаткам между продажами и складом. Если контракт сделан нормально, эти цифры падают заметно и без героических разборов.

Следующие шаги: как внедрять и где пригодится помощь

Начните с инвентаризации. Соберите список объектов и событий, которые уже есть в ваших системах (ERP, WMS, касса, маркетплейсы, CRM), и сопоставьте их с тем, что вы хотите закрепить как минимум. Быстро выяснится, где у вас разные названия для одного и того же, где не хватает идентификаторов, а где статусы существуют только «в голове» у команды.

Дальше договоритесь о владельцах данных. Без этого интеграции превращаются в споры «чья правда». Зафиксируйте ответственность по ключевым зонам: кто ведет товары и справочники, какой источник считается главным по остаткам и резервам, где рождается заказ и кто подтверждает отгрузку и возврат, какой сервис сообщает об оплате и по каким правилам.

Пилот планируйте так, чтобы он дал эффект, но не утонул в деталях. Хороший вариант - один склад и один канал продаж (например, интернет-магазин), плюс четкий набор сценариев: заказ, резерв, отгрузка, возврат. После пилота масштабируйте на остальные склады и каналы, не меняя контракт, а добавляя новые источники.

Инфраструктуру тоже стоит продумать заранее: очередь или шина событий, единые журналы, алерты и метрики доставки, повторные попытки и дедупликация. Простое правило: если событие нельзя найти и объяснить через неделю, оно не помогает бизнесу.

Если нужна помощь на стороне интеграции и инфраструктуры, GSE.kz может выступить системным интегратором: помочь собрать архитектуру, настроить мониторинг и отказоустойчивость, а также подобрать и поставить серверную платформу под шину и сервисы (например, на базе серверов S200) с поддержкой 24/7."}

Минимальный контракт API для продаж и склада: сущности и события | GSE