
# Планирование

Queuety поддерживает повторяющиеся задания. Вместо использования `wp_cron` расписания хранятся в базе данных и примерно раз в минуту проверяются постоянно работающим процессом `wp queuety work` (или родительским процессом пула обработчиков). `wp queuety work --once`, `wp queuety flush` и встроенный резервный механизм WP-Cron плагина не проверяют расписания ([#14](https://github.com/inline0/queuety/issues/14)); без постоянно работающего обработчика запустите `wp queuety schedule run` самостоятельно.

## Планирование с интервалами

Используйте `every()` для простых расписаний на основе интервалов. Выражение может быть любой строкой, совместимой с PHP `DateTimeImmutable::modify()`:

```php
use Queuety\Queuety;

Queuety::schedule( 'cleanup_handler' )
    ->every( '1 hour' )
    ->on_queue( 'maintenance' );

Queuety::schedule( 'sync_inventory' )
    ->every( '30 minutes' )
    ->on_queue( 'sync' );

Queuety::schedule( 'daily_report', [ 'type' => 'summary' ] )
    ->every( '1 day' );
```

## Планирование с Cron

Используйте `cron()` для стандартных выражений cron из 5 полей:

```php
// Every day at 3am
Queuety::schedule( 'nightly_cleanup' )
    ->cron( '0 3 * * *' );

// Every Monday at 9am
Queuety::schedule( 'weekly_digest' )
    ->cron( '0 9 * * 1' )
    ->on_queue( 'emails' );

// Every 15 minutes
Queuety::schedule( 'health_check' )
    ->cron( '*/15 * * * *' );
```

## Параметры расписания

Конструктор `PendingSchedule` поддерживает следующие методы:

| Метод | Описание |
|---|---|
| `every( string $interval )` | Задать выражение интервала |
| `cron( string $expression )` | Задать выражение cron |
| `on_queue( string $queue )` | Задать целевую очередь (по умолчанию: `'default'`) |
| `overlap( OverlapPolicy $policy )` | Задать политику перекрытия (по умолчанию: `Allow`) |

Перед отправкой заданий по расписанию необходимо вызвать либо `every()`, либо `cron()`. Если не задан ни один из методов, будет вызвано исключение `RuntimeException`.

## Политики перекрытия

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

```php
use Queuety\Queuety;
use Queuety\Enums\OverlapPolicy;

Queuety::schedule( 'sync_inventory' )
    ->every( '5 minutes' )
    ->overlap( OverlapPolicy::Skip )
    ->on_queue( 'sync' );
```

Перечисление `OverlapPolicy` содержит три варианта:

| Политика | Значение | Поведение |
|---|---|---|
| `OverlapPolicy::Allow` | `allow` | Всегда отправлять новое задание, даже если предыдущий запуск еще выполняется. Это значение по умолчанию. |
| `OverlapPolicy::Skip` | `skip` | Пропускать текущее срабатывание, если у обработчика есть ожидающие или обрабатываемые задания. `next_run` обычно обновляется, поэтому расписание сохраняет заданный ритм. |
| `OverlapPolicy::Buffer` | `buffer` | Как и при Skip, задание не отправляется, а `next_run` переходит к следующему регулярному срабатыванию. В отличие от Skip, `last_run` не обновляется. |

### Разрешить (по умолчанию)

Одновременно могут выполняться несколько экземпляров одного обработчика. Используйте этот вариант для идемпотентных обработчиков, для которых перекрытие безопасно.

```php
Queuety::schedule( 'send_heartbeat_ping' )
    ->every( '1 minute' )
    ->overlap( OverlapPolicy::Allow );
```

### Пропустить

Срабатывание расписания молча пропускается, если обработчик еще работает. Используйте этот вариант, если запуск второго экземпляра будет неэффективен или вызовет конфликты.

```php
Queuety::schedule( 'rebuild_search_index' )
    ->every( '10 minutes' )
    ->overlap( OverlapPolicy::Skip );
```

### Буферизовать

Сейчас работает так же, как Skip: если у обработчика есть ожидающее или обрабатываемое задание, срабатывание не отправляется, а `next_run` переходит к следующему регулярному запуску. Единственное отличие — Buffer оставляет `last_run` без изменений, тогда как Skip записывает пропущенное срабатывание как время последнего запуска.

```php
Queuety::schedule( 'sync_inventory' )
    ->every( '5 minutes' )
    ->overlap( OverlapPolicy::Buffer );
```

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

При создании расписания Queuety сохраняет его в таблице `queuety_schedules` вместе с выражением, обработчиком, данными и временем следующего запуска.

При каждом срабатывании планировщика (примерно раз в минуту в постоянно работающем обработчике или при запуске `wp queuety schedule run`) планировщик проверяет расписания, для которых наступило время `next_run`. Для каждого такого расписания он отправляет новое задание и обновляет `next_run` до следующего запуска.

## Управление расписаниями через CLI

```bash
# List all schedules
wp queuety schedule list

# Add a recurring schedule
wp queuety schedule add cleanup_handler --every="1 hour"
wp queuety schedule add nightly_report --cron="0 3 * * *" --queue=reports

# Remove a schedule
wp queuety schedule remove cleanup_handler

# Manually trigger the scheduler
wp queuety schedule run
```

## Управление расписаниями через PHP

```php
// Get the scheduler instance
$scheduler = Queuety::scheduler();

// List all schedules
$schedules = $scheduler->list();

// Remove a schedule by handler name
$scheduler->remove( 'cleanup_handler' );

// Manually trigger a tick
$count = $scheduler->tick();
```

## Данные задания

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

```php
Queuety::schedule( 'generate_report', [ 'type' => 'daily', 'format' => 'pdf' ] )
    ->every( '1 day' )
    ->on_queue( 'reports' );
```

При каждом срабатывании расписания отправляется новое задание с этими данными.
