This commit is contained in:
2026-03-31 00:30:50 +02:00
parent 5435209b08
commit af48e84449
30 changed files with 2706 additions and 111 deletions

View File

@@ -64,6 +64,11 @@ Sprzedawca moĹĽe obsĹugiwać zamĂłwienia ze wszystkich kanaĹĂłw
- [x] Mobile Status Panel Toggle: zwijany/rozwijany panel statusow na /orders/list — Phase 53
- [x] Order Detail Image Hover: hover zoom na miniaturkach produktow w /orders/{id} — Phase 54
- [x] Desktop Collapsed Sidebar Fix: ukrycie etykiet, centrowanie ikon w zwiniętym sidebarze — Phase 55
- [x] Dodawanie płatności ręcznych + push set_paid do shopPRO — Phase 56
- [x] Automatyzacja: event `payment.status_changed` + warunek `payment_status` (0/1/2) — Phase 57
- [x] Zachowanie danych formularza automatyzacji po bledzie walidacji — Phase 58
- [x] Automatyzacja: event `order.status_changed` + warunek `order_status` — Phase 59
- [x] Automatyzacja: event `order.status_aged` (cron) + warunek `days_in_status` — Phase 60
### Active (In Progress)

View File

@@ -16,7 +16,11 @@ Wersja mobilna aplikacji, modul po module. Cel: pelna uzywalnosc orderPRO na tel
| 53 | Mobile Status Panel Toggle | 1/1 | Complete |
| 54 | Order Detail Image Hover | 1/1 | Complete |
| 55 | Desktop Collapsed Sidebar Fix | 1/1 | Complete |
| 56 | Order Payments | 0/1 | Planning |
| 56 | Order Payments | 1/1 | Complete |
| 57 | Payment Automation Event | 1/1 | Complete |
| 58 | Automation Form Preserve | 1/1 | Complete |
| 59 | Order Status Automation Event | 1/1 | Complete |
| 60 | Order Status Aged Event | 1/1 | Complete |
| TBD | Mobile Orders List | - | Not started |
| TBD | Mobile Order Details | - | Not started |
| TBD | Mobile Settings | - | Not started |

View File

@@ -2,49 +2,40 @@
## Project Reference
See: .paul/PROJECT.md (updated 2026-03-28)
See: .paul/PROJECT.md (updated 2026-03-31)
**Core value:** Sprzedawca moze obslugiwac zamowienia ze wszystkich kanalow sprzedazy i nadawac przesylki bez przelaczania sie miedzy platformami.
**Current focus:** Milestone v3.0 Mobile Responsive — Phase 56 (Order Payments) planning
**Current focus:** Milestone v3.0 — Phase 60 complete, ready for next PLAN
## Current Position
Milestone: v3.0 Mobile Responsive — In progress
Phase: 5 of N (56 - Order Payments) — Planning
Plan: 56-01 created, awaiting approval
Status: PLAN created, ready for APPLY
Last activity: 2026-03-30Created .paul/phases/56-order-payments/56-01-PLAN.md
Phase: 9 of N (60 - Order Status Aged Event) — Complete
Plan: 60-01 complete
Status: Loop complete — phase 60 done, ready for next PLAN
Last activity: 2026-03-31UNIFY closed for 60-01
Progress:
- Milestone: [####░░░░░░] ~40%
- Phase 56: [░░░░░░░░░░] 0%
- Milestone: [######░░░░] ~55%
- Phase 60: [##########] 100%
## Loop Position
Current loop state:
```
PLAN ──▶ APPLY ──▶ UNIFY
[Plan created, awaiting approval]
[Loop complete - ready for next PLAN]
```
## Session Continuity
Last session: 2026-03-30
Stopped at: Plan 56-01 created
Next action: Review and approve plan, then run /paul:apply .paul/phases/56-order-payments/56-01-PLAN.md
Resume file: .paul/phases/56-order-payments/56-01-PLAN.md
## Accumulated Context
### Decisions
| Date | Decision | Impact |
|------|----------|--------|
| 2026-03-29 | Mobile menu jako slide-in overlay (nie horizontal scroll) | Pelna nawigacja na mobile bez kompromisow |
| 2026-03-29 | Hamburger w topbarze, sidebar fixed z transform slide | Plynna animacja CSS, zero zaleznosci JS |
| 2026-03-30 | Push set_paid do shopPRO API po dodaniu platnosci w orderPRO | Synchronizacja statusu platnosci bez dodatkowego endpointu w shopPRO |
Last session: 2026-03-31
Stopped at: Phase 60 complete
Next action: /paul:plan dla kolejnego modulu
Resume file: .paul/phases/60-order-status-aged-event/60-01-SUMMARY.md
## Git State
Last commit: 70662af
Last commit: 5435209
Branch: main
Feature branches merged: none

View File

@@ -0,0 +1,34 @@
---
phase: 56-order-payments
plan: 01
status: complete
completed: 2026-03-30
---
## Summary
Uruchomienie funkcji dodawania platnosci do zamowienia z zakladki Platnosci + push statusu platnosci do shopPRO API.
## What Was Built
- Migracja `20260330_000073_create_order_payments_table.sql` — tabela `order_payments` (INT UNSIGNED FK) + idempotentne kolumny `total_with_tax`, `total_paid`, `external_payment_type_id` w `orders`
- `OrdersRepository::addPayment()` — INSERT + przeliczenie total_paid i payment_status
- `OrdersRepository::findOrderSourceInfo()` — dane source/integration dla push
- `OrdersController::addPayment()` — POST `/orders/{id}/payment/add` z walidacja, try/catch, activity log
- `OrdersController::pushPaymentToShoppro()` — push `set_paid` do shopPRO API po pelnym oplaceniu
- `ShopproApiClient::setOrderPaid()` — PUT `/api.php?endpoint=orders&action=set_paid`
- Formularz inline w zakladce Platnosci (przycisk "Dodaj platnosc", pola: kwota/typ/data/komentarz, AJAX submit)
- Style `.payment-add-form` w SCSS
## Deviations
- FK type fix: `order_payments.order_id` zmieniony z BIGINT na INT UNSIGNED (zgodnosc z `orders.id`)
- `Csrf::verify()``Csrf::validate()` (poprawna nazwa metody)
- Dodano try/catch w kontrolerze dla lepszej diagnostyki bledow
## Decisions
| Decision | Rationale |
|----------|-----------|
| Push set_paid do shopPRO bez nowego endpointu | shopPRO API juz ma `set_paid` — wystarczajace |
| ShopproIntegrationsRepository jako nullable dep w OrdersController | Minimalna zmiana konstruktora |

View File

@@ -0,0 +1,219 @@
---
phase: 57-payment-automation-event
plan: 01
type: execute
wave: 1
depends_on: ["56-01"]
files_modified:
- src/Modules/Automation/AutomationController.php
- src/Modules/Automation/AutomationService.php
- src/Modules/Orders/OrdersController.php
- src/Modules/Settings/ShopproPaymentStatusSyncService.php
- src/Modules/Cron/CronHandlerFactory.php
- resources/views/automation/form.php
- public/assets/js/modules/automation-form.js
- routes/web.php
- DOCS/ARCHITECTURE.md
- DOCS/TECH_CHANGELOG.md
autonomous: true
---
<objective>
## Goal
Dodanie zdarzenia automatyzacji `payment.status_changed` emitowanego przy zmianie statusu platnosci zamowienia. Warunek `payment_status` pozwala filtrowac po konkretnym statusie (oplacone, czesciowo oplacone, nieoplacone).
## Purpose
Sprzedawca moze tworzyc reguly automatyzacji reagujace na zmiane statusu platnosci — np. wyslij email po oplaceniu, zmien status zamowienia, wystaw paragon.
## Output
- Nowy event `payment.status_changed` w silniku automatyzacji
- Nowy warunek `payment_status` z checkboxami statusow
- Emisja eventu z OrdersController::addPayment() i ShopproPaymentStatusSyncService
- UI: event i warunek widoczne w formularzu regul
</objective>
<context>
## Project Context
@.paul/PROJECT.md
@.paul/ROADMAP.md
## Prior Work
@.paul/phases/56-order-payments/56-01-SUMMARY.md (addPayment, payment_status update)
## Source Files
@src/Modules/Automation/AutomationController.php (ALLOWED_EVENTS linia 19, ALLOWED_CONDITION_TYPES linia 20)
@src/Modules/Automation/AutomationService.php (evaluateSingleCondition, evaluateShipmentStatusCondition — wzorzec)
@src/Modules/Orders/OrdersController.php (addPayment — miejsce emisji eventu)
@src/Modules/Settings/ShopproPaymentStatusSyncService.php (syncSingleOrderPayment — miejsce emisji eventu)
@src/Modules/Cron/CronHandlerFactory.php (kompozycja ShopproPaymentStatusSyncService)
@resources/views/automation/form.php (eventLabels, condition select, JS data)
@public/assets/js/modules/automation-form.js (addCondition, onConditionTypeChange, buildShipmentStatusCheckboxes — wzorzec)
@routes/web.php (tworzenie $automationService linia 253, $ordersController linia 264)
</context>
<acceptance_criteria>
## AC-1: Event payment.status_changed dostepny w formularzu
```gherkin
Given otwieram Ustawienia > Zadania automatyczne > Nowa regula
When klikam dropdown "Zdarzenie"
Then widze opcje "Zmiana statusu platnosci" obok istniejacych zdarzen
```
## AC-2: Warunek payment_status dostepny w formularzu
```gherkin
Given wybralem zdarzenie "Zmiana statusu platnosci"
When dodaje warunek i wybieram typ "Status platnosci"
Then widze checkboxy: Nieoplacone, Czesciowo oplacone, Oplacone
And moge zaznaczyc jeden lub wiecej statusow
```
## AC-3: Event emitowany przy recznym dodaniu platnosci
```gherkin
Given istnieje aktywna regula: event=payment.status_changed, warunek=payment_status IN [oplacone], akcja=update_order_status
When dodaje platnosc pokrywajaca pelna kwote zamowienia
Then regula sie odpala i status zamowienia zmienia sie automatycznie
And w historii automatyzacji widze wpis o wykonaniu reguly
```
## AC-4: Event emitowany przy sync platnosci z shopPRO
```gherkin
Given zamowienie source=shoppro zmienia payment_status przez cron sync
When nowy payment_status rozni sie od poprzedniego
Then event payment.status_changed jest emitowany z kontekstem (old/new status)
```
## AC-5: Warunek filtruje poprawnie
```gherkin
Given regula z warunkiem payment_status = [oplacone]
When event jest emitowany z new_payment_status = 1 (czesciowo oplacone)
Then regula NIE odpala sie
When event jest emitowany z new_payment_status = 2 (oplacone)
Then regula odpala sie
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Backend — event + warunek w silniku automatyzacji</name>
<files>src/Modules/Automation/AutomationController.php, src/Modules/Automation/AutomationService.php</files>
<action>
1. W AutomationController:
- Dodaj 'payment.status_changed' do ALLOWED_EVENTS (linia 19)
- Dodaj 'payment_status' do ALLOWED_CONDITION_TYPES (linia 20)
- W metodzie parseConditions(): obsluz 'payment_status' — zbierz payment_status_keys[] z POST, zapisz jako condition_value.status_keys (wzorzec jak shipment_status)
2. W AutomationService::evaluateSingleCondition():
- Dodaj branch: if ($type === 'payment_status') return $this->evaluatePaymentStatusCondition($value, $context);
3. Dodaj metode evaluatePaymentStatusCondition(array $value, array $context): bool:
- Pobierz status_keys z $value (tablica dozwolonych statusow: '0', '1', '2')
- Pobierz new_payment_status z $context (string)
- Return true jesli new_payment_status jest w status_keys
- Mapowanie: 0=nieoplacone, 1=czesciowo_oplacone, 2=oplacone
Wzoruj sie dokladnie na evaluateShipmentStatusCondition() — analogiczna logika.
</action>
<verify>PHP lint — brak bledow skladniowych</verify>
<done>AC-1, AC-2, AC-5 satisfied: event i warunek zarejestrowane w silniku</done>
</task>
<task type="auto">
<name>Task 2: Emisja eventu z OrdersController i ShopproPaymentStatusSyncService</name>
<files>src/Modules/Orders/OrdersController.php, src/Modules/Settings/ShopproPaymentStatusSyncService.php, src/Modules/Cron/CronHandlerFactory.php, routes/web.php</files>
<action>
1. W OrdersController:
- Dodaj ?AutomationService $automation = null do konstruktora (po $shopproIntegrations)
- W addPayment(), po zapisie platnosci i activity log, przed pushPaymentToShoppro:
try { $this->automation?->trigger('payment.status_changed', $orderId, [
'new_payment_status' => (string) $result['payment_status'],
'total_paid' => $result['total_paid'],
'payment_type_id' => $paymentTypeId,
]); } catch (Throwable) {}
2. W routes/web.php:
- Przekaz $automationService do OrdersController (juz istnieje w scope na linii 253)
- Dodaj do wywolania new OrdersController(..., $automationService)
3. W ShopproPaymentStatusSyncService:
- Dodaj ?AutomationService $automation = null do konstruktora (po $pdo)
- W syncSingleOrderPayment(), po uaktualnieniu payment status (po commit, przed recordActivity):
if ($existingPaymentStatus !== $newPaymentStatus) {
try { $this->automation?->trigger('payment.status_changed', $orderId, [
'new_payment_status' => (string) $newPaymentStatus,
'old_payment_status' => $existingPaymentStatus !== null ? (string) $existingPaymentStatus : '',
'total_paid' => $newTotalPaid,
'payment_method' => $paymentMethod,
]); } catch (Throwable) {}
}
4. W CronHandlerFactory:
- Przekaz $automationService do ShopproPaymentStatusSyncService
- AutomationService jest juz tworzony w CronHandlerFactory (buildAutomationHandler) — uzyj go
- UWAGA: ShopproPaymentStatusSyncHandler jest tworzony w innym miejscu niz automation — sprawdz czy $automationService jest dostepny i przekaz go
</action>
<verify>PHP lint — brak bledow skladniowych. Przejrzyj CronHandlerFactory ze szczegolna uwaga na kolejnosc tworzenia obiektow.</verify>
<done>AC-3, AC-4 satisfied: event emitowany z obu zrodel</done>
</task>
<task type="auto">
<name>Task 3: UI — event i warunek w formularzu automatyzacji</name>
<files>resources/views/automation/form.php, public/assets/js/modules/automation-form.js, DOCS/ARCHITECTURE.md, DOCS/TECH_CHANGELOG.md</files>
<action>
1. W resources/views/automation/form.php:
- Dodaj do $eventLabels: 'payment.status_changed' => 'Zmiana statusu platnosci'
- W sekcji warunków (condition select): dodaj option value="payment_status">Status platnosci
- W sekcji renderowania warunku (PHP): dodaj branch elseif ($conditionType === 'payment_status') z checkboxami:
Statusy: ['0' => 'Nieoplacone', '1' => 'Czesciowo oplacone', '2' => 'Oplacone']
Input name: conditions[idx][payment_status_keys][]
- Dodaj paymentStatusOptions do window.AutomationFormData
2. W public/assets/js/modules/automation-form.js:
- Dodaj function buildPaymentStatusCheckboxes(namePrefix) — analogicznie do buildShipmentStatusCheckboxes ale z paymentStatusOptions
- W addCondition(): dodaj option value="payment_status">Status platnosci w select
- W onConditionTypeChange(): dodaj branch if (select.value === 'payment_status') { configDiv.innerHTML = buildPaymentStatusCheckboxes(namePrefix); }
3. Zaktualizuj DOCS/ARCHITECTURE.md i DOCS/TECH_CHANGELOG.md.
</action>
<verify>Otworz formularz reguly — widoczne nowe zdarzenie i warunek. PHP lint.</verify>
<done>AC-1, AC-2 satisfied: UI gotowe</done>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- database/migrations/* (brak zmian schematu — event_type to VARCHAR, nowy typ nie wymaga migracji)
- src/Modules/Automation/AutomationRepository.php (warstwa danych bez zmian)
- src/Modules/Automation/AutomationExecutionLogRepository.php
## SCOPE LIMITS
- Tylko event payment.status_changed i warunek payment_status
- Nie dodajemy nowych akcji (istniejace: send_email, issue_receipt, update_shipment_status, update_order_status wystarczaja)
- Nie modyfikujemy istniejacych eventow ani warunkow
</boundaries>
<verification>
Before declaring plan complete:
- [ ] PHP lint — brak bledow we wszystkich zmodyfikowanych plikach
- [ ] Formularz reguly: widoczne zdarzenie "Zmiana statusu platnosci"
- [ ] Formularz reguly: warunek "Status platnosci" z checkboxami
- [ ] Dodanie platnosci emituje event
- [ ] Sync platnosci z shopPRO emituje event
- [ ] DOCS zaktualizowane
</verification>
<success_criteria>
- Wszystkie taski ukonczone
- Nowy event i warunek widoczne w UI
- Event emitowany z obu zrodel (reczne + cron sync)
- Warunek filtruje po statusie platnosci
</success_criteria>
<output>
After completion, create `.paul/phases/57-payment-automation-event/57-01-SUMMARY.md`
</output>

View File

@@ -0,0 +1,60 @@
---
phase: 57-payment-automation-event
plan: 01
status: complete
completed: 2026-03-30
---
# Phase 57 Plan 01: Payment Automation Event Summary
**Event automatyzacji `payment.status_changed` z warunkiem `payment_status` — emisja z recznego dodania platnosci i cron sync shopPRO**
## Acceptance Criteria Results
| Criterion | Status | Notes |
|-----------|--------|-------|
| AC-1: Event payment.status_changed dostepny w formularzu | Pass | Opcja "Zmiana statusu platnosci" w dropdown zdarzenia |
| AC-2: Warunek payment_status dostepny w formularzu | Pass | Checkboxy: Nieoplacone, Czesciowo oplacone, Oplacone |
| AC-3: Event emitowany przy recznym dodaniu platnosci | Pass | trigger() w OrdersController::addPayment() po zapisie |
| AC-4: Event emitowany przy sync platnosci z shopPRO | Pass | trigger() w ShopproPaymentStatusSyncService po zmianie statusu |
| AC-5: Warunek filtruje poprawnie | Pass | evaluatePaymentStatusCondition porownuje new_payment_status z status_keys |
## Files Created/Modified
| File | Change | Purpose |
|------|--------|---------|
| src/Modules/Automation/AutomationController.php | Modified | ALLOWED_EVENTS + payment.status_changed, ALLOWED_CONDITION_TYPES + payment_status, PAYMENT_STATUS_OPTIONS, parseConditions branch, paymentStatusOptions w widoku |
| src/Modules/Automation/AutomationService.php | Modified | evaluatePaymentStatusCondition() + branch w evaluateSingleCondition |
| src/Modules/Orders/OrdersController.php | Modified | AutomationService dep + trigger event w addPayment() |
| src/Modules/Settings/ShopproPaymentStatusSyncService.php | Modified | AutomationService dep + trigger event w syncSingleOrderPayment() |
| src/Modules/Cron/CronHandlerFactory.php | Modified | Przeniesienie tworzenia $automationService przed $shopproPaymentSyncService, przekazanie dep |
| routes/web.php | Modified | $automationService przekazany do OrdersController |
| resources/views/automation/form.php | Modified | Event label, warunek payment_status z checkboxami, paymentStatusOptions w JS data |
| public/assets/js/modules/automation-form.js | Modified | buildPaymentStatusCheckboxes(), opcja w addCondition + onConditionTypeChange |
| DOCS/ARCHITECTURE.md | Modified | Dokumentacja eventu i warunku |
| DOCS/TECH_CHANGELOG.md | Modified | Wpis Phase 57 |
## Decisions Made
| Decision | Rationale | Impact |
|----------|-----------|--------|
| Warunek payment_status uzywa prostego porownania string (nie mapowanie jak shipment_status) | Statusy platnosci to proste wartosci 0/1/2, nie wymagaja mapowania wielopoziomowego | Prostsza implementacja, latwiejsze debugowanie |
| $automationService przeniesiony wyzej w CronHandlerFactory | ShopproPaymentSyncService potrzebuje go jako dep — musi byc utworzony wczesniej | Zmiana kolejnosci tworzenia obiektow w factory |
| Emisja eventu non-blocking (try/catch) | Blad automatyzacji nie powinien blokowac zapisu platnosci ani sync crona | Odpornosc na bledy regul |
## Deviations from Plan
None — plan executed exactly as written.
## Next Phase Readiness
**Ready:**
- Event payment.status_changed dziala z obu zrodel (reczne + cron)
- Warunek payment_status pozwala filtrowac po statusie
- Wszystkie istniejace akcje (send_email, issue_receipt, update_order_status, update_shipment_status) dzialaja z nowym eventem
**Concerns:**
- Brak — standardowy wzorzec automatyzacji
**Blockers:**
- None

View File

@@ -0,0 +1,122 @@
---
phase: 58-automation-form-preserve
plan: 01
type: execute
wave: 1
depends_on: ["57-01"]
files_modified:
- src/Modules/Automation/AutomationController.php
- DOCS/TECH_CHANGELOG.md
autonomous: true
---
<objective>
## Goal
Zachowanie danych formularza automatyzacji po bledzie walidacji. Zamiast redirect (ktory traci dane) — re-render formularza z wypelnionymi polami i komunikatem bledu.
## Purpose
Uzytkownik nie traci wypelnionych danych (nazwa, zdarzenie, warunki, akcje) po napotkaniu bledu walidacji.
## Output
- Zmiana store() i update() w AutomationController — renderForm() z danymi z request zamiast redirect
</objective>
<context>
## Source Files
@src/Modules/Automation/AutomationController.php (store linia 106, update linia 133, renderForm linia 236)
@resources/views/automation/form.php (formularz korzysta z $rule do wypelnienia pol)
</context>
<acceptance_criteria>
## AC-1: Store — dane zachowane po bledzie walidacji
```gherkin
Given wypelniam formularz nowej reguly automatyzacji
When walidacja zwraca blad (np. brak warunkow)
Then formularz wyswietla sie ponownie z wypelnionymi danymi
And komunikat bledu jest widoczny
And nie nastepuje redirect
```
## AC-2: Update — dane zachowane po bledzie walidacji
```gherkin
Given edytuje istniejaca regule i zmieniam dane
When walidacja zwraca blad
Then formularz wyswietla sie ponownie z danymi z formularza (nie z DB)
And komunikat bledu jest widoczny
```
## AC-3: Zapis sukces — bez zmian w zachowaniu
```gherkin
Given wypelniam poprawnie formularz
When zapisuje
Then regula jest zapisana i nastepuje redirect do listy
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Re-render formularza zamiast redirect przy bledzie walidacji</name>
<files>src/Modules/Automation/AutomationController.php</files>
<action>
1. Dodaj prywatna metode buildRuleFromRequest(Request $request, ?int $id = null): array
- Zbuduj tablice $rule z POST data w formacie oczekiwanym przez form.php:
- 'id' => $id
- 'name' => $request->input('name')
- 'event_type' => $request->input('event_type')
- 'is_active' => $request->input('is_active') !== null ? 1 : 0
- 'conditions' => zmapuj z extractConditions — format: [{condition_type, condition_value}]
- 'actions' => zmapuj z extractActions — format: [{action_type, action_config}]
- Warunki i akcje powinny byc w takim formacie, jaki form.php oczekuje od $rule
2. Zmien renderForm(?array $rule, string $errorMessage = ''): Response
- Dodaj parametr $errorMessage
- Uzyj $errorMessage jesli niepusty, inaczej Flash::get
3. W store():
- Zamiast: Flash::set('error') + redirect('/create')
- Zrob: return $this->renderForm($this->buildRuleFromRequest($request), $validationError)
4. W update():
- Zamiast: Flash::set('error') + redirect('/edit?id=')
- Zrob: return $this->renderForm($this->buildRuleFromRequest($request, $id), $validationError)
5. Upewnij sie ze try/catch bledy (blad zapisu DB) tez uzywaja renderForm z danymi request — nie redirect.
</action>
<verify>PHP lint. Sprawdz ze formularz z bledna walidacja renderuje sie z danymi.</verify>
<done>AC-1, AC-2, AC-3 satisfied</done>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- resources/views/automation/form.php (widok juz obsluguje $rule — nie wymaga zmian)
- public/assets/js/modules/automation-form.js
- src/Modules/Automation/AutomationRepository.php
- src/Modules/Automation/AutomationService.php
## SCOPE LIMITS
- Tylko formularz automatyzacji — nie naprawiamy innych formularzy w tej fazie
- Nie zmieniamy struktury Flash class
</boundaries>
<verification>
- [ ] PHP lint bez bledow
- [ ] store() z bledna walidacja — formularz zachowuje dane
- [ ] update() z bledna walidacja — formularz zachowuje dane
- [ ] store() z poprawnymi danymi — zapis + redirect (bez regresji)
</verification>
<success_criteria>
- Dane formularza zachowane po bledzie walidacji
- Brak regresji w happy path (poprawny zapis)
</success_criteria>
<output>
After completion, create `.paul/phases/58-automation-form-preserve/58-01-SUMMARY.md`
</output>

View File

@@ -0,0 +1,21 @@
---
phase: 58-automation-form-preserve
plan: 01
status: complete
completed: 2026-03-30
---
## Summary
Zachowanie danych formularza automatyzacji po bledzie walidacji — re-render z danymi z request zamiast redirect.
## What Was Built
- `AutomationController::buildRuleFromRequest()` — buduje $rule z POST data (conditions, actions, name, event_type, is_active)
- `renderForm()` z parametrem $errorMessage — priorytet nad Flash
- `store()` i `update()` — re-render zamiast redirect przy bledzie walidacji i DB error
- form.php: $isEdit oparty na isset($rule['id']), conditions/actions wyciagane z $rule niezaleznie
## Deviations
- Zmiana w form.php (poza boundaries) — konieczna aby $isEdit poprawnie rozroznial nowy formularz od edycji gdy $rule jest ustawione z request data

View File

@@ -0,0 +1,183 @@
---
phase: 59-order-status-automation-event
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- src/Modules/Automation/AutomationController.php
- src/Modules/Automation/AutomationService.php
- src/Modules/Orders/OrdersController.php
- resources/views/automation/form.php
- resources/views/automation/index.php
- public/assets/js/modules/automation-form.js
autonomous: true
---
<objective>
## Goal
Dodanie zdarzenia automatyzacji `order.status_changed` emitowanego przy zmianie statusu zamowienia. Warunek `order_status` pozwala filtrowac po konkretnym statusie docelowym.
## Purpose
Uzytkownik moze tworzyc reguly automatyzacji reagujace na zmiane statusu zamowienia — np. wyslanie maila po zmianie na "wyslane" lub wystawienie paragonu po zmianie na "oplacone".
## Output
- Event `order.status_changed` dostepny w formularzu automatyzacji
- Warunek `order_status` z checkboxami aktywnych statusow
- Emisja eventu z OrdersController (reczna zmiana) i AutomationService (chain z akcji update_order_status)
</objective>
<context>
## Project Context
@.paul/PROJECT.md
@.paul/ROADMAP.md
@.paul/STATE.md
## Prior Work
@.paul/phases/57-payment-automation-event/57-01-SUMMARY.md — identyczny wzorzec (event + warunek + emisja)
## Source Files
@src/Modules/Automation/AutomationController.php
@src/Modules/Automation/AutomationService.php
@src/Modules/Orders/OrdersController.php
@resources/views/automation/form.php
@resources/views/automation/index.php
@public/assets/js/modules/automation-form.js
</context>
<acceptance_criteria>
## AC-1: Event order.status_changed dostepny w formularzu
```gherkin
Given uzytkownik otwiera formularz nowego zadania automatycznego
When rozwija dropdown "Zdarzenie"
Then widzi opcje "Zmiana statusu zamowienia"
```
## AC-2: Warunek order_status z checkboxami statusow
```gherkin
Given uzytkownik wybrarl zdarzenie "Zmiana statusu zamowienia"
When dodaje warunek "Status zamowienia"
Then widzi checkboxy z aktywnymi statusami zamowien
And moze zaznaczyc jeden lub wiecej statusow
```
## AC-3: Emisja eventu przy recznej zmianie statusu
```gherkin
Given istnieje aktywna regula: event=order.status_changed, warunek=order_status IN [wyslane], akcja=send_email
When uzytkownik zmienia status zamowienia na "wyslane" (ze strony szczegolw lub inline z listy)
Then event order.status_changed jest emitowany z kontekstem (old_status, new_status)
And regula jest dopasowana i akcja wykonana
```
## AC-4: Chain emission z akcji update_order_status
```gherkin
Given regula A: event=payment.status_changed, akcja=update_order_status(wyslane)
And regula B: event=order.status_changed, warunek=order_status IN [wyslane], akcja=send_email
When platnosc sie zmienia i regula A zmienia status zamowienia
Then regula B jest wyzwalana przez chain event order.status_changed
```
## AC-5: Etykieta w widoku listy regul i historii
```gherkin
Given istnieja reguly z event_type=order.status_changed
When uzytkownik otwiera strone /settings/automation
Then w kolumnie "Zdarzenie" widzi "Zmiana statusu zamowienia" (nie surowa nazwe)
And w zakladce Historia rowniez widzi czytelna etykiete
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Backend — event + warunek + emisja</name>
<files>src/Modules/Automation/AutomationController.php, src/Modules/Automation/AutomationService.php, src/Modules/Orders/OrdersController.php</files>
<action>
**AutomationController.php:**
- Dodaj `'order.status_changed'` do `ALLOWED_EVENTS` (linia 19)
- Dodaj `'order_status'` do `ALLOWED_CONDITION_TYPES` (linia 20)
- W `buildRuleFromRequest()` dodaj branch `elseif ($type === 'order_status')` analogiczny do `payment_status` — klucz `order_status_codes`, wartosc z `$cond['order_status_codes']`
- W `parseConditionValue()` dodaj branch `if ($type === 'order_status')` — walidacja kodow statusow vs `$this->repository->listActiveOrderStatuses()` (analogicznie do payment_status ale kody z DB)
**AutomationService.php:**
- Dodaj metode `evaluateOrderStatusCondition(array $value, array $context): bool` — porownuje `$context['new_status']` z `$value['order_status_codes']`
- W `evaluateSingleCondition()` dodaj branch: `if ($type === 'order_status') return $this->evaluateOrderStatusCondition($value, $context);`
- W `handleUpdateOrderStatus()` po udanej zmianie (`$updated === true`) dodaj `$this->emitEvent('order.status_changed', ...)` z kontekstem `old_status`, `new_status`, `automation_source`, `automation_rule` — analogicznie do `handleUpdateShipmentStatus()`
- Potrzeba pobrac old_status przed zmiana: dodaj query lub uzyj `$this->orders->findDetails()` do pobrania aktualnego statusu PRZED wywolaniem `updateOrderStatus()`
**OrdersController.php:**
- Po udanym `updateOrderStatus()` (linia 280, `$success === true`) dodaj emisje:
```php
try {
$this->automation?->trigger('order.status_changed', $orderId, [
'old_status' => $oldStatus,
'new_status' => $newStatus,
]);
} catch (Throwable) {}
```
- Pobierz `$oldStatus` PRZED wywolaniem `updateOrderStatus()` — dodaj query do pobrania aktualnego `external_status_id`
</action>
<verify>Grep ALLOWED_EVENTS zawiera order.status_changed; grep evaluateOrderStatusCondition istnieje; grep trigger('order.status_changed' w OrdersController</verify>
<done>AC-3, AC-4 satisfied: event emitowany z obu zrodel, warunek ewaluowany poprawnie</done>
</task>
<task type="auto">
<name>Task 2: Frontend — formularz + widok listy</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_changed' => 'Zmiana statusu zamowienia'`
- W sekcji renderowania warunkow dodaj branch `elseif ($condType === 'order_status')` — renderuj checkboxy z `$orderStatusOptions` (juz przekazywane do widoku przez renderForm), analogicznie do `payment_status` ale z kodem statusu jako value i nazwa jako label
**index.php:**
- Dodaj do `$eventLabels`: `'order.status_changed' => 'Zmiana statusu zamowienia'`
**automation-form.js:**
- W `addConditionRow()` dodaj opcje `<option value="order_status">Status zamowienia</option>` do selecta typu warunku
- Dodaj renderowanie checkboxow statusow zamowien gdy type === 'order_status' — uzyj danych z `window.__orderStatusOptions` (lub analogicznego mechanizmu jak payment/shipment status)
- W form.php dodaj `<script>window.__orderStatusOptions = <?= json_encode($orderStatusOptions) ?>;</script>` jesli jeszcze nie istnieje (sprawdz czy juz jest dla akcji update_order_status)
- W `onConditionTypeChange()` pokaz/ukryj panel checkboxow order_status
</action>
<verify>Otworz /settings/automation/create, sprawdz ze "Zmiana statusu zamowienia" jest w dropdown zdarzenia, ze warunek "Status zamowienia" renderuje checkboxy, i ze lista regul pokazuje czytelna etykiete</verify>
<done>AC-1, AC-2, AC-5 satisfied: event w dropdown, warunek z checkboxami, etykiety czytelne</done>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- database/migrations/* (brak nowych tabel/kolumn)
- src/Modules/Automation/AutomationRepository.php (bez zmian)
- src/Modules/Orders/OrdersRepository.php (bez zmian — `updateOrderStatus()` juz istnieje)
## SCOPE LIMITS
- Tylko event `order.status_changed` i warunek `order_status`
- Nie dodajemy nowych akcji
- Import zamowien z Allegro/shopPRO NIE emituje tego eventu (import ustawia status poczatkowy, nie zmienia istniejacego)
- Emisja tylko z recznej zmiany (OrdersController) i chain z akcji automatyzacji (AutomationService)
</boundaries>
<verification>
Before declaring plan complete:
- [ ] `order.status_changed` widoczny w dropdown zdarzenia w formularzu
- [ ] Warunek `order_status` renderuje checkboxy z aktywnymi statusami
- [ ] Reczna zmiana statusu zamowienia emituje event
- [ ] Akcja `update_order_status` emituje chain event `order.status_changed`
- [ ] Etykieta "Zmiana statusu zamowienia" wyswietlana w tabeli regul i historii
- [ ] Zapis i edycja reguly z tym zdarzeniem/warunkiem dziala poprawnie
- [ ] Brak petli: chain protection (depth + dedup) zapobiega nieskonczonej rekurencji
</verification>
<success_criteria>
- Wszystkie taski wykonane
- Wszystkie AC spelnione
- Brak bledow PHP/JS
- Formularz zachowuje dane warunku order_status po bledzie walidacji (wzorzec z Phase 58)
</success_criteria>
<output>
After completion, create `.paul/phases/59-order-status-automation-event/59-01-SUMMARY.md`
</output>

View File

@@ -0,0 +1,118 @@
---
phase: 59-order-status-automation-event
plan: 01
subsystem: automation
tags: [automation, events, order-status, conditions]
requires:
- phase: 57-payment-automation-event
provides: payment.status_changed event pattern, condition evaluation pattern
provides:
- order.status_changed automation event
- order_status condition type with checkbox UI
- chain emission from update_order_status action
affects: [future automation events, order workflow]
tech-stack:
added: []
patterns: [order status condition evaluation analogous to payment_status]
key-files:
created: []
modified:
- src/Modules/Automation/AutomationController.php
- src/Modules/Automation/AutomationService.php
- src/Modules/Orders/OrdersController.php
- resources/views/automation/form.php
- resources/views/automation/index.php
- public/assets/js/modules/automation-form.js
key-decisions:
- "Emisja eventu tylko przy realnej zmianie statusu (old != new)"
- "Chain emission z akcji update_order_status przez emitEvent() z depth protection"
patterns-established:
- "order_status condition: order_status_codes array z lowercase kodami statusow"
duration: 15min
started: 2026-03-30T23:45:00Z
completed: 2026-03-31T00:00:00Z
---
# Phase 59 Plan 01: Order Status Automation Event Summary
**Event automatyzacji `order.status_changed` z warunkiem `order_status` — emisja z recznej zmiany statusu i chain z akcji automatyzacji**
## Performance
| Metric | Value |
|--------|-------|
| Duration | ~15min |
| Tasks | 2 completed |
| Files modified | 6 |
## Acceptance Criteria Results
| Criterion | Status | Notes |
|-----------|--------|-------|
| AC-1: Event order.status_changed dostepny w formularzu | Pass | Opcja "Zmiana statusu zamowienia" w dropdown zdarzenia |
| AC-2: Warunek order_status z checkboxami statusow | Pass | Checkboxy z aktywnymi statusami zamowien (PHP + JS) |
| AC-3: Emisja eventu przy recznej zmianie statusu | Pass | OrdersController emituje po udanym updateOrderStatus() |
| AC-4: Chain emission z akcji update_order_status | Pass | AutomationService emituje przez emitEvent() z depth protection |
| AC-5: Etykieta w widoku listy regul i historii | Pass | index.php zawiera etykiety payment.status_changed + order.status_changed |
## Accomplishments
- Event `order.status_changed` dostepny w silniku automatyzacji z pelnym UI (dropdown zdarzenia, checkboxy warunku)
- Emisja z dwoch zrodel: reczna zmiana statusu (OrdersController) i chain z akcji automatyzacji (AutomationService)
- Naprawiono brakujaca etykiete `payment.status_changed` w index.php (bug z Phase 57)
## Files Created/Modified
| File | Change | Purpose |
|------|--------|---------|
| `src/Modules/Automation/AutomationController.php` | Modified | ALLOWED_EVENTS + order.status_changed, ALLOWED_CONDITION_TYPES + order_status, parseConditionValue + order_status, buildRuleFromRequest + order_status |
| `src/Modules/Automation/AutomationService.php` | Modified | evaluateOrderStatusCondition(), chain emit z handleUpdateOrderStatus(), dodano $context param |
| `src/Modules/Orders/OrdersController.php` | Modified | Pobranie oldStatus przed zmiana, emisja order.status_changed po sukcesie |
| `resources/views/automation/form.php` | Modified | Etykieta zdarzenia, opcja warunku order_status, checkboxy statusow zamowien |
| `resources/views/automation/index.php` | Modified | Etykiety payment.status_changed + order.status_changed |
| `public/assets/js/modules/automation-form.js` | Modified | buildOrderStatusCheckboxes(), opcja order_status w addCondition, onConditionTypeChange |
## Decisions Made
| Decision | Rationale | Impact |
|----------|-----------|--------|
| Emisja tylko przy old_status != new_status | Brak falszywych triggerow przy ustawieniu tego samego statusu | Spojnosc z wzorcem shipment.status_changed |
| handleUpdateOrderStatus otrzymuje $context dla chain | Potrzebny do emitEvent() z depth/dedup protection | Zapobiega petlom automatyzacji |
## Deviations from Plan
### Auto-fixed Issues
**1. Bugfix: brakujaca etykieta payment.status_changed w index.php**
- **Found during:** Poczatek sesji (zgloszenie uzytkownika)
- **Issue:** Phase 57 nie dodala etykiety do index.php — wyswietlala sie surowa nazwa
- **Fix:** Dodano wpis do $eventLabels w index.php
- **Files:** resources/views/automation/index.php
**Total impact:** Jeden bugfix z Phase 57 naprawiony przy okazji
## Issues Encountered
None
## Next Phase Readiness
**Ready:**
- Pelny zestaw eventow automatyzacji: receipt.created, shipment.created, shipment.status_changed, payment.status_changed, order.status_changed
- Wszystkie warianty warunkow: integration, shipment_status, payment_status, order_status
**Concerns:**
- None
**Blockers:**
- None
---
*Phase: 59-order-status-automation-event, Plan: 01*
*Completed: 2026-03-31*

View File

@@ -0,0 +1,235 @@
---
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>

View File

@@ -0,0 +1,125 @@
---
phase: 60-order-status-aged-event
plan: 01
subsystem: automation
tags: [automation, cron, order-status, time-based, aged]
requires:
- phase: 59-order-status-automation-event
provides: order_status condition type, evaluateOrderStatusCondition
provides:
- order.status_aged cron-based automation event
- days_in_status condition type
- OrderStatusAgedService (cron scanner)
- OrderStatusAgedHandler (cron handler)
affects: [automation workflows, cron infrastructure]
tech-stack:
added: []
patterns: [cron-based automation event scanning order_status_history]
key-files:
created:
- src/Modules/Automation/OrderStatusAgedService.php
- src/Modules/Cron/OrderStatusAgedHandler.php
- database/migrations/20260331_000074_seed_order_status_aged_cron.sql
modified:
- src/Modules/Automation/AutomationController.php
- src/Modules/Automation/AutomationService.php
- src/Modules/Cron/CronHandlerFactory.php
- resources/views/automation/form.php
- resources/views/automation/index.php
- public/assets/js/modules/automation-form.js
key-decisions:
- "Skanowanie przez JOIN order_status_history z HAVING MAX(changed_at) — precyzyjne wykrywanie czasu w statusie"
- "Limit 100 zamowien na regule na cykl — bezpieczenstwo przed przeciazeniem"
- "Naturalna deduplikacja — po zmianie statusu zamowienie nie spelnia warunku w nastepnym cyklu"
- "Cron co 1 godzine (3600s) — kompromis miedzy reaktywnoscia a obciazeniem"
patterns-established:
- "Cron-based automation event: OrderStatusAgedService skanuje DB i emituje event przez AutomationService.trigger()"
- "days_in_status condition: pole numeryczne, ewaluacja >= (nie ==)"
duration: 15min
started: 2026-03-31T00:10:00Z
completed: 2026-03-31T00:25:00Z
---
# Phase 60 Plan 01: Order Status Aged Event Summary
**Zdarzenie automatyzacji `order.status_aged` wyzwalane cronem — skanuje zamowienia w danym statusie od X dni i uruchamia reguly**
## Performance
| Metric | Value |
|--------|-------|
| Duration | ~15min |
| Tasks | 3 completed |
| Files created | 3 |
| Files modified | 6 |
## Acceptance Criteria Results
| Criterion | Status | Notes |
|-----------|--------|-------|
| AC-1: Event order.status_aged dostepny w formularzu | Pass | Opcja "Minelo X dni od zmiany statusu" w dropdown |
| AC-2: Warunek days_in_status z polem numerycznym | Pass | Input number min=1, placeholder "Liczba dni" |
| AC-3: Warunek order_status dziala z tym zdarzeniem | Pass | Reuse warunku z Phase 59, oba warunki zapisuja sie poprawnie |
| AC-4: Cron handler skanuje i wyzwala event | Pass | OrderStatusAgedService skanuje order_status_history, trigger z kontekstem |
| AC-5: Deduplikacja naturalna | Pass | Po zmianie statusu zamowienie nie spelnia warunku w nastepnym cyklu |
## Accomplishments
- Cron-based event `order.status_aged` — pierwsze zdarzenie czasowe w silniku automatyzacji
- OrderStatusAgedService skanuje zamowienia po `order_status_history.changed_at` z precyzyjnym query HAVING
- Warunek `days_in_status` z polem numerycznym i ewaluacja >= (zamowienie w statusie od 7 dni spelnia warunek "5 dni")
- Handler zarejestrowany w CronHandlerFactory, migracja seeduje cron schedule co 1 godzine
## Files Created/Modified
| File | Change | Purpose |
|------|--------|---------|
| `database/migrations/20260331_000074_seed_order_status_aged_cron.sql` | Created | Seed cron schedule: job_type=order_status_aged, interval=3600s |
| `src/Modules/Automation/OrderStatusAgedService.php` | Created | Serwis skanowania: findActiveByEvent, extractStatusCodes/Days, findAgedOrders, trigger |
| `src/Modules/Cron/OrderStatusAgedHandler.php` | Created | Cron handler delegujacy do OrderStatusAgedService.scan() |
| `src/Modules/Automation/AutomationController.php` | Modified | ALLOWED_EVENTS + order.status_aged, ALLOWED_CONDITION_TYPES + days_in_status, parseConditionValue + days_in_status |
| `src/Modules/Automation/AutomationService.php` | Modified | evaluateDaysInStatusCondition() — porownanie context.days_in_status >= value.days |
| `src/Modules/Cron/CronHandlerFactory.php` | Modified | Import OrderStatusAgedService, rejestracja handlera order_status_aged |
| `resources/views/automation/form.php` | Modified | Etykieta zdarzenia, opcja warunku days_in_status, pole numeryczne |
| `resources/views/automation/index.php` | Modified | Etykieta order.status_aged |
| `public/assets/js/modules/automation-form.js` | Modified | buildDaysInStatusInput(), opcja w addCondition/onChange |
## Decisions Made
| Decision | Rationale | Impact |
|----------|-----------|--------|
| Query HAVING MAX(changed_at) vs kolumna na orders | Precyzyjne — bazuje na faktycznej dacie zmiany z historii, nie wymaga nowej kolumny | Brak zmian schematu tabeli orders |
| Limit 100 zamowien na regule | Bezpieczenstwo — zapobiega przeciazeniu przy wielu zamowieniach w starym statusie | Moze wymagac podwyzszenia przy duzej skali |
| Cron co 1 godzine | Kompromis — wystarczajaco czeste dla regul dniowych, nie przeciaza bazy | Konfigurowalny z panelu cron schedules |
| Ewaluacja >= (nie ==) | Zamowienie "8 dni w statusie" spelnia regule "7 dni" — intuicyjne | Brak false negatives |
## Deviations from Plan
None — plan executed exactly as written.
## Issues Encountered
None
## Next Phase Readiness
**Ready:**
- Pelny zestaw eventow: receipt.created, shipment.created, shipment.status_changed, payment.status_changed, order.status_changed, order.status_aged
- Pelny zestaw warunkow: integration, shipment_status, payment_status, order_status, days_in_status
- Pierwszy cron-based event — wzorzec do reuzytku dla przyszlych eventow czasowych
**Concerns:**
- None
**Blockers:**
- None
---
*Phase: 60-order-status-aged-event, Plan: 01*
*Completed: 2026-03-31*