Инструменты пользователя

Инструменты сайта


sharixopen:knowledge:scenarios:neg_request_declined

NEG_REQUEST в DECLINED: сохранение active Relationship и очистка после срока

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

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

Темы: Обработчики → NEG_REQUEST · Архитектура → Владение моделями · Обработчики → Runtime.

Область: ShariX Open. Обработчик: open_neg_request_declined, тип NEG_REQUEST (4), рабочий статус DECLINED (459). 31.10.2026 исходники повторно получены из Git. Обе Python-копии совпали с новой канонической генерацией Blockly; проверены 11 исходников генераторов. Сам обработчик и его внешние действия не запускались. Выбранная версия, настройки и доступность подключений конкретной установки проверяются отдельно.

Назначение и границы

Handler получает уже отклонённый запрос на публичную dbsynce.Relationship. После контрольного срока:

  • Active: сохранить Relationship и закрыть только Ticket.
  • Неактивная: попробовать soft delete со status=2, затем отдельно закрыть Ticket.
  • Связь не найдена при повторном чтении: удаление становится no-op, после которого возможна попытка CLOSED.

Решение об отказе принято до этой стадии. Политика Permissions на Relationship не переносится.

Это отличается от ACCESS_REQUEST/DECLINED, который при active permission отправляет ALERT и остаётся ждать разбора. В NEG/DECLINED active-состояние не блокирует закрытие Ticket после срока. Встроенный алгоритм пишет локальную запись журнала о сохранении active, но не вызывает notify_admins или notify_creator.

В публичной модели Relationship используются:

  • user_who — инициирующая сторона;
  • user_whom — связанная сторона;
  • neg_type — тип отношения;
  • company — контекст компании;
  • ticket_status — ссылка на сопровождающий Ticket.

Сохранение active Relationship не является отзывом роли или удалением пользователя. NEGREQ_AUTOACCEPT в этой стадии не используется.

Просмотрщик не исполняет процесс: BPMN имеют isExecutable=false, runtime запускает выбранный проверенный Python из Blockly. End event завершает показанный проход, а не аннулирует действующие отношения сторон.

Один проход и ожидание (BPMN)

Схема 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" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" id="Definitions_neg_declined_logic" targetNamespace="urn:sharix:knowledge:handlers">
  <bpmn:process id="negdeclined_Process_negdeclined" name="open_neg_request_declined: контрольный срок, сохранение active и закрытие Ticket" isExecutable="false">
    <bpmn:documentation>{"notes": ["Git built-in, BPMN isExecutable=false; код handler не выполняется при генерации.", "Сначала grace по Ticket.updated_at, затем проверка Relationship.", "Active Relationship сохраняется: после срока закрывается только Ticket.", "Soft delete неактивной связи — status=2, не физическое удаление."], "sources": [{"commit": "76e0c693b628ed4a1fa34515a3f8abdff26149bb", "path": "handlers/open_neg_request_declined.py", "repository": "sharix-open-handlers-examples", "sha256": "6813d34df6519db2544c33c365daab408c50abf0c4d7a94d5b4f5ba1289d0355"}, {"commit": "76e0c693b628ed4a1fa34515a3f8abdff26149bb", "path": "generated_handler_blocks/open_neg_request_declined.json", "repository": "sharix-open-handlers-examples", "sha256": "93d5a75a98975fccf560e9a03f2600c166d9eb9acf90dab0256e02a1b9068f95"}, {"commit": "76e0c693b628ed4a1fa34515a3f8abdff26149bb", "path": "catalog.py", "repository": "sharix-open-handlers-examples", "sha256": "e3dcb72b7e620e2837adacd7af053f35b04cdff475789496d9c114e17d57dfde"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "core/blockly_runtime.py", "repository": "sharix-open-handlers-backend", "sha256": "3e222150cbff315077c6c9b160d46e59856cedf6d6591d213e958c63cb2f2428"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "core/handler_operations.py", "repository": "sharix-open-handlers-backend", "sha256": "28620658501c375f556e24437871620b84a17a88d0b62b2d1a3cc5deb2f14b1e"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "core/ejabberd_muc.py", "repository": "sharix-open-handlers-backend", "sha256": "35c2eba89fab36b5c444439a87beaa516b55133991136d5370418d92a18d7bcd"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "core/requesters.py", "repository": "sharix-open-handlers-backend", "sha256": "965a348cea477722bbaf4c8c6b06023eb238b71664a332f7ea8c90bfe72ea808"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "ticket_blockly_codegen.py", "repository": "sharix-open-handlers-backend", "sha256": "bce71eab622922f8dd0954222842349799b60e18a0ab6310cc3e5f305603661e"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "ticket_codegen_contract.json", "repository": "sharix-open-handlers-backend", "sha256": "0bfd2b0ebb07bd380ed3c867386536ab88ef7dfa6e13f1cd8c91d32bb02c1ae9"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "lib/tickets.py", "repository": "sharix-open-tickets", "sha256": "0d260c17947f9a186d07af2749159780e3c7354a95b4bafe9d67493663c96883"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "api/views/handler_workflow.py", "repository": "sharix-open-tickets", "sha256": "f97c4b467a9a30749f5c4b86b39a833c430e57aaa1f030f70030d9b5c609d368"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "api/handler_auth.py", "repository": "sharix-open-tickets", "sha256": "ac76dc45aaa9742e5e1da0b98aae914bb2e287048d33e97b4a245a6997bc8425"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "utils.py", "repository": "sharix-open-tickets", "sha256": "a5680205c6522e1d9a74d9d267fe9ee96faf6f06436b00877fc0a93d80234bf5"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "views/ticket_detail.py", "repository": "sharix-open-tickets", "sha256": "05d344b0fba14cf895400694d4a18f486c800fef08dd1acb43ea46909b9e7e59"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "signals.py", "repository": "sharix-open-tickets", "sha256": "39aad8690a2e7d35cebfb052127a8130a951cbb4eea316c4f767a0a59fc5fa7e"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "services/handler_wakeup.py", "repository": "sharix-open-tickets", "sha256": "c9fdc62a18301fd7c075395c697d5b1c738c77713aa71fcbb5ab097e173d98de"}, {"commit": "778a3280cee115ba0efe3fdff7b0e708b0c10093", "path": "apiviews/handler_workflow.py", "repository": "sharix-open-backend", "sha256": "0f699a4e16760cbbdd09d894093c685ba69073fedf0522852cb1fdc9811636e3"}, {"commit": "778a3280cee115ba0efe3fdff7b0e708b0c10093", "path": "lib/dicts.py", "repository": "sharix-open-backend", "sha256": "d92ddaa0964324e337303f8de110acf6ff21e15d7b40a6e523ce78ed48b7d8c5"}, {"commit": "778a3280cee115ba0efe3fdff7b0e708b0c10093", "path": "models/relationship.py", "repository": "sharix-open-backend", "sha256": "873f27666a7d03b8058958b752d0e69d17d3433daf72f901fa88275cf10f43aa"}]}</bpmn:documentation>
    <bpmn:laneSet id="negdeclined_Lanes_negdeclined">
      <bpmn:lane id="negdeclined_Lane_worker" name="Worker: один проход Python">
        <bpmn:flowNodeRef>negdeclined_start</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>negdeclined_status</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>negdeclined_age</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>negdeclined_read</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>negdeclined_active</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>negdeclined_log</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>negdeclined_close</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>negdeclined_confirmed</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>negdeclined_remove</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>negdeclined_end</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>negdeclined_stale</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>negdeclined_stale_end</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>negdeclined_grace</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>negdeclined_delete</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>negdeclined_not_found</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>negdeclined_read_error</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>negdeclined_delete_error</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>negdeclined_close_error</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>negdeclined_retry</bpmn:flowNodeRef>
      </bpmn:lane>
      <bpmn:lane id="negdeclined_Lane_queue" name="Планировщик: между проходами">
        <bpmn:flowNodeRef>negdeclined_wait</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>negdeclined_timer</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>negdeclined_revision</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>negdeclined_ready</bpmn:flowNodeRef>
      </bpmn:lane>
    </bpmn:laneSet>
    <bpmn:startEvent id="negdeclined_start" name="Ticket готов к проходу">
      <bpmn:outgoing>negdeclined_Flow_0</bpmn:outgoing>
    </bpmn:startEvent>
    <bpmn:exclusiveGateway id="negdeclined_status" name="Статус DECLINED?">
      <bpmn:incoming>negdeclined_Flow_0</bpmn:incoming>
      <bpmn:incoming>negdeclined_Flow_24</bpmn:incoming>
      <bpmn:outgoing>negdeclined_Flow_1</bpmn:outgoing>
      <bpmn:outgoing>negdeclined_Flow_2</bpmn:outgoing>
    </bpmn:exclusiveGateway>
    <bpmn:exclusiveGateway id="negdeclined_age" name="Срок от updated_at наступил?">
      <bpmn:incoming>negdeclined_Flow_1</bpmn:incoming>
      <bpmn:outgoing>negdeclined_Flow_4</bpmn:outgoing>
      <bpmn:outgoing>negdeclined_Flow_5</bpmn:outgoing>
    </bpmn:exclusiveGateway>
    <bpmn:serviceTask id="negdeclined_read" name="Scoped API: проверить Relationship active">
      <bpmn:incoming>negdeclined_Flow_4</bpmn:incoming>
      <bpmn:outgoing>negdeclined_Flow_6</bpmn:outgoing>
    </bpmn:serviceTask>
    <bpmn:exclusiveGateway id="negdeclined_active" name="Связь active?">
      <bpmn:incoming>negdeclined_Flow_6</bpmn:incoming>
      <bpmn:outgoing>negdeclined_Flow_7</bpmn:outgoing>
      <bpmn:outgoing>negdeclined_Flow_8</bpmn:outgoing>
    </bpmn:exclusiveGateway>
    <bpmn:serviceTask id="negdeclined_log" name="Журнал: сохранить active; закрыть только Ticket">
      <bpmn:incoming>negdeclined_Flow_7</bpmn:incoming>
      <bpmn:outgoing>negdeclined_Flow_9</bpmn:outgoing>
    </bpmn:serviceTask>
    <bpmn:serviceTask id="negdeclined_close" name="Scoped Ticket API: CLOSED">
      <bpmn:incoming>negdeclined_Flow_9</bpmn:incoming>
      <bpmn:incoming>negdeclined_Flow_10</bpmn:incoming>
      <bpmn:outgoing>negdeclined_Flow_11</bpmn:outgoing>
    </bpmn:serviceTask>
    <bpmn:exclusiveGateway id="negdeclined_confirmed" name="Переход подтверждён?">
      <bpmn:incoming>negdeclined_Flow_11</bpmn:incoming>
      <bpmn:outgoing>negdeclined_Flow_12</bpmn:outgoing>
      <bpmn:outgoing>negdeclined_Flow_14</bpmn:outgoing>
    </bpmn:exclusiveGateway>
    <bpmn:serviceTask id="negdeclined_remove" name="Убрать локальный Ticket и комнату">
      <bpmn:incoming>negdeclined_Flow_12</bpmn:incoming>
      <bpmn:outgoing>negdeclined_Flow_13</bpmn:outgoing>
    </bpmn:serviceTask>
    <bpmn:endEvent id="negdeclined_end" name="Этот проход завершён">
      <bpmn:incoming>negdeclined_Flow_13</bpmn:incoming>
    </bpmn:endEvent>
    <bpmn:serviceTask id="negdeclined_stale" name="Убрать Ticket и комнату">
      <bpmn:incoming>negdeclined_Flow_2</bpmn:incoming>
      <bpmn:outgoing>negdeclined_Flow_3</bpmn:outgoing>
    </bpmn:serviceTask>
    <bpmn:endEvent id="negdeclined_stale_end" name="Прежний владелец освобождён">
      <bpmn:incoming>negdeclined_Flow_3</bpmn:incoming>
    </bpmn:endEvent>
    <bpmn:serviceTask id="negdeclined_grace" name="declined_relationship_grace; короткий defer">
      <bpmn:documentation>min(updated_at+NEG_REQUEST_DECLINED_CLEANUP_GRACE_DAYS, now+DEFERRED_TICKET_RECHECK_SECONDS)</bpmn:documentation>
      <bpmn:incoming>negdeclined_Flow_5</bpmn:incoming>
      <bpmn:outgoing>negdeclined_Flow_15</bpmn:outgoing>
    </bpmn:serviceTask>
    <bpmn:serviceTask id="negdeclined_delete" name="Повторно прочитать; soft-delete неактивную связь">
      <bpmn:documentation>Отсутствующая связь: no-op. Ставшая active при повторном чтении: исключение, не удаление.</bpmn:documentation>
      <bpmn:incoming>negdeclined_Flow_8</bpmn:incoming>
      <bpmn:outgoing>negdeclined_Flow_10</bpmn:outgoing>
    </bpmn:serviceTask>
    <bpmn:endEvent id="negdeclined_not_found" name="404: очистка запрошена">
      <bpmn:incoming>negdeclined_Flow_14</bpmn:incoming>
    </bpmn:endEvent>
    <bpmn:boundaryEvent id="negdeclined_read_error" name="Ошибка read API" attachedToRef="negdeclined_read" cancelActivity="true">
      <bpmn:outgoing>negdeclined_Flow_16</bpmn:outgoing>
      <bpmn:errorEventDefinition />
    </bpmn:boundaryEvent>
    <bpmn:boundaryEvent id="negdeclined_delete_error" name="Ошибка / active race" attachedToRef="negdeclined_delete" cancelActivity="true">
      <bpmn:outgoing>negdeclined_Flow_17</bpmn:outgoing>
      <bpmn:errorEventDefinition />
    </bpmn:boundaryEvent>
    <bpmn:boundaryEvent id="negdeclined_close_error" name="Ошибка перехода" attachedToRef="negdeclined_close" cancelActivity="true">
      <bpmn:outgoing>negdeclined_Flow_18</bpmn:outgoing>
      <bpmn:errorEventDefinition />
    </bpmn:boundaryEvent>
    <bpmn:serviceTask id="negdeclined_retry" name="processing_error + общий defer">
      <bpmn:incoming>negdeclined_Flow_16</bpmn:incoming>
      <bpmn:incoming>negdeclined_Flow_17</bpmn:incoming>
      <bpmn:incoming>negdeclined_Flow_18</bpmn:incoming>
      <bpmn:outgoing>negdeclined_Flow_19</bpmn:outgoing>
    </bpmn:serviceTask>
    <bpmn:eventBasedGateway id="negdeclined_wait" name="Что наступило раньше?">
      <bpmn:incoming>negdeclined_Flow_15</bpmn:incoming>
      <bpmn:incoming>negdeclined_Flow_19</bpmn:incoming>
      <bpmn:outgoing>negdeclined_Flow_20</bpmn:outgoing>
      <bpmn:outgoing>negdeclined_Flow_21</bpmn:outgoing>
    </bpmn:eventBasedGateway>
    <bpmn:intermediateCatchEvent id="negdeclined_timer" name="Наступил локальный срок defer">
      <bpmn:incoming>negdeclined_Flow_20</bpmn:incoming>
      <bpmn:outgoing>negdeclined_Flow_22</bpmn:outgoing>
      <bpmn:timerEventDefinition>
        <bpmn:timeDate xsi:type="bpmn:tFormalExpression">deferred_until</bpmn:timeDate>
      </bpmn:timerEventDefinition>
    </bpmn:intermediateCatchEvent>
    <bpmn:intermediateCatchEvent id="negdeclined_revision" name="Новая authoritative revision">
      <bpmn:incoming>negdeclined_Flow_21</bpmn:incoming>
      <bpmn:outgoing>negdeclined_Flow_23</bpmn:outgoing>
      <bpmn:messageEventDefinition />
    </bpmn:intermediateCatchEvent>
    <bpmn:serviceTask id="negdeclined_ready" name="Снять defer, вернуть в ready FIFO">
      <bpmn:incoming>negdeclined_Flow_22</bpmn:incoming>
      <bpmn:incoming>negdeclined_Flow_23</bpmn:incoming>
      <bpmn:outgoing>negdeclined_Flow_24</bpmn:outgoing>
    </bpmn:serviceTask>
    <bpmn:sequenceFlow id="negdeclined_Flow_0" sourceRef="negdeclined_start" targetRef="negdeclined_status" name="" />
    <bpmn:sequenceFlow id="negdeclined_Flow_1" sourceRef="negdeclined_status" targetRef="negdeclined_age" name="Да" />
    <bpmn:sequenceFlow id="negdeclined_Flow_2" sourceRef="negdeclined_status" targetRef="negdeclined_stale" name="Нет" />
    <bpmn:sequenceFlow id="negdeclined_Flow_3" sourceRef="negdeclined_stale" targetRef="negdeclined_stale_end" name="" />
    <bpmn:sequenceFlow id="negdeclined_Flow_4" sourceRef="negdeclined_age" targetRef="negdeclined_read" name="Да" />
    <bpmn:sequenceFlow id="negdeclined_Flow_5" sourceRef="negdeclined_age" targetRef="negdeclined_grace" name="Нет" />
    <bpmn:sequenceFlow id="negdeclined_Flow_6" sourceRef="negdeclined_read" targetRef="negdeclined_active" name="" />
    <bpmn:sequenceFlow id="negdeclined_Flow_7" sourceRef="negdeclined_active" targetRef="negdeclined_log" name="Да" />
    <bpmn:sequenceFlow id="negdeclined_Flow_8" sourceRef="negdeclined_active" targetRef="negdeclined_delete" name="Нет / не найдена" />
    <bpmn:sequenceFlow id="negdeclined_Flow_9" sourceRef="negdeclined_log" targetRef="negdeclined_close" name="" />
    <bpmn:sequenceFlow id="negdeclined_Flow_10" sourceRef="negdeclined_delete" targetRef="negdeclined_close" name="" />
    <bpmn:sequenceFlow id="negdeclined_Flow_11" sourceRef="negdeclined_close" targetRef="negdeclined_confirmed" name="" />
    <bpmn:sequenceFlow id="negdeclined_Flow_12" sourceRef="negdeclined_confirmed" targetRef="negdeclined_remove" name="Да" />
    <bpmn:sequenceFlow id="negdeclined_Flow_13" sourceRef="negdeclined_remove" targetRef="negdeclined_end" name="" />
    <bpmn:sequenceFlow id="negdeclined_Flow_14" sourceRef="negdeclined_confirmed" targetRef="negdeclined_not_found" name="Нет (404)" />
    <bpmn:sequenceFlow id="negdeclined_Flow_15" sourceRef="negdeclined_grace" targetRef="negdeclined_wait" name="" />
    <bpmn:sequenceFlow id="negdeclined_Flow_16" sourceRef="negdeclined_read_error" targetRef="negdeclined_retry" name="" />
    <bpmn:sequenceFlow id="negdeclined_Flow_17" sourceRef="negdeclined_delete_error" targetRef="negdeclined_retry" name="" />
    <bpmn:sequenceFlow id="negdeclined_Flow_18" sourceRef="negdeclined_close_error" targetRef="negdeclined_retry" name="" />
    <bpmn:sequenceFlow id="negdeclined_Flow_19" sourceRef="negdeclined_retry" targetRef="negdeclined_wait" name="" />
    <bpmn:sequenceFlow id="negdeclined_Flow_20" sourceRef="negdeclined_wait" targetRef="negdeclined_timer" name="" />
    <bpmn:sequenceFlow id="negdeclined_Flow_21" sourceRef="negdeclined_wait" targetRef="negdeclined_revision" name="" />
    <bpmn:sequenceFlow id="negdeclined_Flow_22" sourceRef="negdeclined_timer" targetRef="negdeclined_ready" name="" />
    <bpmn:sequenceFlow id="negdeclined_Flow_23" sourceRef="negdeclined_revision" targetRef="negdeclined_ready" name="" />
    <bpmn:sequenceFlow id="negdeclined_Flow_24" sourceRef="negdeclined_ready" targetRef="negdeclined_status" name="Новый проход" />
  </bpmn:process>
  <bpmn:collaboration id="Collaboration_neg_declined_logic">
    <bpmn:participant id="negdeclined_Pool_negdeclined" name="open_neg_request_declined: контрольный срок, сохранение active и закрытие Ticket" processRef="negdeclined_Process_negdeclined" />
  </bpmn:collaboration>
  <bpmndi:BPMNDiagram id="Diagram_neg_declined_logic">
    <bpmndi:BPMNPlane id="Plane_neg_declined_logic" bpmnElement="Collaboration_neg_declined_logic">
      <bpmndi:BPMNShape id="negdeclined_DI_Pool_negdeclined" bpmnElement="negdeclined_Pool_negdeclined" isHorizontal="true">
        <dc:Bounds x="0.0" y="0.0" width="2540" height="1010" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="negdeclined_DI_Lane_worker" bpmnElement="negdeclined_Lane_worker" isHorizontal="true">
        <dc:Bounds x="30.0" y="0.0" width="2510" height="610.0" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="negdeclined_DI_Lane_queue" bpmnElement="negdeclined_Lane_queue" isHorizontal="true">
        <dc:Bounds x="30.0" y="610.0" width="2510" height="400.0" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="negdeclined_DI_start" bpmnElement="negdeclined_start">
        <dc:Bounds x="132.0" y="92.0" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="negdeclined_DI_status" bpmnElement="negdeclined_status">
        <dc:Bounds x="325.0" y="85.0" width="50" height="50" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="negdeclined_DI_age" bpmnElement="negdeclined_age">
        <dc:Bounds x="565.0" y="85.0" width="50" height="50" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="negdeclined_DI_read" bpmnElement="negdeclined_read">
        <dc:Bounds x="775.0" y="75.0" width="150" height="70" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="negdeclined_DI_active" bpmnElement="negdeclined_active">
        <dc:Bounds x="1085.0" y="85.0" width="50" height="50" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="negdeclined_DI_log" bpmnElement="negdeclined_log">
        <dc:Bounds x="1295.0" y="75.0" width="150" height="70" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="negdeclined_DI_close" bpmnElement="negdeclined_close">
        <dc:Bounds x="1555.0" y="75.0" width="150" height="70" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="negdeclined_DI_confirmed" bpmnElement="negdeclined_confirmed">
        <dc:Bounds x="1865.0" y="85.0" width="50" height="50" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="negdeclined_DI_remove" bpmnElement="negdeclined_remove">
        <dc:Bounds x="2075.0" y="75.0" width="150" height="70" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="negdeclined_DI_end" bpmnElement="negdeclined_end">
        <dc:Bounds x="2362.0" y="92.0" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="negdeclined_DI_stale" bpmnElement="negdeclined_stale">
        <dc:Bounds x="275.0" y="275.0" width="150" height="70" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="negdeclined_DI_stale_end" bpmnElement="negdeclined_stale_end">
        <dc:Bounds x="572.0" y="292.0" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="negdeclined_DI_grace" bpmnElement="negdeclined_grace">
        <dc:Bounds x="775.0" y="475.0" width="150" height="70" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="negdeclined_DI_delete" bpmnElement="negdeclined_delete">
        <dc:Bounds x="1295.0" y="275.0" width="150" height="70" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="negdeclined_DI_not_found" bpmnElement="negdeclined_not_found">
        <dc:Bounds x="1872.0" y="292.0" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="negdeclined_DI_read_error" bpmnElement="negdeclined_read_error">
        <dc:Bounds x="882.0" y="127.0" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="negdeclined_DI_delete_error" bpmnElement="negdeclined_delete_error">
        <dc:Bounds x="1402.0" y="327.0" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="negdeclined_DI_close_error" bpmnElement="negdeclined_close_error">
        <dc:Bounds x="1662.0" y="127.0" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="negdeclined_DI_retry" bpmnElement="negdeclined_retry">
        <dc:Bounds x="2075.0" y="475.0" width="150" height="70" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="negdeclined_DI_wait" bpmnElement="negdeclined_wait">
        <dc:Bounds x="1085.0" y="755.0" width="50" height="50" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="negdeclined_DI_timer" bpmnElement="negdeclined_timer">
        <dc:Bounds x="1352.0" y="692.0" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="negdeclined_DI_revision" bpmnElement="negdeclined_revision">
        <dc:Bounds x="1352.0" y="892.0" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="negdeclined_DI_ready" bpmnElement="negdeclined_ready">
        <dc:Bounds x="1555.0" y="745.0" width="150" height="70" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="negdeclined_DI_Flow_0" bpmnElement="negdeclined_Flow_0">
        <di:waypoint x="168.0" y="110.0" />
        <di:waypoint x="325.0" y="110.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="negdeclined_DI_Flow_1" bpmnElement="negdeclined_Flow_1">
        <di:waypoint x="375.0" y="110.0" />
        <di:waypoint x="565.0" y="110.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="negdeclined_DI_Flow_2" bpmnElement="negdeclined_Flow_2">
        <di:waypoint x="350.0" y="135.0" />
        <di:waypoint x="350.0" y="275.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="negdeclined_DI_Flow_3" bpmnElement="negdeclined_Flow_3">
        <di:waypoint x="425.0" y="310.0" />
        <di:waypoint x="572.0" y="310.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="negdeclined_DI_Flow_4" bpmnElement="negdeclined_Flow_4">
        <di:waypoint x="615.0" y="110.0" />
        <di:waypoint x="775.0" y="110.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="negdeclined_DI_Flow_5" bpmnElement="negdeclined_Flow_5">
        <di:waypoint x="615.0" y="110.0" />
        <di:waypoint x="710.0" y="110.0" />
        <di:waypoint x="710.0" y="440.0" />
        <di:waypoint x="850.0" y="440.0" />
        <di:waypoint x="850.0" y="475.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="negdeclined_DI_Flow_6" bpmnElement="negdeclined_Flow_6">
        <di:waypoint x="925.0" y="110.0" />
        <di:waypoint x="1085.0" y="110.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="negdeclined_DI_Flow_7" bpmnElement="negdeclined_Flow_7">
        <di:waypoint x="1135.0" y="110.0" />
        <di:waypoint x="1295.0" y="110.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="negdeclined_DI_Flow_8" bpmnElement="negdeclined_Flow_8">
        <di:waypoint x="1135.0" y="110.0" />
        <di:waypoint x="1215.0" y="110.0" />
        <di:waypoint x="1215.0" y="310.0" />
        <di:waypoint x="1295.0" y="310.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="negdeclined_DI_Flow_9" bpmnElement="negdeclined_Flow_9">
        <di:waypoint x="1445.0" y="110.0" />
        <di:waypoint x="1555.0" y="110.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="negdeclined_DI_Flow_10" bpmnElement="negdeclined_Flow_10">
        <di:waypoint x="1445.0" y="310.0" />
        <di:waypoint x="1490.0" y="310.0" />
        <di:waypoint x="1550.0" y="310.0" />
        <di:waypoint x="1550.0" y="45.0" />
        <di:waypoint x="1630.0" y="45.0" />
        <di:waypoint x="1630.0" y="75.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="negdeclined_DI_Flow_11" bpmnElement="negdeclined_Flow_11">
        <di:waypoint x="1705.0" y="110.0" />
        <di:waypoint x="1865.0" y="110.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="negdeclined_DI_Flow_12" bpmnElement="negdeclined_Flow_12">
        <di:waypoint x="1915.0" y="110.0" />
        <di:waypoint x="2075.0" y="110.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="negdeclined_DI_Flow_13" bpmnElement="negdeclined_Flow_13">
        <di:waypoint x="2225.0" y="110.0" />
        <di:waypoint x="2362.0" y="110.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="negdeclined_DI_Flow_14" bpmnElement="negdeclined_Flow_14">
        <di:waypoint x="1890.0" y="135.0" />
        <di:waypoint x="1890.0" y="292.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="negdeclined_DI_Flow_15" bpmnElement="negdeclined_Flow_15">
        <di:waypoint x="925.0" y="510.0" />
        <di:waypoint x="1005.0" y="510.0" />
        <di:waypoint x="1005.0" y="780.0" />
        <di:waypoint x="1085.0" y="780.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="negdeclined_DI_Flow_16" bpmnElement="negdeclined_Flow_16">
        <di:waypoint x="918.0" y="145.0" />
        <di:waypoint x="960.0" y="145.0" />
        <di:waypoint x="960.0" y="580.0" />
        <di:waypoint x="2150.0" y="580.0" />
        <di:waypoint x="2150.0" y="475.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="negdeclined_DI_Flow_17" bpmnElement="negdeclined_Flow_17">
        <di:waypoint x="1438.0" y="345.0" />
        <di:waypoint x="1480.0" y="345.0" />
        <di:waypoint x="1480.0" y="560.0" />
        <di:waypoint x="2150.0" y="560.0" />
        <di:waypoint x="2150.0" y="475.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="negdeclined_DI_Flow_18" bpmnElement="negdeclined_Flow_18">
        <di:waypoint x="1698.0" y="145.0" />
        <di:waypoint x="1740.0" y="145.0" />
        <di:waypoint x="2150.0" y="145.0" />
        <di:waypoint x="2150.0" y="475.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="negdeclined_DI_Flow_19" bpmnElement="negdeclined_Flow_19">
        <di:waypoint x="2225.0" y="510.0" />
        <di:waypoint x="2280.0" y="510.0" />
        <di:waypoint x="2280.0" y="640.0" />
        <di:waypoint x="1110.0" y="640.0" />
        <di:waypoint x="1110.0" y="755.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="negdeclined_DI_Flow_20" bpmnElement="negdeclined_Flow_20">
        <di:waypoint x="1135.0" y="780.0" />
        <di:waypoint x="1243.5" y="780.0" />
        <di:waypoint x="1243.5" y="710.0" />
        <di:waypoint x="1352.0" y="710.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="negdeclined_DI_Flow_21" bpmnElement="negdeclined_Flow_21">
        <di:waypoint x="1135.0" y="780.0" />
        <di:waypoint x="1243.5" y="780.0" />
        <di:waypoint x="1243.5" y="910.0" />
        <di:waypoint x="1352.0" y="910.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="negdeclined_DI_Flow_22" bpmnElement="negdeclined_Flow_22">
        <di:waypoint x="1388.0" y="710.0" />
        <di:waypoint x="1471.5" y="710.0" />
        <di:waypoint x="1471.5" y="780.0" />
        <di:waypoint x="1555.0" y="780.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="negdeclined_DI_Flow_23" bpmnElement="negdeclined_Flow_23">
        <di:waypoint x="1388.0" y="910.0" />
        <di:waypoint x="1471.5" y="910.0" />
        <di:waypoint x="1471.5" y="780.0" />
        <di:waypoint x="1555.0" y="780.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="negdeclined_DI_Flow_24" bpmnElement="negdeclined_Flow_24">
        <di:waypoint x="1705.0" y="780.0" />
        <di:waypoint x="2520.0" y="780.0" />
        <di:waypoint x="2520.0" y="25.0" />
        <di:waypoint x="350.0" y="25.0" />
        <di:waypoint x="350.0" y="85.0" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="2240.0" y="5.0" width="180" height="26" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</bpmn:definitions>
  • Чужой статус — запросить удаление локального Ticket и комнаты.
  • Контрольный срок ещё не наступил — записать declined_relationship_grace, назначить короткий defer и освободить worker. До этого срока Relationship вообще не читается данной бизнес-ветвью.
  • Срок наступил, Relationship active — записать в журнал, что active сохраняется, и перейти к запросу CLOSED без soft delete.
  • Срок наступил, Relationship не active/не найдена — вызвать soft_delete_related_relationship, затем запросить CLOSED. При успешном переходе убрать локальный Ticket и комнату.

Расчёт контрольного срока:

  • начало — Ticket.updated_at;
  • интервал — NEG_REQUEST_DECLINED_CLEANUP_GRACE_DAYS;
  • минимум настройки — 0 дней;
  • новая сохранённая ревизия может сдвинуть дату.

Created_at и отдельная дата отказа вместо updated_at не используются. Фактическое значение сервера здесь не предполагается.

Ожидание контрольного срока

Граница — updated_at + grace_days. Следующий локальный defer выбирается как минимум этой границы и now + DEFERRED_TICKET_RECHECK_SECONDS: handler периодически сверяется, не занимает worker до конца grace и может получить новую ревизию раньше. Локальная причина и строка журнала defer — declined_relationship_grace.

Если updated_at отсутствует либо не разбирается, проверка возраста возвращает false, а helper расчёта не устанавливает корректный срок. Это не доказательство истёкшего grace и не штатное ожидание по достоверной дате; данные нужно сверить. Новая authoritative revision может снять defer, повтор того же снимка — нет. После рестарта очередь заново получает актуальное состояние.

Backend soft delete и отдельное закрытие (BPMN)

Схема 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_neg_declined_cleanup" targetNamespace="urn:sharix:knowledge:handlers">
  <bpmn:process id="relationship_Process_relationship" name="Backend relationship API: action=soft_delete" isExecutable="false">
    <bpmn:documentation>{"notes": ["Git built-in, BPMN isExecutable=false; код handler не выполняется при генерации.", "Сначала grace по Ticket.updated_at, затем проверка Relationship.", "Active Relationship сохраняется: после срока закрывается только Ticket.", "Soft delete неактивной связи — status=2, не физическое удаление.", "Этот API вызывается только для предполагаемой неактивной связи. Active-ветвь главного handler закрывает Ticket без soft_delete.", "Активность повторно проверяется backend под блокировкой."], "sources": [{"commit": "76e0c693b628ed4a1fa34515a3f8abdff26149bb", "path": "handlers/open_neg_request_declined.py", "repository": "sharix-open-handlers-examples", "sha256": "6813d34df6519db2544c33c365daab408c50abf0c4d7a94d5b4f5ba1289d0355"}, {"commit": "76e0c693b628ed4a1fa34515a3f8abdff26149bb", "path": "generated_handler_blocks/open_neg_request_declined.json", "repository": "sharix-open-handlers-examples", "sha256": "93d5a75a98975fccf560e9a03f2600c166d9eb9acf90dab0256e02a1b9068f95"}, {"commit": "76e0c693b628ed4a1fa34515a3f8abdff26149bb", "path": "catalog.py", "repository": "sharix-open-handlers-examples", "sha256": "e3dcb72b7e620e2837adacd7af053f35b04cdff475789496d9c114e17d57dfde"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "core/blockly_runtime.py", "repository": "sharix-open-handlers-backend", "sha256": "3e222150cbff315077c6c9b160d46e59856cedf6d6591d213e958c63cb2f2428"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "core/handler_operations.py", "repository": "sharix-open-handlers-backend", "sha256": "28620658501c375f556e24437871620b84a17a88d0b62b2d1a3cc5deb2f14b1e"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "core/ejabberd_muc.py", "repository": "sharix-open-handlers-backend", "sha256": "35c2eba89fab36b5c444439a87beaa516b55133991136d5370418d92a18d7bcd"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "core/requesters.py", "repository": "sharix-open-handlers-backend", "sha256": "965a348cea477722bbaf4c8c6b06023eb238b71664a332f7ea8c90bfe72ea808"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "ticket_blockly_codegen.py", "repository": "sharix-open-handlers-backend", "sha256": "bce71eab622922f8dd0954222842349799b60e18a0ab6310cc3e5f305603661e"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "ticket_codegen_contract.json", "repository": "sharix-open-handlers-backend", "sha256": "0bfd2b0ebb07bd380ed3c867386536ab88ef7dfa6e13f1cd8c91d32bb02c1ae9"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "lib/tickets.py", "repository": "sharix-open-tickets", "sha256": "0d260c17947f9a186d07af2749159780e3c7354a95b4bafe9d67493663c96883"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "api/views/handler_workflow.py", "repository": "sharix-open-tickets", "sha256": "f97c4b467a9a30749f5c4b86b39a833c430e57aaa1f030f70030d9b5c609d368"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "api/handler_auth.py", "repository": "sharix-open-tickets", "sha256": "ac76dc45aaa9742e5e1da0b98aae914bb2e287048d33e97b4a245a6997bc8425"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "utils.py", "repository": "sharix-open-tickets", "sha256": "a5680205c6522e1d9a74d9d267fe9ee96faf6f06436b00877fc0a93d80234bf5"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "views/ticket_detail.py", "repository": "sharix-open-tickets", "sha256": "05d344b0fba14cf895400694d4a18f486c800fef08dd1acb43ea46909b9e7e59"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "signals.py", "repository": "sharix-open-tickets", "sha256": "39aad8690a2e7d35cebfb052127a8130a951cbb4eea316c4f767a0a59fc5fa7e"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "services/handler_wakeup.py", "repository": "sharix-open-tickets", "sha256": "c9fdc62a18301fd7c075395c697d5b1c738c77713aa71fcbb5ab097e173d98de"}, {"commit": "778a3280cee115ba0efe3fdff7b0e708b0c10093", "path": "apiviews/handler_workflow.py", "repository": "sharix-open-backend", "sha256": "0f699a4e16760cbbdd09d894093c685ba69073fedf0522852cb1fdc9811636e3"}, {"commit": "778a3280cee115ba0efe3fdff7b0e708b0c10093", "path": "lib/dicts.py", "repository": "sharix-open-backend", "sha256": "d92ddaa0964324e337303f8de110acf6ff21e15d7b40a6e523ce78ed48b7d8c5"}, {"commit": "778a3280cee115ba0efe3fdff7b0e708b0c10093", "path": "models/relationship.py", "repository": "sharix-open-backend", "sha256": "873f27666a7d03b8058958b752d0e69d17d3433daf72f901fa88275cf10f43aa"}]}</bpmn:documentation>
    <bpmn:laneSet id="relationship_Lanes_relationship">
      <bpmn:lane id="relationship_Lane_worker" name="Транзакция изменения связанного объекта">
        <bpmn:flowNodeRef>relationship_start</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>relationship_lock</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>relationship_find</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>relationship_active</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>relationship_save</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>relationship_ok</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>relationship_end</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>relationship_reject</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>relationship_missing</bpmn:flowNodeRef>
      </bpmn:lane>
    </bpmn:laneSet>
    <bpmn:startEvent id="relationship_start" name="Запрос soft_delete">
      <bpmn:outgoing>relationship_Flow_0</bpmn:outgoing>
      <bpmn:messageEventDefinition />
    </bpmn:startEvent>
    <bpmn:serviceTask id="relationship_lock" name="Identity/capability/scope; блокировки">
      <bpmn:incoming>relationship_Flow_0</bpmn:incoming>
      <bpmn:outgoing>relationship_Flow_1</bpmn:outgoing>
    </bpmn:serviceTask>
    <bpmn:serviceTask id="relationship_find" name="Разрешить единственную relationship по Ticket">
      <bpmn:incoming>relationship_Flow_1</bpmn:incoming>
      <bpmn:outgoing>relationship_Flow_2</bpmn:outgoing>
    </bpmn:serviceTask>
    <bpmn:exclusiveGateway id="relationship_active" name="Сохранённый status=0?">
      <bpmn:incoming>relationship_Flow_2</bpmn:incoming>
      <bpmn:outgoing>relationship_Flow_3</bpmn:outgoing>
      <bpmn:outgoing>relationship_Flow_4</bpmn:outgoing>
    </bpmn:exclusiveGateway>
    <bpmn:serviceTask id="relationship_save" name="Сохранить status=2 и Event">
      <bpmn:incoming>relationship_Flow_3</bpmn:incoming>
      <bpmn:outgoing>relationship_Flow_5</bpmn:outgoing>
    </bpmn:serviceTask>
    <bpmn:sendTask id="relationship_ok" name="Успешный ответ">
      <bpmn:incoming>relationship_Flow_5</bpmn:incoming>
      <bpmn:outgoing>relationship_Flow_6</bpmn:outgoing>
    </bpmn:sendTask>
    <bpmn:endEvent id="relationship_end" name="Soft delete зафиксирован">
      <bpmn:incoming>relationship_Flow_6</bpmn:incoming>
    </bpmn:endEvent>
    <bpmn:endEvent id="relationship_reject" name="Отказ: active не удаляется">
      <bpmn:incoming>relationship_Flow_4</bpmn:incoming>
    </bpmn:endEvent>
    <bpmn:boundaryEvent id="relationship_missing" name="404 / 409" attachedToRef="relationship_find" cancelActivity="true">
      <bpmn:errorEventDefinition />
    </bpmn:boundaryEvent>
    <bpmn:sequenceFlow id="relationship_Flow_0" sourceRef="relationship_start" targetRef="relationship_lock" name="" />
    <bpmn:sequenceFlow id="relationship_Flow_1" sourceRef="relationship_lock" targetRef="relationship_find" name="" />
    <bpmn:sequenceFlow id="relationship_Flow_2" sourceRef="relationship_find" targetRef="relationship_active" name="" />
    <bpmn:sequenceFlow id="relationship_Flow_3" sourceRef="relationship_active" targetRef="relationship_save" name="Нет" />
    <bpmn:sequenceFlow id="relationship_Flow_4" sourceRef="relationship_active" targetRef="relationship_reject" name="Да" />
    <bpmn:sequenceFlow id="relationship_Flow_5" sourceRef="relationship_save" targetRef="relationship_ok" name="" />
    <bpmn:sequenceFlow id="relationship_Flow_6" sourceRef="relationship_ok" targetRef="relationship_end" name="" />
  </bpmn:process>
  <bpmn:process id="ticket_Process_ticket" name="Backend Ticket API: отдельный запрос CLOSED после подтверждения очистки" isExecutable="false">
    <bpmn:documentation>{"notes": ["Git built-in, BPMN isExecutable=false; код handler не выполняется при генерации.", "Сначала grace по Ticket.updated_at, затем проверка Relationship.", "Active Relationship сохраняется: после срока закрывается только Ticket.", "Soft delete неактивной связи — status=2, не физическое удаление.", "Этот API вызывается только для предполагаемой неактивной связи. Active-ветвь главного handler закрывает Ticket без soft_delete.", "Активность повторно проверяется backend под блокировкой."], "sources": [{"commit": "76e0c693b628ed4a1fa34515a3f8abdff26149bb", "path": "handlers/open_neg_request_declined.py", "repository": "sharix-open-handlers-examples", "sha256": "6813d34df6519db2544c33c365daab408c50abf0c4d7a94d5b4f5ba1289d0355"}, {"commit": "76e0c693b628ed4a1fa34515a3f8abdff26149bb", "path": "generated_handler_blocks/open_neg_request_declined.json", "repository": "sharix-open-handlers-examples", "sha256": "93d5a75a98975fccf560e9a03f2600c166d9eb9acf90dab0256e02a1b9068f95"}, {"commit": "76e0c693b628ed4a1fa34515a3f8abdff26149bb", "path": "catalog.py", "repository": "sharix-open-handlers-examples", "sha256": "e3dcb72b7e620e2837adacd7af053f35b04cdff475789496d9c114e17d57dfde"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "core/blockly_runtime.py", "repository": "sharix-open-handlers-backend", "sha256": "3e222150cbff315077c6c9b160d46e59856cedf6d6591d213e958c63cb2f2428"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "core/handler_operations.py", "repository": "sharix-open-handlers-backend", "sha256": "28620658501c375f556e24437871620b84a17a88d0b62b2d1a3cc5deb2f14b1e"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "core/ejabberd_muc.py", "repository": "sharix-open-handlers-backend", "sha256": "35c2eba89fab36b5c444439a87beaa516b55133991136d5370418d92a18d7bcd"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "core/requesters.py", "repository": "sharix-open-handlers-backend", "sha256": "965a348cea477722bbaf4c8c6b06023eb238b71664a332f7ea8c90bfe72ea808"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "ticket_blockly_codegen.py", "repository": "sharix-open-handlers-backend", "sha256": "bce71eab622922f8dd0954222842349799b60e18a0ab6310cc3e5f305603661e"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "ticket_codegen_contract.json", "repository": "sharix-open-handlers-backend", "sha256": "0bfd2b0ebb07bd380ed3c867386536ab88ef7dfa6e13f1cd8c91d32bb02c1ae9"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "lib/tickets.py", "repository": "sharix-open-tickets", "sha256": "0d260c17947f9a186d07af2749159780e3c7354a95b4bafe9d67493663c96883"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "api/views/handler_workflow.py", "repository": "sharix-open-tickets", "sha256": "f97c4b467a9a30749f5c4b86b39a833c430e57aaa1f030f70030d9b5c609d368"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "api/handler_auth.py", "repository": "sharix-open-tickets", "sha256": "ac76dc45aaa9742e5e1da0b98aae914bb2e287048d33e97b4a245a6997bc8425"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "utils.py", "repository": "sharix-open-tickets", "sha256": "a5680205c6522e1d9a74d9d267fe9ee96faf6f06436b00877fc0a93d80234bf5"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "views/ticket_detail.py", "repository": "sharix-open-tickets", "sha256": "05d344b0fba14cf895400694d4a18f486c800fef08dd1acb43ea46909b9e7e59"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "signals.py", "repository": "sharix-open-tickets", "sha256": "39aad8690a2e7d35cebfb052127a8130a951cbb4eea316c4f767a0a59fc5fa7e"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "services/handler_wakeup.py", "repository": "sharix-open-tickets", "sha256": "c9fdc62a18301fd7c075395c697d5b1c738c77713aa71fcbb5ab097e173d98de"}, {"commit": "778a3280cee115ba0efe3fdff7b0e708b0c10093", "path": "apiviews/handler_workflow.py", "repository": "sharix-open-backend", "sha256": "0f699a4e16760cbbdd09d894093c685ba69073fedf0522852cb1fdc9811636e3"}, {"commit": "778a3280cee115ba0efe3fdff7b0e708b0c10093", "path": "lib/dicts.py", "repository": "sharix-open-backend", "sha256": "d92ddaa0964324e337303f8de110acf6ff21e15d7b40a6e523ce78ed48b7d8c5"}, {"commit": "778a3280cee115ba0efe3fdff7b0e708b0c10093", "path": "models/relationship.py", "repository": "sharix-open-backend", "sha256": "873f27666a7d03b8058958b752d0e69d17d3433daf72f901fa88275cf10f43aa"}]}</bpmn:documentation>
    <bpmn:laneSet id="ticket_Lanes_ticket">
      <bpmn:lane id="ticket_Lane_worker" name="Отдельная транзакция">
        <bpmn:flowNodeRef>ticket_start</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>ticket_scope</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>ticket_change</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>ticket_wake</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>ticket_done</bpmn:flowNodeRef>
      </bpmn:lane>
    </bpmn:laneSet>
    <bpmn:startEvent id="ticket_start" name="Handler запросил CLOSED">
      <bpmn:outgoing>ticket_Flow_0</bpmn:outgoing>
      <bpmn:messageEventDefinition />
    </bpmn:startEvent>
    <bpmn:serviceTask id="ticket_scope" name="Проверить capability и актуальный scope">
      <bpmn:incoming>ticket_Flow_0</bpmn:incoming>
      <bpmn:outgoing>ticket_Flow_1</bpmn:outgoing>
    </bpmn:serviceTask>
    <bpmn:serviceTask id="ticket_change" name="Проверить lifecycle; сохранить CLOSED и Event">
      <bpmn:incoming>ticket_Flow_1</bpmn:incoming>
      <bpmn:outgoing>ticket_Flow_2</bpmn:outgoing>
    </bpmn:serviceTask>
    <bpmn:sendTask id="ticket_wake" name="После commit: ticket.moved прежнему владельцу">
      <bpmn:incoming>ticket_Flow_2</bpmn:incoming>
      <bpmn:outgoing>ticket_Flow_3</bpmn:outgoing>
    </bpmn:sendTask>
    <bpmn:endEvent id="ticket_done" name="Ticket закрыт, очередь освобождается">
      <bpmn:incoming>ticket_Flow_3</bpmn:incoming>
    </bpmn:endEvent>
    <bpmn:sequenceFlow id="ticket_Flow_0" sourceRef="ticket_start" targetRef="ticket_scope" name="" />
    <bpmn:sequenceFlow id="ticket_Flow_1" sourceRef="ticket_scope" targetRef="ticket_change" name="" />
    <bpmn:sequenceFlow id="ticket_Flow_2" sourceRef="ticket_change" targetRef="ticket_wake" name="" />
    <bpmn:sequenceFlow id="ticket_Flow_3" sourceRef="ticket_wake" targetRef="ticket_done" name="" />
  </bpmn:process>
  <bpmn:collaboration id="Collaboration_neg_declined_cleanup">
    <bpmn:participant id="relationship_Pool_relationship" name="Backend relationship API: action=soft_delete" processRef="relationship_Process_relationship" />
    <bpmn:participant id="ticket_Pool_ticket" name="Backend Ticket API: отдельный запрос CLOSED после подтверждения очистки" processRef="ticket_Process_ticket" />
    <bpmn:messageFlow id="Message_neg_declined_cleanup_0" sourceRef="relationship_ok" targetRef="ticket_start" name="Ответ разрешает handler перейти к отдельному запросу CLOSED" />
  </bpmn:collaboration>
  <bpmndi:BPMNDiagram id="Diagram_neg_declined_cleanup">
    <bpmndi:BPMNPlane id="Plane_neg_declined_cleanup" bpmnElement="Collaboration_neg_declined_cleanup">
      <bpmndi:BPMNShape id="relationship_DI_Pool_relationship" bpmnElement="relationship_Pool_relationship" isHorizontal="true">
        <dc:Bounds x="0.0" y="0.0" width="1940" height="410" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="relationship_DI_Lane_worker" bpmnElement="relationship_Lane_worker" isHorizontal="true">
        <dc:Bounds x="30.0" y="0.0" width="1910" height="410" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="relationship_DI_start" bpmnElement="relationship_start">
        <dc:Bounds x="132.0" y="92.0" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="relationship_DI_lock" bpmnElement="relationship_lock">
        <dc:Bounds x="345.0" y="75.0" width="150" height="70" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="relationship_DI_find" bpmnElement="relationship_find">
        <dc:Bounds x="625.0" y="75.0" width="150" height="70" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="relationship_DI_active" bpmnElement="relationship_active">
        <dc:Bounds x="955.0" y="85.0" width="50" height="50" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="relationship_DI_save" bpmnElement="relationship_save">
        <dc:Bounds x="1185.0" y="75.0" width="150" height="70" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="relationship_DI_ok" bpmnElement="relationship_ok">
        <dc:Bounds x="1455.0" y="75.0" width="150" height="70" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="relationship_DI_end" bpmnElement="relationship_end">
        <dc:Bounds x="1762.0" y="92.0" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="relationship_DI_reject" bpmnElement="relationship_reject">
        <dc:Bounds x="962.0" y="292.0" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="relationship_DI_missing" bpmnElement="relationship_missing">
        <dc:Bounds x="732.0" y="127.0" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="relationship_DI_Flow_0" bpmnElement="relationship_Flow_0">
        <di:waypoint x="168.0" y="110.0" />
        <di:waypoint x="345.0" y="110.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="relationship_DI_Flow_1" bpmnElement="relationship_Flow_1">
        <di:waypoint x="495.0" y="110.0" />
        <di:waypoint x="625.0" y="110.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="relationship_DI_Flow_2" bpmnElement="relationship_Flow_2">
        <di:waypoint x="775.0" y="110.0" />
        <di:waypoint x="955.0" y="110.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="relationship_DI_Flow_3" bpmnElement="relationship_Flow_3">
        <di:waypoint x="1005.0" y="110.0" />
        <di:waypoint x="1185.0" y="110.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="relationship_DI_Flow_4" bpmnElement="relationship_Flow_4">
        <di:waypoint x="980.0" y="135.0" />
        <di:waypoint x="980.0" y="292.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="relationship_DI_Flow_5" bpmnElement="relationship_Flow_5">
        <di:waypoint x="1335.0" y="110.0" />
        <di:waypoint x="1455.0" y="110.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="relationship_DI_Flow_6" bpmnElement="relationship_Flow_6">
        <di:waypoint x="1605.0" y="110.0" />
        <di:waypoint x="1762.0" y="110.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="ticket_DI_Pool_ticket" bpmnElement="ticket_Pool_ticket" isHorizontal="true">
        <dc:Bounds x="0.0" y="590.0" width="1420" height="210" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="ticket_DI_Lane_worker" bpmnElement="ticket_Lane_worker" isHorizontal="true">
        <dc:Bounds x="30.0" y="590.0" width="1390" height="210" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="ticket_DI_start" bpmnElement="ticket_start">
        <dc:Bounds x="132.0" y="682.0" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="ticket_DI_scope" bpmnElement="ticket_scope">
        <dc:Bounds x="345.0" y="665.0" width="150" height="70" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="ticket_DI_change" bpmnElement="ticket_change">
        <dc:Bounds x="625.0" y="665.0" width="150" height="70" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="ticket_DI_wake" bpmnElement="ticket_wake">
        <dc:Bounds x="905.0" y="665.0" width="150" height="70" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="ticket_DI_done" bpmnElement="ticket_done">
        <dc:Bounds x="1242.0" y="682.0" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="ticket_DI_Flow_0" bpmnElement="ticket_Flow_0">
        <di:waypoint x="168.0" y="700.0" />
        <di:waypoint x="345.0" y="700.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="ticket_DI_Flow_1" bpmnElement="ticket_Flow_1">
        <di:waypoint x="495.0" y="700.0" />
        <di:waypoint x="625.0" y="700.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="ticket_DI_Flow_2" bpmnElement="ticket_Flow_2">
        <di:waypoint x="775.0" y="700.0" />
        <di:waypoint x="905.0" y="700.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="ticket_DI_Flow_3" bpmnElement="ticket_Flow_3">
        <di:waypoint x="1055.0" y="700.0" />
        <di:waypoint x="1242.0" y="700.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="DI_Message_neg_declined_cleanup_0" bpmnElement="Message_neg_declined_cleanup_0">
        <di:waypoint x="1530" y="145" />
        <di:waypoint x="1530" y="500" />
        <di:waypoint x="150" y="500" />
        <di:waypoint x="150" y="682" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="500" y="465" width="260" height="42" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</bpmn:definitions>

Эта схема раскрывает только ветвь предполагаемой неактивной связи. Active-ветвь основного алгоритма пропускает soft_delete и закрывает Ticket напрямую.

Повторное чтение перед soft delete:

  • Нет связи: no-op; затем handler пробует CLOSED.
  • Уже active: исключение, старое решение о неактивности не разрешает удаление.
  • Неактивная связь существует: выполнить scoped soft_delete; backend повторяет защиту под lock.

Для существующей неактивной связи POST dbsynce/api/handler/ticket/{ticket_id}/relationship/ с action=soft_delete требует related.relationship.status, текущий Ticket scope и единственную связь. Backend блокирует Ticket/Relationship и ещё раз отвергает удаление status=0. Успех сохраняет status=2 и Event. Это soft delete, а не физическое удаление строки.

Если связь стала active между проверками, запрос может завершиться ошибкой и общим retry. На следующем актуальном проходе главная active-ветвь сохранит связь и закроет только Ticket при соблюдении срока. Нельзя обходить backend guard ради завершения soft delete.

Soft delete и CLOSED подтверждать отдельно:

  • первый запрос меняет статус Relationship и создаёт Event;
  • второй запрос меняет статус Ticket и создаёт свой Event;
  • между запросами может возникнуть ошибка;
  • отсутствующая связь позволяет попытку CLOSED без успешного POST soft_delete.

Общего commit этих двух запросов нет. После сохранённого status=2 Ticket ещё может оставаться DECLINED. Стрелка между API-пулами описывает последовательность handler, а не прямой вызов одного endpoint другим.

Внешние действия и события

Текущий lifecycle для NEG_REQUEST DECLINED (459) допускает один исход:

  • CLOSED (451): закрыть сопровождающий Ticket при разрешённом доступе.

Прямого возврата DECLINED → ACCEPTED в коде нет; прежнее упоминание исправлено. Доступ к карточке и право смены статуса проверяются отдельно. Само изменение Ticket не активирует Relationship.

Канонический backend публикует события после commit. ticket.moved исключает прежний локальный снимок, при занятом worker — через pending discard; ticket.changed будит владельца новой стадии, если он существует. Для CLOSED отдельного NEG handler нет. Потерю доставки восстанавливает scoped periodic reconciliation по INTERVAL_TIME_BD_REQ. Ошибка списка не является подтверждением пустой очереди.

Отдельного ALERT о сохранении active Relationship в этой встроенной ветви нет. Для диагностики смотрят журнал и сохранённые состояния; участие в MUC-комнате не является подтверждением рассмотрения или командой изменить связь.

Ошибки и диагностика

Ситуация Результат
Grace ещё не истёк declined_relationship_grace, короткий defer до ближайшей проверки
Active после grace Сохранить Relationship, запись журнала, закрыть только Ticket
Неактивная связь после grace Soft delete status=2, затем отдельный CLOSED
Связь не найдена при повторном чтении No-op удаления, затем попытка CLOSED
Ошибка read/soft-delete/перехода processing_error, DEFERRED_TICKET_RECHECK_SECONDS
404 при переходе CLOSED Запрос локальной очистки как при устаревшем scope

Read по связи также может вернуть 404 при выходе Ticket из scope; несколько записей дают 409, а не произвольный выбор. Постоянное расхождение требует разбора данных/identity, а не только ожидания таймера.

Порядок диагностики:

  1. Прочитать отдельно Ticket.status и Relationship.status.
  2. Сверить текущие ревизию и updated_at, вычислить deadline.
  3. Для неактивной связи проверить Event soft delete.
  4. Отдельно проверить Event CLOSED.
  5. При неопределённом ответе перечитать сохранённые состояния до повтора.

Active Relationship вместе с CLOSED Ticket может быть штатным результатом. Это не доказательство удаления или деактивации связи.

Проверенные источники

Git SHA/SHA-256 и параметры моделей закреплены в metadata и documentation BPMN. Код handler при анализе не исполнялся; изменение источников требует повторного сопоставления.

Связанные материалы

sharixopen/knowledge/scenarios/neg_request_declined.txt · Последнее изменение: — sx_knowledge_api_open

© 2022 ShariX