
# Планування

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()` для стандартних 5-польових виразів cron:

```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' );
```

Щоразу, коли спрацьовує розклад, надсилається нове завдання з цим корисним навантаженням.
