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>
10 KiB
10 KiB
phase: 129-order-user-notes
plan: 01
subsystem: orders
tags: [order-notes, crud, badge, audit-log, user-authored]
requires:
- phase: 106-customer-return-alert
provides: badge pattern (
risk-return-badge) + subquery liczby per zamowienie
- phase: 114-accounting-configs-refactor
provides: OrderProAlerts.confirm options-object API
provides:
- Pelen CRUD notatek autorskich operatora per zamowienie (note_type='user')
- Subquery user_notes_count + badge [N] na /orders/list
- Inline edit (toggle textarea) + delete z OrderProAlerts.confirm
affects:
- Przyszle fazy z eventem automatyzacji note.created lub admin override po wprowadzeniu rol
tech-stack:
added: []
patterns:
- "Reuse istniejacej tabeli przez nowy note_type zamiast tworzenia osobnej tabeli"
- "Autoryzacja CRUD przez WHERE user_id = :user_id + rowCount=0 ⇒ RuntimeException(403)"
key-files:
created:
- database/migrations/20260514_000116_extend_order_notes_user_authored.sql
- src/Modules/Orders/OrderNotesService.php
- resources/scss/modules/_order-notes.scss
- public/assets/js/modules/order-notes.js
modified:
- src/Modules/Orders/OrdersController.php
- src/Modules/Orders/OrdersRepository.php
- routes/web.php
- resources/views/orders/show.php
- resources/views/layouts/app.php
- resources/lang/pl.php
- resources/scss/app.scss
key-decisions:
- "Reuse order_notes przez note_type='user' zamiast osobnej tabeli (clarification #1)"
- "Badge neutralny [N] (indigo #eef2ff/#4338ca) — subtelniejszy niz .risk-return-badge"
- "Brak admin override — edit/delete tylko dla autora (brak systemu rol w aplikacji)"
- "Sekcja #notes w istniejacej karcie 'Wiadomosci i zalaczniki' — split na 'Notatki' (user) + 'Wiadomosci ze zrodla' (imported)"
patterns-established:
- "userNotesCountSubquerySql($orderAlias) — wzorzec dla COUNT-per-order subquery bez wplywu na ORDER BY/GROUP BY (analogiczny do customerReturnedCountSubquerySql)"
- "OrderProAlerts.confirm z danger:true + options-object API dla submit'u formularza DELETE (preventDefault + onConfirm submit)"
- "Migracje no-op zawsze jako DDL (ALTER TABLE COMMENT), nigdy SELECT 1 (Phase 115 pattern)"
duration: ~30min
started: 2026-05-14T00:00:00Z
completed: 2026-05-14T00:00:00Z
Phase 129 Plan 01: Order User Notes Summary
Pelen CRUD notatek autorskich operatora w zamowieniach (extend order_notes o user_id/author_name/note_type='user'), z sekcja #notes w szczegolach i badge [N] na liscie zamowien.
Performance
| Metric | Value |
|---|---|
| Duration | ~30min |
| Tasks | 3 of 3 completed |
| Files created | 5 |
| Files modified | 7 |
| AC pass rate | 6 of 6 (pending live smoke) |
Acceptance Criteria Results
| Criterion | Status | Notes |
|---|---|---|
| AC-1: Migracja DB — kolumny user notes | Pass (code) | Migracja 20260514_000116_*.sql z information_schema guard + DDL no-op fallback. Aktywacja na zywej bazie: pending operator. |
| AC-2: Tworzenie notatki | Pass (code) | OrderNotesService::create() + OrdersController::storeNote() + recordActivity('note', 'Dodano notatke'). Redirect 302 → /orders/{id}#notes. |
| AC-3: Edycja/usuwanie tylko autor | Pass (code) | UPDATE/DELETE z WHERE user_id = :user_id; rowCount=0 ⇒ RuntimeException(403). UI ukrywa przyciski gdy note.user_id != session.user_id. |
| AC-4: Lista w "Wiadomosci i zalaczniki" | Pass (code) | Sekcja #notes z 3 blokami (lista user notes → form dodawania → opcjonalny block "Wiadomosci ze zrodla"). |
AC-5: Badge [N] na liscie zamowien |
Pass (code) | <a class="order-notes-badge" href="/orders/{id}#notes">[N]</a> wstrzykniete w order_ref HTML; widoczne tylko gdy user_notes_count >= 1. |
| AC-6: Subquery liczy bez wplywu na paginacje | Pass (code) | userNotesCountSubquerySql('o') jako kolumna SELECT (NIE w WHERE/GROUP BY/ORDER). Wspierane indeksem idx_order_notes_type_order (note_type, order_id). |
Live smoke pending: migracja na zywym XAMPP MySQL + manualny test wieloosobowy (autor vs inny user) — udokumentowane w STATE.md follow-ups.
Verification Results
$ C:/xampp/php/php.exe -l src/Modules/Orders/OrderNotesService.php
No syntax errors detected
$ C:/xampp/php/php.exe -l src/Modules/Orders/OrdersController.php
No syntax errors detected
$ C:/xampp/php/php.exe -l src/Modules/Orders/OrdersRepository.php
No syntax errors detected
$ C:/xampp/php/php.exe -l routes/web.php
No syntax errors detected
$ C:/xampp/php/php.exe -l resources/views/orders/show.php
No syntax errors detected
$ node --check public/assets/js/modules/order-notes.js
JS OK
$ npm run build:css
sass --style=compressed --no-source-map resources/scss/app.scss public/assets/css/app.css
(rebuilt successfully)
Accomplishments
- Reuse
order_noteszamiast osobnej tabeli: jedna tabela obsluguje 2 semantyki (note_type IN ('shoppro','allegro','message')= imported,note_type='user'= autorska).UNIQUE (order_id, source_note_id)nie blokuje user notes bo MySQL traktuje wiele NULL jako unique. - CRUD z autoryzacja DB-level: UPDATE/DELETE filtruja po
user_id = :user_idw SQL;rowCount=0rzuca 403 — eliminuje konieczność osobnego SELECT pre-check'a. - Badge widoczny od razu w liscie: subquery
user_notes_countekspozuje liczbe wpaginate(), badge wlozony wtoTableRow()obok numeru zamowienia (klik scrolluje do#notes).
Files Created/Modified
| File | Change | Purpose |
|---|---|---|
database/migrations/20260514_000116_extend_order_notes_user_authored.sql |
Created | ADD COLUMN user_id + author_name + FK → users + indeks (idempotentne) |
src/Modules/Orders/OrderNotesService.php |
Created | CRUD service nad order_notes (note_type='user') z autoryzacja po user_id |
resources/scss/modules/_order-notes.scss |
Created | .order-notes-badge, .order-user-notes, .order-event--user, .btn-link, .order-note-form |
public/assets/js/modules/order-notes.js |
Created | Vanilla JS: inline edit toggle + OrderProAlerts.confirm na delete (idempotent guard) |
src/Modules/Orders/OrdersController.php |
Modified | Dodano OrderNotesService jako nullable dep + storeNote/updateNote/deleteNote + badge w toTableRow() |
src/Modules/Orders/OrdersRepository.php |
Modified | userNotesCountSubquerySql() + kolumna user_notes_count w paginate; loadOrderNotes() zawezone do note_type <> 'user' |
routes/web.php |
Modified | 3 nowe routes (POST notes/store |
resources/views/orders/show.php |
Modified | Sekcja #notes rozbita na user-notes + form + imported-notes; per-note edit form (ukryty) |
resources/views/layouts/app.php |
Modified | <script> dla order-notes.js |
resources/lang/pl.php |
Modified | 9 nowych kluczy orders.details.notes_user_* + notes_imported_title |
resources/scss/app.scss |
Modified | @use "modules/order-notes" |
public/assets/css/app.css |
Modified | Rebuilt by npm run build:css |
Decisions Made
| Decision | Rationale | Impact |
|---|---|---|
Reuse order_notes z note_type='user' |
Jedna tabela = mniej obiektow DB, prosciej testowac, UNIQUE NULL nie koliduje | Importowane notatki ze zrodla nadal dzialaja niezmienione; w loadOrderNotes() dorzucony filtr note_type <> 'user' |
| Brak admin override (tylko autor edit/delete) | Brak systemu rol w aplikacji (grep is_admin|role= → 0 hits) |
Operator ktory dodal notatke moze ja modyfikowac; admin override odlozony do osobnej fazy gdy beda role |
Badge [N] w order_ref (NIE osobna kolumna) |
Minimalny footprint w tabeli, spojnie z risk-return-badge (przy buyer_name) |
Badge widoczny bez zmian w naglowkach tabeli /orders/list |
Body limit 2000 znakow (mb_strlen) |
TEXT moze przechowac wiecej, ale UX podpowiada krotkie notatki; spojnie z polem comment | Walidacja w OrderNotesService::sanitizeBody() — rzut InvalidArgumentException gdy przekroczenie |
| Migracja idempotentna z DDL no-op fallback | Decyzja z Phase 115/125 — SELECT 1 powoduje SQLSTATE 2014 przy PDO unbuffered |
Re-run migracji = no-op (ALTER TABLE COMMENT) bez bledu |
| Edit toggle (ukryty form per notatka) zamiast modala | Mniej UI ceremoniaiu, spojnie z istniejacym order-event layoutem |
JS prosty (show/hide pary js-order-note-body ↔ js-order-note-edit-form) |
Deviations from Plan
Summary
| Type | Count | Impact |
|---|---|---|
| Auto-fixed | 1 | Cosmetic naming |
| Scope additions | 0 | None |
| Deferred | 0 | None |
Total impact: Minimalne deviation — plan wykonany niemal 1:1.
Auto-fixed Issues
1. [Naming] formatOrderRow → toTableRow
- Found during: Task 2 (badge w controllerze)
- Issue: Plan referowal do nieistniejacej metody
formatOrderRow()wOrdersController - Fix: Edycja
toTableRow()(rzeczywista nazwa metody) — semantyka identyczna - Files:
src/Modules/Orders/OrdersController.php - Verification:
grep -n "public function|toTableRow"potwierdzilotoTableRowjako wlasciwa nazwa - Commit: N/A (jeden Task 2 commit obejmie wszystkie zmiany)
Deferred Items
None — plan executed exactly as written.
Issues Encountered
| Issue | Resolution |
|---|---|
Plan referowal formatOrderRow() (nieistniejacy) |
Sprawdzono rzeczywiste metody przez Grep "public function" — toTableRow jest poprawna nazwa. Patch zaaplikowany. |
Next Phase Readiness
Ready:
- Migracja czeka na operator (
php bin/migrate.php). - UI sekcji notatek + badge gotowe — manualny smoke test moze byc wykonany po migracji.
- Pattern
userNotesCountSubquerySql+ nullableOrderNotesServicewOrdersController— gotowe do reuse w przyszlych phasach (np. event automatyzacjinote.createdlub admin override).
Concerns:
- Bez systemu rol nie ma admin override — jezeli operator chce zeby kazdy user mogl edytowac/usuwac kazda notatke, trzeba zmienic warunek w
OrderNotesService::update()/delete()(usunacAND user_id = :user_id). - Brak filtra "ma notatki" / "nie ma notatek" w liscie zamowien — kandydat na rozszerzenie jezeli operator zechce.
Blockers:
- None — plan wdrozony, smoke test po stronie operatora.
Phase: 129-order-user-notes, Plan: 01 Completed: 2026-05-14