> ## Documentation Index
> Fetch the complete documentation index at: https://docs.yume.cloud/llms.txt
> Use this file to discover all available pages before exploring further.

# Фоновые процессы

> Каталог фоновых задач CRM: пересчёт аренд, штрафы, распределение оплат по расписанию, уведомления и их периодичность.

CRM полагается на очередь фоновых задач для двух типов работы: **периодические задачи** (запускаются по расписанию)
и **пересчёты по требованию** (запускаются при изменении аренды, продажи или оплаты). Часть работы выполняется
в фоне, часть — сразу, прямо во время вашего действия. Ниже разбираемся, что и когда
исполняется.

<Warning>
  Важная особенность: **часть тяжёлых пересчётов — это не фоновые задачи, а обычный синхронный код**. Центральный
  пересчёт аренды, пересчёт оплаты аренды, пересчёт продажи, посев штрафов, построение расписания платежей и пересчёт
  занятости инвентаря вызываются **синхронно** внутри обработки запроса и увеличивают время ответа. «Задачами» в
  терминах очереди являются только те пункты, что отмечены как асинхронные (см. таблицу ниже).

  Это осознанный остаток, а не недоделка:

  * **центральный пересчёт аренды возвращает те самые суммы, которые уходят в ответ.** Если вынести его в фон, клиент
    увидит устаревшие цены и долг, пока фоновая обработка не догонит. Это уже смена контракта денежного API, а не
    оптимизация;
  * **пересчёт занятости инвентаря управляет бронированием.** Устаревшая доступность грозит двойной бронью, поэтому он
    остаётся немедленным. Известный запас: за одно изменение аренды он выполняется два-три раза, хотя достаточно одного.

  А вот побочные эффекты смены статуса аренды — уведомления и лиды воронки — из обработки запроса **вынесены**: они
  выполняются фоновой задачей после того, как изменение зафиксировано в базе.
</Warning>

## Задачи и компании

Каждая фоновая задача выполняется в данных той компании, из которой она была поставлена — система запоминает компанию
в момент постановки и восстанавливает её перед выполнением, без ручной настройки.

Отдельно устроены **кросс-компанийные** задачи: они не привязаны к одной компании, а сами проходят по всем компаниям
по очереди. Это начисление автоштрафов, автосписание, массовый пересчёт прибыли, а также обслуживание компаний и
биллинг. Для них работа «вне компании» — штатный режим, и они объявлены такими явно.

<Warning>
  **Задача, которой компания обязательна, при потере контекста компании завершается ошибкой.** Раньше такая задача молча
  выполнялась «вне компании» — и не падала, потому что в общей области базы лежат таблицы с теми же названиями: чтения и
  записи попадали в них, ошибки не возникало никогда, и утечка оставалась незаметной. Теперь требование объявляется на
  уровне самой задачи: если оно объявлено, а компания не определилась, задача падает с понятной ошибкой и видна в
  мониторинге.

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

  Пока строгий режим выключен и требование не объявлено, задача без контекста компании по-прежнему выполняется, но
  оставляет предупреждение в журнале.
</Warning>

## Расписание периодических задач

Расписание хранится в базе данных и редактируется в том числе из админки. Планировщик берёт задания
из базы. Из статического конфига к CRM относятся две задачи (остальные — интеграции и ассистент):

| Задача                               | Периодичность   | Компания                           |
| ------------------------------------ | --------------- | ---------------------------------- |
| Начисление автоштрафов               | Каждую минуту   | по всем компаниям (перебор внутри) |
| Автосписание по расписанию           | Ежедневно 05:00 | по всем компаниям (перебор внутри) |
| Синхронизация трекеров               | Каждые 5 минут  | не CRM                             |
| Удаление дубликатов справочника авто | Ежедневно 00:00 | не CRM                             |
| Уборка зависших запусков ассистента  | Каждую минуту   | не CRM                             |

<Info>
  Помимо статического конфига, во время работы приложения в базе динамически создаются записи расписания:
  одноразовые/периодические уведомления о ТО, одноразовые алерты «просрочка близко» и записи для начисления
  автоштрафов (по ним итерирует задача начисления автоштрафов). Они не видны в статическом конфиге, но исполняются тем
  же планировщиком.
</Info>

## Каталог фоновых процессов CRM

Легенда столбца «Тип»: **периодическая** — запускается по расписанию; **задача** — асинхронная задача из очереди;
**функция** — обычный синхронный код (вызывается прямо в запросе, несмотря на расположение среди задач).

| Процесс                        | Тип           | Триггер                                                              | Что делает                                                                                                                                 | Связано с                                                                                       |
| ------------------------------ | ------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------- |
| Начисление автоштрафов         | периодическая | Каждую минуту                                                        | Находит просроченные аренды и начисляет автоматический штраф + шлёт уведомление                                                            | [Депозиты и штрафы](/ru/logic/rent/deposits-penalties)                                          |
| Посев штрафов и алертов        | функция       | При оформлении аренды (обычной или Yume Plus)                        | Создаёт/обновляет записи для начисления штрафов и записи расписания для алертов о просрочке                                                | [Аренда: жизненный цикл](/ru/logic/rent/lifecycle)                                              |
| Центральный пересчёт аренды    | функция       | Почти любое изменение аренды                                         | Главный пересчёт аренды: цены, скидки, штрафы, расписание, налог; ставит задачу пересчёта прибыли                                          | [Тарифы](/ru/logic/rent/pricing), [Скидки](/ru/logic/rent/pricing)                              |
| Построение расписания платежей | функция       | Внутри центрального пересчёта аренды                                 | Строит дневное расписание платежей, распределяет оплаты                                                                                    | [Депозиты и штрафы](/ru/logic/rent/deposits-penalties)                                          |
| Пересчёт оплаты аренды         | функция       | При изменении оплаты                                                 | Пересчитывает поле «Оплачено» аренды и ставит задачу пересчёта прибыли                                                                     | [Прибыль](/ru/logic/profit-payback)                                                             |
| Синхронизация автоштрафов      | задача        | Внутри пересчёта прибыли (когда нужно)                               | Синхронизирует автоматические штрафы с рассчитанными суммами по инвентарю                                                                  | [Депозиты и штрафы](/ru/logic/rent/deposits-penalties)                                          |
| Пересчёт прибыли аренды        | задача        | Асинхронно из центрального пересчёта аренды / пересчёта оплаты       | Обновляет автоматические штрафы и пересчитывает прибыль по аренде                                                                          | [Прибыль](/ru/logic/profit-payback), [Метрики](/ru/logic/metrics)                               |
| Пересчёт продажи               | функция       | При изменении продажи или её оплаты                                  | Пересчёт продажи: цены, статус, склад; ставит задачу пересчёта прибыли по продаже                                                          | [Прибыль](/ru/logic/profit-payback)                                                             |
| Пересчёт прибыли продажи       | задача        | Асинхронно из пересчёта продажи                                      | Пересчитывает прибыль по продаже                                                                                                           | [Прибыль](/ru/logic/profit-payback)                                                             |
| Массовый пересчёт прибыли      | задача        | По требованию (без расписания)                                       | Массово пересчитывает прибыль по всем или одной компании, где плановый заработок разошёлся с ценой                                         | [Прибыль](/ru/logic/profit-payback)                                                             |
| Наполнение аренды (обычной)    | функция       | Оформление аренды из корзины                                         | Создаёт аренду, подбирает свободный инвентарь, генерирует комментарии                                                                      | [Аренда: жизненный цикл](/ru/logic/rent/lifecycle)                                              |
| Наполнение аренды (Yume Plus)  | задача        | Асинхронно из счёта Yume Plus (и синхронно из заказа)                | Наполняет аренду инвентарём и комплектами по корзине Yume Plus                                                                             | [Товары](/ru/logic/inventory/inventory-groups), [Комплекты](/ru/logic/inventory/inventory-sets) |
| Пересчёт занятости инвентаря   | функция       | При каждом сохранении аренды                                         | Пересчитывает занятость инвентаря; остаётся немедленным, потому что от него зависит бронирование                                           | [Инвентарь](/ru/logic/inventory)                                                                |
| Триггеры по статусу аренды     | задача        | Асинхронно при смене статуса аренды, после фиксации изменений в базе | Отправляет уведомления по переходу статуса и создаёт лиды воронки                                                                          | [Аренда: жизненный цикл](/ru/logic/rent/lifecycle)                                              |
| Автосписание по расписанию     | периодическая | Ежедневно 05:00                                                      | По компаниям с включённым расписанием транзакций и балансом клиента запускает центральный пересчёт активных аренд и сверяет балансы счетов | [Финансы и оплаты](/ru/logic/finances)                                                          |
| Алерт «скоро просрочка»        | задача        | Из одноразовой записи расписания (близко к просрочке)                | Готовит контекст и отправляет триггер о приближении просрочки                                                                              | [Аренда: жизненный цикл](/ru/logic/rent/lifecycle)                                              |
| Уведомление о ТО               | задача        | Расписание, одноразовая запись или сигнал пробега                    | Пуш + WhatsApp о необходимости ТО инвентаря                                                                                                | [Инвентарь](/ru/logic/inventory)                                                                |
| Отчёт по закрытию смены        | задача        | Асинхронно при закрытии смены                                        | Собирает статистику смены и шлёт отчёт в Telegram кассиру                                                                                  | [Метрики](/ru/logic/metrics)                                                                    |

## Как это работает

### Центральный пересчёт аренды

Это самая тяжёлая функция модуля: она заново считает **всю финансовую картину аренды** одним проходом по инвентарю и
услугам и сохраняет аренду. Вызывается синхронно из десятка мест (изменение аренды, статуса, инвентаря, скидок,
залога, доставки, бонусов, а также из начисления штрафа).

Основные шаги:

<Steps>
  <Step title="Ранний выход по удалённой аренде">
    Если аренда помечена как удалённая — снимаются запланированные списания, и функция завершается.
  </Step>

  <Step title="Расписание и режим подсчёта">
    Если у компании включена функция расписания транзакций, строится список дат аренды, оставшихся дат и оставшейся
    длительности, и подсчёт ведётся **по дням**; иначе — **по длительности**.
  </Step>

  <Step title="Расчёт по каждой позиции: цена, штраф, скидка">
    Для каждой строки инвентаря считаются стоимость позиции, сумма автоматического штрафа и скидки. Формула штрафа
    зависит от настройки округления периода просрочки (вверх/вниз), шага начисления штрафа, буфера просрочки и шага
    округления суммы штрафа — см. ниже.
  </Step>

  <Step title="Агрегация и запись позиций">
    Суммы сворачиваются в «Сумму за инвентарь», «Инвентарь со скидкой», сумму штрафов и т.д., затем строки инвентаря и
    услуг обновляются пакетно, если значения изменились.
  </Step>

  <Step title="Итоговые поля аренды и налог">
    Считаются «Сумма без скидок», «Итоговая сумма к оплате», «Сумма налога», «Сумма штрафов» (= автоматический штраф +
    ручной штраф), «Размер скидки»; по периоду просрочки статус переводится между «В аренде» и «Просрочена», и аренда сохраняется.
  </Step>

  <Step title="Скидки, склад, расписание, прибыль">
    Пересчитываются применённые скидки и занятость склада; при включённой функции строится расписание платежей; и, если
    плановый заработок разошёлся с фактической ценой, ставится асинхронная задача пересчёта прибыли по аренде.
  </Step>
</Steps>

Ключевые формулы:

**Стоимость одной позиции инвентаря (аренда):**

Число тарифных периодов округляется **вверх**, затем умножается на цену за период:

```
Стоимость позиции = Цена за период × округление_вверх(Длительность ÷ длительность тарифного периода)
```

**Автоматический штраф за просрочку:**

```
Период просрочки = (Фактический возврат или «сейчас») − Плановое окончание − время паузы

Если Плановое окончание > «сейчас» − буфер просрочки → штраф = 0 (ещё не просрочено)

Число шагов просрочки = округление((Период просрочки − буфер просрочки) ÷ Шаг начисления штрафа)
    (округление вверх, если период просрочки округляется вверх; иначе вниз)

Штраф за позицию = Цена за период × Число шагов просрочки ÷ округление_вверх(длительность тарифного периода ÷ Шаг начисления штрафа)
    (значение не меньше нуля, затем приводится к шагу округления суммы штрафа)
```

**Итоговая цена аренды с налогом:**

```
Сумма налога = округление((Итоговая сумма к оплате + Сумма штрафов) × Ставка налога)

Если налог включён в цену:
    Итоговая сумма к оплате = округление(Итоговая сумма к оплате + Сумма штрафов + Стоимость доставки)
Иначе:
    Итоговая сумма к оплате = округление((Итоговая сумма к оплате + Сумма штрафов) × (1 + Ставка налога) + Стоимость доставки)
```

<Note>
  Центральный пересчёт аренды поддерживает **автопродление** через ограниченный цикл повторов: если срок аренды истёк
  («сейчас» позже планового окончания) и аренда в статусе «В аренде» или «Просрочена», плановое окончание сдвигается на новый
  период, а пересчёт запускается заново. Число повторов жёстко ограничено (до 15 проходов); если за это число проходов
  расчёт не сошёлся (например, при некорректных данных, когда длительность автопродления равна нулю и окончание не
  продвигается), в журнал пишется предупреждение и процедура завершается с последним результатом — бесконечный цикл
  исключён.
</Note>

### Начисление автоштрафов по расписанию

Запускается **каждую минуту**. Работает партиями до 100 записей за раз, отсортированных по времени последнего запуска
(сначала те, что ещё ни разу не запускались).

<Steps>
  <Step title="Отбор просроченных">
    Выбираются записи штрафов, где подключена интеграция аренды, начисление включено, наступил срок, и с момента прошлого
    запуска прошло не меньше заданного интервала.
  </Step>

  <Step title="Группировка по компаниям">
    Записи группируются по компаниям — задача обрабатывает каждую компанию по очереди.
  </Step>

  <Step title="Начисление или очистка">
    Для каждой аренды: если она удалена или не в статусе «В аренде» — удаляются все её записи для начисления штрафов;
    иначе выполняется начисление штрафа, которое запускает центральный пересчёт аренды и, если аренда просрочена, ставит
    уведомление о просрочке.
  </Step>

  <Step title="Отметка о запуске">
    У обработанных записей проставляется время последнего запуска.
  </Step>
</Steps>

<Note>
  Посев штрафов — это «посевная» функция: при создании аренды она создаёт/обновляет записи для начисления штрафов (по
  одной на каждый уникальный срок окончания у инвентаря) и, отдельно, регистрирует одноразовые записи
  расписания для алерта «скоро просрочка», если у компании настроены триггеры перехода из статуса «В аренде» в состояние
  близкой просрочки с задержкой больше одной минуты.
</Note>

### Расписание платежей

Работает только при включённой функции расписания транзакций. Три уровня:

* **Построение расписания** — строит список дат аренды и вычисляет **выходные дни** по правилам цикла рабочих и
  выходных дней и по кастомным пометкам расписания. Первый день по умолчанию считается выходным (не оплачивается),
  если не включена функция платы за первый день.
* **Раскладка по дням** — раскладывает цену инвентаря и скидки **по дням** пропорционально (цена дня = стоимость
  позиции ÷ число дней аренды), распределяет оплату, добавляет штрафы к дню их создания и синхронизирует записи
  расписания (создание/обновление/удаление).
* **Разнос фактических оплат** — «жадно» разносит фактические оплаты по дням расписания с переносом переплаты на
  предыдущие дни, ограничивая каждый день его плановой суммой.

**Логика разноса переплаты по дням расписания:**

```
Повторять (не более чем число дней + 1 раз):
    переплата = 0
    Для каждого дня, начиная с последнего к первому:
        всего = оплачено в дне + переплата
        оплачено в дне = min(всего, плановая сумма дня)   // не больше плановой суммы дня
        переплата = всего − оплачено в дне
    Если переплата ≤ 0 — остановиться
    Иначе остаток переплаты добавить к последнему дню
```

### Прибыль: пересчёт по аренде, по продаже и массовый

**Пересчёт прибыли по аренде** — тонкая обёртка: при необходимости сперва синхронизирует автоматические штрафы под
пересчитанные суммы, затем пересчитывает прибыль по аренде с полным набором цен. **Пересчёт прибыли по продаже** просто
пересчитывает прибыль по конкретной продаже.

**Массовый пересчёт прибыли** — ремонтная задача: перебирает все компании (кроме служебных) и пересчитывает прибыль по
продажам, задачам мастерской и арендам, где расхождение между плановым заработком и ценой больше единицы (для продаж и
аренд сравнивается с итоговой суммой к оплате, для задач мастерской — с ценой). Может фильтроваться по конкретной
компании или конкретной аренде.

Диаграмма зависимостей пересчёта аренды:

```mermaid theme={null}
flowchart TD
    A["Изменение аренды / оплаты<br/>(запрос, сигнал)"] --> B["Центральный пересчёт аренды<br/>(синхронно)"]
    B --> C["Пересчёт применённых скидок / занятости склада"]
    B --> D["Построение расписания платежей<br/>(если включено расписание транзакций)"]
    D --> D1["Разнос фактических оплат по дням"]
    B --> E{"Плановый заработок ≠ цена?"}
    E -->|да| F["Пересчёт прибыли аренды<br/>(асинхронная задача)"]
    F --> G["Синхронизация автоматических штрафов"]
    F --> H["Пересчёт прибыли по аренде"]
    I["Периодически: начисление автоштрафов<br/>(каждую минуту)"] --> J["Начисление штрафа по аренде"]
    J --> B
```

<Tip>
  При отладке «почему сумма аренды не пересчиталась» первым делом проверяйте, дошёл ли вызов до центрального пересчёта
  аренды (он синхронный) и поставилась ли из него задача пересчёта прибыли — постановка происходит только при
  расхождении планового заработка и фактической цены. Если фоновая обработка остановлена, немедленные пересчёты всё равно
  отработают, а асинхронная часть (прибыль, автоматические штрафы через очередь, уведомления и лиды по смене статуса) — нет.
</Tip>
