---
phase: 60-order-status-aged-event
plan: 01
type: execute
wave: 1
depends_on: ["59-01"]
files_modified:
  - src/Modules/Automation/AutomationController.php
  - src/Modules/Automation/AutomationService.php
  - src/Modules/Automation/AutomationRepository.php
  - src/Modules/Cron/CronHandlerFactory.php
  - src/Modules/Cron/OrderStatusAgedHandler.php
  - src/Modules/Automation/OrderStatusAgedService.php
  - database/migrations/20260331_000074_seed_order_status_aged_cron.sql
  - resources/views/automation/form.php
  - resources/views/automation/index.php
  - public/assets/js/modules/automation-form.js
autonomous: true
---

<objective>
## Goal
Nowe zdarzenie automatyzacji `order.status_aged` wyzwalane cyklicznie przez cron. Skanuje zamowienia, ktorych status nie zmienil sie od X dni i uruchamia pasujace reguly automatyzacji.

## Purpose
Uzytkownik moze np. ustawic regule: "Jesli zamowienie ma status 'wyslane' od 7 dni, zmien na 'zrealizowane'". Przydatne gdy brak trackingu przesylki i nie wiadomo czy dotarla.

## Output
- Event `order.status_aged` dostepny w formularzu automatyzacji
- Warunek `order_status` (reuse z Phase 59) + nowy warunek `days_in_status` (pole numeryczne)
- Cron handler skanujacy `order_status_history` i wyzwalajacy event
- Migracja seedujaca cron schedule
</objective>

<context>
## Project Context
@.paul/PROJECT.md
@.paul/ROADMAP.md
@.paul/STATE.md

## Prior Work
@.paul/phases/59-order-status-automation-event/59-01-SUMMARY.md — event order.status_changed + warunek order_status

## Source Files
@src/Modules/Automation/AutomationController.php
@src/Modules/Automation/AutomationService.php
@src/Modules/Automation/AutomationRepository.php
@src/Modules/Cron/CronHandlerFactory.php
@src/Modules/Orders/OrdersRepository.php (recordStatusChange, order_status_history)
</context>

<acceptance_criteria>

## AC-1: Event order.status_aged dostepny w formularzu
```gherkin
Given uzytkownik otwiera formularz nowego zadania automatycznego
When rozwija dropdown "Zdarzenie"
Then widzi opcje "Minelo X dni od zmiany statusu"
```

## AC-2: Warunek days_in_status z polem numerycznym
```gherkin
Given uzytkownik wybral zdarzenie "Minelo X dni od zmiany statusu"
When dodaje warunek "Liczba dni w statusie"
Then widzi pole numeryczne (min 1) do wpisania ilosci dni
```

## AC-3: Warunek order_status dziala z tym zdarzeniem
```gherkin
Given uzytkownik wybral zdarzenie "Minelo X dni od zmiany statusu"
When dodaje warunek "Status zamowienia" i zaznacza "wyslane"
And dodaje warunek "Liczba dni w statusie" = 7
Then regula zapisuje sie poprawnie z oboma warunkami
```

## AC-4: Cron handler skanuje i wyzwala event
```gherkin
Given istnieje regula: event=order.status_aged, warunek order_status=wyslane, days_in_status=7, akcja=update_order_status(zrealizowane)
And zamowienie #100 ma status "wyslane" od 8 dni (w order_status_history)
When cron uruchamia handler order_status_aged
Then event order.status_aged jest wyzwolony dla zamowienia #100
And regula jest dopasowana i status zmieniony na "zrealizowane"
```

## AC-5: Deduplikacja — zamowienie przetworzone raz na cykl
```gherkin
Given regula juz wykonala akcje na zamowieniu #100 w poprzednim cyklu crona
And zamowienie #100 nadal ma status "wyslane" (bo akcja zmienila na "zrealizowane" ale to inny status)
When cron uruchamia handler ponownie
Then zamowienie #100 nie jest przetwarzane ponownie (status sie zmienil)
```

</acceptance_criteria>

<tasks>

<task type="auto">
  <name>Task 1: Migracja cron schedule + serwis skanowania</name>
  <files>database/migrations/20260331_000074_seed_order_status_aged_cron.sql, src/Modules/Automation/OrderStatusAgedService.php, src/Modules/Cron/OrderStatusAgedHandler.php, src/Modules/Cron/CronHandlerFactory.php</files>
  <action>
    **Migracja (seed cron schedule):**
    - INSERT INTO cron_schedules: job_key=`order_status_aged`, interval_minutes=60 (co godzine), is_active=1
    - Idempotentny INSERT (IF NOT EXISTS pattern jak inne migracje)

    **OrderStatusAgedService.php** (nowa klasa w `App\Modules\Automation`):
    - Konstruktor: `AutomationRepository $repository`, `AutomationService $automation`, `PDO $db`
    - Metoda `scan(): void`:
      1. Pobierz aktywne reguly z event_type=`order.status_aged` (`$this->repository->findActiveByEvent('order.status_aged')`)
      2. Dla kazdej reguly:
         a. Wyciagnij warunki: `order_status` (kody statusow) i `days_in_status` (int dni)
         b. Query do DB: zamowienia gdzie `external_status_id` IN (kody) i ostatnia zmiana w `order_status_history` (MAX(changed_at) WHERE to_status_id = external_status_id) jest starsza niz X dni
         c. Dla kazdego znalezionego zamowienia: `$this->automation->trigger('order.status_aged', $orderId, ['current_status' => ..., 'days_in_status' => ..., 'status_changed_at' => ...])`
      3. Deduplikacja naturalna: po wykonaniu akcji (np. zmiana statusu) zamowienie nie spelnia juz warunku w nastepnym cyklu

    **Query do skanowania (w OrderStatusAgedService):**
    ```sql
    SELECT o.id, o.external_status_id, MAX(h.changed_at) AS last_changed
    FROM orders o
    INNER JOIN order_status_history h ON h.order_id = o.id
      AND LOWER(h.to_status_id) = LOWER(o.external_status_id)
    WHERE LOWER(o.external_status_id) IN (:statuses)
    GROUP BY o.id, o.external_status_id
    HAVING MAX(h.changed_at) <= DATE_SUB(NOW(), INTERVAL :days DAY)
    ```
    - Uzyj prepared statements (medoo pattern nie wymagany - raw PDO OK jak w OrdersRepository)
    - Limit do 100 zamowien na cykl (bezpieczenstwo)

    **OrderStatusAgedHandler.php** (nowa klasa w `App\Modules\Cron`):
    - Implementuje `CronHandlerInterface` (sprawdz jaki interfejs uzywaja inne handlery)
    - Konstruktor: `OrderStatusAgedService $service`
    - Metoda `handle()`: wywoluje `$this->service->scan()`

    **CronHandlerFactory.php:**
    - Dodaj import `use App\Modules\Automation\OrderStatusAgedService;`
    - Dodaj import `use App\Modules\Cron\OrderStatusAgedHandler;`
    - W `build()` dodaj handler `'order_status_aged'` do tablicy handlerow:
      ```php
      'order_status_aged' => new OrderStatusAgedHandler(
          new OrderStatusAgedService($automationRepository, $automationService, $this->db)
      ),
      ```
    - `$automationRepository` juz jest tworzony w `buildAutomationService()` — wyciagnij go lub utwórz wczesniej
  </action>
  <verify>Plik migracji istnieje, OrderStatusAgedService.php ma metode scan(), handler zarejestrowany w CronHandlerFactory</verify>
  <done>AC-4, AC-5 satisfied: cron handler skanuje zamowienia i wyzwala event z naturalna deduplikacja</done>
</task>

<task type="auto">
  <name>Task 2: Backend kontroler + serwis automatyzacji</name>
  <files>src/Modules/Automation/AutomationController.php, src/Modules/Automation/AutomationService.php</files>
  <action>
    **AutomationController.php:**
    - Dodaj `'order.status_aged'` do `ALLOWED_EVENTS`
    - Dodaj `'days_in_status'` do `ALLOWED_CONDITION_TYPES`
    - W `buildRuleFromRequest()` dodaj branch `elseif ($type === 'days_in_status')`:
      `$value = ['days' => max(1, (int) ($cond['days'] ?? 0))];`
    - W `parseConditionValue()` dodaj branch `if ($type === 'days_in_status')`:
      ```php
      $days = (int) ($condition['days'] ?? 0);
      return $days >= 1 ? ['days' => $days] : null;
      ```

    **AutomationService.php:**
    - Dodaj metode `evaluateDaysInStatusCondition(array $value, array $context): bool`:
      - Porownuje `$value['days']` z `$context['days_in_status']`
      - Zwraca true jesli `$context['days_in_status'] >= $value['days']`
    - W `evaluateSingleCondition()` dodaj branch:
      `if ($type === 'days_in_status') return $this->evaluateDaysInStatusCondition($value, $context);`
  </action>
  <verify>Grep ALLOWED_EVENTS zawiera order.status_aged; grep evaluateDaysInStatusCondition istnieje; grep days_in_status w parseConditionValue</verify>
  <done>AC-3 satisfied: warunki order_status i days_in_status dzialaja razem</done>
</task>

<task type="auto">
  <name>Task 3: Frontend — formularz + widok</name>
  <files>resources/views/automation/form.php, resources/views/automation/index.php, public/assets/js/modules/automation-form.js</files>
  <action>
    **form.php:**
    - Dodaj do `$eventLabels`: `'order.status_aged' => 'Minelo X dni od zmiany statusu'`
    - W sekcji renderowania warunkow dodaj branch `elseif ($conditionType === 'days_in_status')`:
      Renderuj `<input type="number" min="1" step="1" class="form-control" name="conditions[{idx}][days]" value="{value}" placeholder="Liczba dni">`
      Wartosc z `$condValue['days'] ?? ''`

    **index.php:**
    - Dodaj do `$eventLabels`: `'order.status_aged' => 'Minelo X dni od zmiany statusu'`

    **automation-form.js:**
    - Dodaj funkcje `buildDaysInStatusInput(namePrefix)`:
      Zwraca `<input type="number" min="1" step="1" class="form-control" name="{namePrefix}[days]" placeholder="Liczba dni">`
    - W `addCondition()` dodaj opcje `<option value="days_in_status">Liczba dni w statusie</option>`
    - W `onConditionTypeChange()` dodaj branch:
      `if (select.value === 'days_in_status') { configDiv.innerHTML = buildDaysInStatusInput(namePrefix); }`
  </action>
  <verify>Otworz /settings/automation/create, sprawdz ze zdarzenie i warunki sa dostepne w formularzu</verify>
  <done>AC-1, AC-2 satisfied: event w dropdown, warunek z polem numerycznym</done>
</task>

</tasks>

<boundaries>

## DO NOT CHANGE
- src/Modules/Orders/OrdersRepository.php (query skanowania w osobnym serwisie)
- Istniejace handlery crona

## SCOPE LIMITS
- Tylko event `order.status_aged` i warunek `days_in_status`
- Cron co 1 godzine (konfigurowalny z panelu cron schedules)
- Limit 100 zamowien na cykl na regule
- Brak UI do konfiguracji interwalu crona (juz istnieje w panelu cron)

</boundaries>

<verification>
Before declaring plan complete:
- [ ] Migracja seeduje cron schedule
- [ ] OrderStatusAgedService skanuje zamowienia po order_status_history
- [ ] Handler zarejestrowany w CronHandlerFactory
- [ ] `order.status_aged` widoczny w dropdown zdarzenia
- [ ] Warunek `days_in_status` renderuje pole numeryczne
- [ ] Warunek `order_status` dziala z tym zdarzeniem (reuse z Phase 59)
- [ ] Zapis i edycja reguly dziala poprawnie
- [ ] Naturalna deduplikacja: po zmianie statusu zamowienie nie spelnia warunku
</verification>

<success_criteria>
- Wszystkie taski wykonane
- Wszystkie AC spelnione
- Brak bledow PHP/JS
- Cron handler bezpieczny (limit, try/catch, non-blocking)
</success_criteria>

<output>
After completion, create `.paul/phases/60-order-status-aged-event/60-01-SUMMARY.md`
</output>
