Содержание

Создание заказа через API: транзакция, ответ и внешние события

Назначение: Сценарий процесса. Продукт: ShariX Open.

Для кого: Разработчик · Интегратор · Рецензент и приёмщик · Нейросеть.

Темы: Заказы → Создание и черновик · Заказы → Жизненный цикл · API → Контракты.

Сценарий описывает POST /dbsynce/api/v1/orders/ в ShariX Open: от получения запроса до сохранённого заказа, HTTP-ответа и событий после commit. Это описание проверенного исходного кода, а не отчёт о выполнении запроса на рабочем сервере.

Где находится операция

Корневой core/urls.py подключает dbsynce.urls по префиксу dbsynce/. При включённом DBSYNCE_OPEN_API_ENABLED регистрируются маршруты apiviews/open_api.py. POST принимает OpenOrderListCreateAPIView, создание выполняет lib/open_order_api.py:create_order_from_payload.

OpenAPIBaseView использует IsAuthenticated | HasAPIKey. Но предметная операция получает request.user и проверяет участников и доступные объекты. Пропуск по API key не означает, что вместо actor можно передать произвольного пользователя или создать заказ от любой организации. Для интеграции сначала определяют поддержанный контекст пользователя и предметные права.

Для проверки нужен предназначенный тестовый контекст: доступный тариф, компания/предложение, пользователь с подходящим основанием и допустимые данные получателя. Не подбирать чужие ID ради прохождения валидации.

Что выбрать до отправки

Условие Поведение текущей операции
creation_mode=draft Создать черновик с начальным Ticket TEMPLATE.
creation_mode=place Разместить заказ с начальным Ticket BOOKED; затем возможно действие назначения.
Режим отсутствует normalize_order_creation_mode выбирает place. Не считать отсутствующее поле командой сохранения черновика.
Категория subscription Операция принудительно выбирает place.
self_order=true Проверяются доступность self-order и предложение исполнителя текущего пользователя. Одновременный корпоративный заказ отклоняется.

Получатель услуги может отличаться от создателя. Операция отдельно получает receiver и его снимок receiver_name/receiver_phone. Состав обязательных полей зависит от выбранного предложения и его правил; произвольный универсальный payload не заменяет доступные клиенту options.

Сохранение в одной транзакции

  1. Нормализуются режим и self-order, выбираются доступные связанные объекты. Проверяются корпоративный контекст, получатель, время, длительность, количество и маршрут.
  2. Создаётся Orders. pk назначает БД; приложение не вычисляет его как максимальный ID плюс один.
  3. create_ticket_order создаёт SERVICE_REQUEST Ticket. Заказ сохраняет ссылку ticket_status и связанные вычисленные значения.
  4. Вызывается initialize_order_execution через backend-порт. Если runtime или политика не настроены, этапов может не быть; вызов сам по себе не доказывает создания плана.
  5. Если режим place и указан provider, выполняется ACTION_ASSIGN_CREATED. Неуспешный результат добавляется в warnings. Созданный заказ при этом может остаться сохранённым.
  6. Планируется мобильное событие order.created или order.placed. Сетевые callbacks, запланированные через on_commit, относятся к следующей границе.

create_order_from_payload помечена transaction.atomic. Исключение внутри неё до commit отменяет её записи. Неуспешный результат назначения, обработанный как warning, не равен такому исключению.

BPMN: границы транзакции и ответа

Описательная схема isExecutable=false. Она показывает проверенный API-путь и отдельно события после commit. Схема не исполняет заказ и не заменяет Blockly последующих обработчиков.

Схема BPMN

Загрузка схемы…

Исходник схемы
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_OrderCreateAPISource20261029" targetNamespace="urn:sharix:knowledge:handlers">
  <bpmn:process id="backend_Process_backend" name="ShariX Open: API создания заказа" isExecutable="false">
    <bpmn:documentation>{"notes": ["Схема свёрнута до границ HTTP/транзакции/событий. Ветви draft/place, subscription и назначения описаны рядом. Callback не гарантирует доставку. Аутентификация APIView предшествует операции; ошибка до commit откатывает её записи."], "sources": [{"commit": "778a3280cee115ba0efe3fdff7b0e708b0c10093", "path": "models/orders.py", "repository": "sharix-open-backend", "sha256": "4a5dbbec8a62ed3ca933ae5494032ba2a8a4369c7c1ece4011c98755df23faf2"}, {"commit": "778a3280cee115ba0efe3fdff7b0e708b0c10093", "path": "lib/open_order_api.py", "repository": "sharix-open-backend", "sha256": "8950a4169a352db3d250c9e08f6de7a4fa2a31985a247541411e20bd224ca2b6"}, {"commit": "778a3280cee115ba0efe3fdff7b0e708b0c10093", "path": "lib/ticket_gen.py", "repository": "sharix-open-backend", "sha256": "7bdf92007916aec7e968a45f142e5f98fffb5dfe9b61ee7fcae3c65c826dfbde"}, {"commit": "778a3280cee115ba0efe3fdff7b0e708b0c10093", "path": "lib/order_actions.py", "repository": "sharix-open-backend", "sha256": "27ce243408a46f1b700d94bf78a25c68ef21d51078bb03627174e4a424d82ef0"}, {"commit": "778a3280cee115ba0efe3fdff7b0e708b0c10093", "path": "lib/order_permissions.py", "repository": "sharix-open-backend", "sha256": "9c1636daa80c4da68c3ae5cf347e279127d142eeff91e969559e8bd54ca3d81d"}, {"commit": "778a3280cee115ba0efe3fdff7b0e708b0c10093", "path": "lib/mobile_order_events.py", "repository": "sharix-open-backend", "sha256": "818b21d471b629dcae3d96ee089e0f83406a41d03ad36a6c4ddabdf8857f2e68"}, {"commit": "778a3280cee115ba0efe3fdff7b0e708b0c10093", "path": "lib/integration_ports.py", "repository": "sharix-open-backend", "sha256": "d079ec56e6709b29d7755e47d2a88d423235fe8eb1bf7a92851ae25d74bf1066"}, {"commit": "778a3280cee115ba0efe3fdff7b0e708b0c10093", "path": "apiviews/open_api.py", "repository": "sharix-open-backend", "sha256": "cb8fdda304d29c2cc5c0fc938759a13c60022871b7b93d77883d521820021ad3"}, {"commit": "778a3280cee115ba0efe3fdff7b0e708b0c10093", "path": "urls.py", "repository": "sharix-open-backend", "sha256": "61ffd2f0563291da2dcc9488a5443cb5e2c0395108740a6700034cfd90548158"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "signals.py", "repository": "sharix-open-tickets", "sha256": "39aad8690a2e7d35cebfb052127a8130a951cbb4eea316c4f767a0a59fc5fa7e"}]}</bpmn:documentation>
    <bpmn:laneSet id="backend_Lanes_backend">
      <bpmn:lane id="backend_Lane_api" name="HTTP и транзакционная операция">
        <bpmn:flowNodeRef>backend_start</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>backend_create</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>backend_commit</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>backend_callbacks</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>backend_response</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>backend_error</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>backend_classify</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>backend_known</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>backend_other</bpmn:flowNodeRef>
      </bpmn:lane>
    </bpmn:laneSet>
    <bpmn:startEvent id="backend_start" name="POST orders">
      <bpmn:outgoing>backend_Flow_0</bpmn:outgoing>
    </bpmn:startEvent>
    <bpmn:serviceTask id="backend_create" name="Проверить и создать заказ (atomic)">
      <bpmn:incoming>backend_Flow_0</bpmn:incoming>
      <bpmn:outgoing>backend_Flow_1</bpmn:outgoing>
    </bpmn:serviceTask>
    <bpmn:serviceTask id="backend_commit" name="Commit: Order/Ticket/execution">
      <bpmn:incoming>backend_Flow_1</bpmn:incoming>
      <bpmn:outgoing>backend_Flow_2</bpmn:outgoing>
    </bpmn:serviceTask>
    <bpmn:serviceTask id="backend_callbacks" name="on_commit: отправить события">
      <bpmn:incoming>backend_Flow_2</bpmn:incoming>
      <bpmn:outgoing>backend_Flow_3</bpmn:outgoing>
    </bpmn:serviceTask>
    <bpmn:endEvent id="backend_response" name="201: snapshot и warnings">
      <bpmn:incoming>backend_Flow_3</bpmn:incoming>
    </bpmn:endEvent>
    <bpmn:boundaryEvent id="backend_error" name="Исключение" attachedToRef="backend_create" cancelActivity="true">
      <bpmn:outgoing>backend_Flow_4</bpmn:outgoing>
      <bpmn:errorEventDefinition />
    </bpmn:boundaryEvent>
    <bpmn:exclusiveGateway id="backend_classify" name="Тип отказа?">
      <bpmn:incoming>backend_Flow_4</bpmn:incoming>
      <bpmn:outgoing>backend_Flow_5</bpmn:outgoing>
      <bpmn:outgoing>backend_Flow_6</bpmn:outgoing>
    </bpmn:exclusiveGateway>
    <bpmn:endEvent id="backend_known" name="ValidationError 400; PermissionDenied 403">
      <bpmn:incoming>backend_Flow_5</bpmn:incoming>
    </bpmn:endEvent>
    <bpmn:endEvent id="backend_other" name="Прочие исключения: нет гарантии ответа 400">
      <bpmn:incoming>backend_Flow_6</bpmn:incoming>
    </bpmn:endEvent>
    <bpmn:sequenceFlow id="backend_Flow_0" sourceRef="backend_start" targetRef="backend_create" name="" />
    <bpmn:sequenceFlow id="backend_Flow_1" sourceRef="backend_create" targetRef="backend_commit" name="" />
    <bpmn:sequenceFlow id="backend_Flow_2" sourceRef="backend_commit" targetRef="backend_callbacks" name="" />
    <bpmn:sequenceFlow id="backend_Flow_3" sourceRef="backend_callbacks" targetRef="backend_response" name="" />
    <bpmn:sequenceFlow id="backend_Flow_4" sourceRef="backend_error" targetRef="backend_classify" name="" />
    <bpmn:sequenceFlow id="backend_Flow_5" sourceRef="backend_classify" targetRef="backend_known" name="Явно обработан" />
    <bpmn:sequenceFlow id="backend_Flow_6" sourceRef="backend_classify" targetRef="backend_other" name="Иной отказ" />
  </bpmn:process>
  <bpmn:collaboration id="Collaboration_OrderCreateAPISource20261029">
    <bpmn:participant id="backend_Pool_backend" name="ShariX Open: API создания заказа" processRef="backend_Process_backend" />
  </bpmn:collaboration>
  <bpmndi:BPMNDiagram id="Diagram_OrderCreateAPISource20261029">
    <bpmndi:BPMNPlane id="Plane_OrderCreateAPISource20261029" bpmnElement="Collaboration_OrderCreateAPISource20261029">
      <bpmndi:BPMNShape id="backend_DI_Pool_backend" bpmnElement="backend_Pool_backend" isHorizontal="true">
        <dc:Bounds x="0.0" y="0.0" width="1330" height="550" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="backend_DI_Lane_api" bpmnElement="backend_Lane_api" isHorizontal="true">
        <dc:Bounds x="30.0" y="0.0" width="1300" height="550" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="backend_DI_start" bpmnElement="backend_start">
        <dc:Bounds x="112.0" y="112.0" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="backend_DI_create" bpmnElement="backend_create">
        <dc:Bounds x="295.0" y="95.0" width="150" height="70" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="backend_DI_commit" bpmnElement="backend_commit">
        <dc:Bounds x="565.0" y="95.0" width="150" height="70" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="backend_DI_callbacks" bpmnElement="backend_callbacks">
        <dc:Bounds x="835.0" y="95.0" width="150" height="70" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="backend_DI_response" bpmnElement="backend_response">
        <dc:Bounds x="1152.0" y="112.0" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="backend_DI_error" bpmnElement="backend_error">
        <dc:Bounds x="402.0" y="147.0" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="backend_DI_classify" bpmnElement="backend_classify">
        <dc:Bounds x="345.0" y="265.0" width="50" height="50" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="backend_DI_known" bpmnElement="backend_known">
        <dc:Bounds x="682.0" y="272.0" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="backend_DI_other" bpmnElement="backend_other">
        <dc:Bounds x="352.0" y="432.0" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="backend_DI_Flow_0" bpmnElement="backend_Flow_0">
        <di:waypoint x="148.0" y="130.0" />
        <di:waypoint x="295.0" y="130.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="backend_DI_Flow_1" bpmnElement="backend_Flow_1">
        <di:waypoint x="445.0" y="130.0" />
        <di:waypoint x="565.0" y="130.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="backend_DI_Flow_2" bpmnElement="backend_Flow_2">
        <di:waypoint x="715.0" y="130.0" />
        <di:waypoint x="835.0" y="130.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="backend_DI_Flow_3" bpmnElement="backend_Flow_3">
        <di:waypoint x="985.0" y="130.0" />
        <di:waypoint x="1152.0" y="130.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="backend_DI_Flow_4" bpmnElement="backend_Flow_4">
        <di:waypoint x="420.0" y="183.0" />
        <di:waypoint x="420.0" y="225.0" />
        <di:waypoint x="370.0" y="225.0" />
        <di:waypoint x="370.0" y="265.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="backend_DI_Flow_5" bpmnElement="backend_Flow_5">
        <di:waypoint x="395.0" y="290.0" />
        <di:waypoint x="682.0" y="290.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="backend_DI_Flow_6" bpmnElement="backend_Flow_6">
        <di:waypoint x="370.0" y="315.0" />
        <di:waypoint x="370.0" y="432.0" />
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</bpmn:definitions>

Что означает ответ

После возврата из операции view получает snapshot по созданному order.pk и формирует ответ:

Нужно сохранить ID возвращённого заказа и прочитать warnings: success=true означает успех создания, а не безусловный успех назначения и всех внешних доставок.

Пример трактовки: если заказ создан, но предложение исполнителя не удалось назначить, клиент показывает созданный заказ и причину из warnings. Он не должен отправлять ещё один POST только ради повторной попытки назначения: это уже другое действие над существующим заказом.

Получение snapshot и сериализация ответа выполняются после возврата из функции создания. Поэтому ошибка подготовки HTTP-ответа также не доказывает, что заказ не был сохранён. Эту границу учитывают при восстановлении после таймаута/ошибки.

События после commit

Ticket-сигналы планируют ticket.changed; при каноническом переходе предыдущему обработчику также передаётся ticket.moved. Обработчик получает сигнал и сверяет сохранённое состояние. schedule_order_mobile_event предпринимает доставку мобильного события после commit.

Атомарность БД не гарантирует однократную доставку сети. Если данные уже committed, транспортная ошибка не откатывает их автоматически. Отдельно подтверждают:

Это три отдельных свидетельства.

Отказы и неизвестный результат

Ситуация Что зафиксировано в коде / что делать
PermissionDenied из создания View возвращает 403 с success=false и message.
Django ValidationError из создания View возвращает 400 с success=false и errors.
Неизвестный creation_mode Нормализация бросает ValueError; эта view явно его не перехватывает. Нельзя обещать готовый ответ 400 — это известное ограничение.
Таймаут, обрыв или ошибка после сохранения Сначала установить, создан ли заказ. Повтор POST может создать ещё один.

В изученном клиентском POST не найден отдельный контракт idempotency key. Сначала проверить доступный пользователю список/историю и контекст операции; если исход нельзя установить однозначно — передать его на разбор. Последний ID в общей таблице не является доказательством результата именно этого запроса.

HTML-форма проверяется отдельно

sharix_admin.views.order.OrderCreateView.form_valid собирает заказ через OrderForm и собственный atomic-блок. Она использует общие ticket/action/execution primitives, но не вызывает целиком create_order_from_payload; у неё есть дополнительные шаги подписки и расширений.

Форма отдельно перехватывает неизвестный способ сохранения и возвращает ошибку формы. Это не исправляет API-ветку ValueError. При изменении общего правила нужно проверить оба входа, а не считать успешный API доказательством правильности формы.

Как принять изменение этой операции

На предназначенной установке проверить:

Не повторять создание на рабочих данных для проверки потери ответа. Такой случай воспроизводят в изолированном тестовом контексте. Общие критерии — приёмка изменений, структура данных — заказ и Ticket, локальное API-расхождение — Webservice Running.

Сценарий касается открытого API ShariX Open. Внутренние операции Platform и закрытых компонентов описываются в intrawiki; проверку shared-изменения другим продуктом передают его ответственному.

Исходники, сверенные 31.10.2026

Проверены опубликованные исходники указанных commits. Это анализ кода, а не подтверждение версии, данных или выполненного запроса на сервере.