feat(129): order user notes module

CRUD notatek autorskich operatora per zamowienie z badge [N] na liscie
zamowien. Reuse istniejacej tabeli `order_notes` przez nowy
`note_type='user'` z `user_id` (FK->users SET NULL) i `author_name`
(snapshot). Sekcja `#notes` w "Wiadomosci i zalaczniki" w
`/orders/{id}` z inline edit form + delete przez
`OrderProAlerts.confirm`. Autoryzacja DB-level
(`WHERE user_id = :user_id`, rowCount=0 ⇒ 403) — bez admin override
(brak systemu rol w aplikacji).

- Migracja `20260514_000116_*.sql` (ADD COLUMN user_id + author_name +
  FK + indeks `idx_order_notes_type_order`); idempotentne z DDL
  no-op fallback.
- `OrderNotesService` (CRUD + walidacja body ≤ 2000 znakow); subquery
  `user_notes_count` w paginate; badge HTML w `toTableRow()`.
- 3 routy POST /orders/{id}/notes(/update|/delete).
- SCSS module `_order-notes.scss` + vanilla JS `order-notes.js`
  (inline edit toggle + delete confirm; idempotent guard).
- 9 kluczy i18n PL; PROJECT.md + ROADMAP.md + tech_changelog.md +
  db_schema.md zaktualizowane.

Follow-up: `php bin/migrate.php` + manualny smoke test (autor vs inny
user + badge na /orders/list).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-14 15:20:05 +02:00
parent c78ac335ee
commit 48351b5f36
20 changed files with 1261 additions and 25 deletions

View File

@@ -1,5 +1,29 @@
# Technical Changelog
## 2026-05-14 - Phase 129 Plan 01: Order User Notes module
**Co zrobiono:**
- Migracja `database/migrations/20260514_000116_extend_order_notes_user_authored.sql``order_notes` rozszerzona o `user_id INT UNSIGNED NULL` (FK → `users(id)` ON DELETE SET NULL), `author_name VARCHAR(190) NULL`, oraz indeks `idx_order_notes_type_order (note_type, order_id)`. Wszystkie ADD owinięte w `INFORMATION_SCHEMA` guard z DDL no-op fallback (`ALTER TABLE COMMENT`) — pattern Phase 115/125.
- `src/Modules/Orders/OrderNotesService.php` — nowy serwis CRUD nad `order_notes` z `note_type='user'`. Metody: `listUserNotes`, `listImportedNotes`, `countUserNotes`, `findById`, `create`, `update`, `delete`. Autoryzacja przez `WHERE user_id = :user_id` w UPDATE/DELETE — rowCount=0 ⇒ rzut `RuntimeException(code=403)`. Walidacja `body`: trim, niepuste, ≤ 2000 znakow.
- `src/Modules/Orders/OrdersRepository.php` — dodany `userNotesCountSubquerySql($orderAlias)` (subquery `COUNT(*) FROM order_notes WHERE note_type='user'`) używany w `paginateSql()` jako kolumna `user_notes_count`. `loadOrderNotes()` zawężony do `note_type <> 'user'` (importowane ze źródła). `transformOrderRow()` ekspozuje `user_notes_count`.
- `src/Modules/Orders/OrdersController.php` — nowa opcjonalna zależność `OrderNotesService` w konstruktorze (na końcu, nullable, BC-safe). 3 metody: `storeNote`, `updateNote`, `deleteNote` (każda CSRF + sesja + try/catch `RuntimeException`/`InvalidArgumentException`; rejestruje `order_activity_log event_type='note'` przez `OrdersRepository::recordActivity`). `toTableRow()` renderuje `<a class="order-notes-badge" href="/orders/{id}#notes">[N]</a>` obok numeru zamówienia gdy `user_notes_count > 0`. `show()` pobiera `userNotes` + `currentUserId` i przekazuje do widoku.
- `routes/web.php` — 3 nowe route'y `POST /orders/{id}/notes`, `POST /orders/{id}/notes/{noteId}/update`, `POST /orders/{id}/notes/{noteId}/delete`. `OrderNotesService` instancjonowany przed `new OrdersController(...)` i przekazany ostatnim argumentem.
- `resources/views/orders/show.php` — sekcja "Wiadomości i załączniki" przebudowana na 3 bloki: (1) `<div class="order-user-notes" id="notes">` z listą notatek (data · autor) + akcjami edit/delete dla autora + inline formularz dodawania, (2) ukryty `order-note-edit-form` per notatka rozwijany przez JS, (3) opcjonalny blok "Wiadomości ze źródła" gdy `$notesList !== []` (importowane, bez akcji).
- `resources/lang/pl.php` — 10 nowych kluczy `orders.details.notes_user_*` / `notes_imported_title` (UI labels w PL).
- `resources/scss/modules/_order-notes.scss` (nowy) + `@use` w `app.scss``.order-notes-badge` (niebieskoszary `#eef2ff/#4338ca`), `.order-user-notes`, `.order-event--user` (lewa krawędź `#6366f1`), `.order-imported-notes` (opacity 0.75), `.btn-link`, `.order-note-form`, `.order-note-edit-form`. CSS przebudowany via `npm run build:css`.
- `public/assets/js/modules/order-notes.js` (nowy) + `<script>` w `layouts/app.php` — wanilijowy JS: klik "Edytuj" toggle'uje `js-order-note-body``js-order-note-edit-form`, klik "Anuluj" wraca, submit formularza DELETE przechwycony i potwierdzany przez `OrderProAlerts.confirm({title, message, danger:true, onConfirm})` (options-object API z decyzji Phase 114). Idempotent guard `window.__orderNotesInit` + `dataset.bound`.
- `.paul/codebase/db_schema.md` — sekcja `order_notes` rozszerzona o pełne kolumny + notatkę Phase 129-01 (note_type='user' vs imported).
**Dlaczego:**
- Operator potrzebował miejsca na własne adnotacje per zamówienie (uzgodnienia z klientem, ustalenia wewnętrzne) niezależne od notatek importowanych z shopPRO/Allegro. Bez badge'a na liście trzeba by wchodzić w każde zamówienie żeby sprawdzić czy ma notatki.
- Reuse istniejącej tabeli `order_notes` (Plan clarification #1) zamiast nowej tabeli — mniej obiektów DB, jeden punkt zarządzania, semantyka rozróżniona przez `note_type`. UNIQUE `(order_id, source_note_id)` nadal działa bo MySQL traktuje wiele NULL jako unique.
- Brak admin override (Plan clarification #3 z dopiskiem): aplikacja nie ma systemu ról (`grep -rn "is_admin|role=" src/Modules/Auth` zwrócił 0 trafień). Autoryzacja przez `note.user_id = session.user_id` — operator który dodał notatkę edytuje/usuwa, inni widzą ale nie modyfikują. Pełen admin-override odłożony do osobnej fazy po wprowadzeniu ról.
- Indeks `idx_order_notes_type_order (note_type, order_id)` zapewnia że subquery `user_notes_count` w paginacji `/orders/list` nie degraduje performance przy rosnącej liczbie notatek (Phase 106 pattern dla `customer_returned_count`).
**BREAKING:** brak — wszystkie zmiany BC. `loadOrderNotes()` teraz zwraca tylko `note_type <> 'user'`, ale nikt poza `findDetails()` jej nie używa, a sekcja widoku zachowuje wstecznie kompatybilne `$notesList` z importowanych notatek (osobny blok pod nową sekcją "Notatki").
---
## 2026-05-14 - Phase 128 Plan 01: polkurier ShipmentService + Tracking + UI prepare
**Co zrobiono:**