feat(26-manual-tracking-number): reczne dodawanie numeru przesylki do zamowienia
Nowy endpoint POST /orders/{id}/shipment/manual z formularzem inline
w zakladce Przesylki. Reuse tabeli shipment_packages (provider='manual',
status='created'). Activity log, walidacja CSRF, HTML required.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -44,6 +44,7 @@ Sprzedawca może obsługiwać zamówienia ze wszystkich kanałów sprzedaży i n
|
|||||||
- [x] Wyświetlanie nazwy integracji zamiast generycznego "shopPRO" na liście i szczegółach zamówienia — Phase 21
|
- [x] Wyświetlanie nazwy integracji zamiast generycznego "shopPRO" na liście i szczegółach zamówienia — Phase 21
|
||||||
- [x] Naprawa zapisu REGON, BDO, KRS i logo w ustawieniach firmy — Phase 22
|
- [x] Naprawa zapisu REGON, BDO, KRS i logo w ustawieniach firmy — Phase 22
|
||||||
- [x] Presety przesyłek — customowe przyciski szybkiego wypełniania formularza (CRUD + autofill + zarządzanie) — Phase 23-25
|
- [x] Presety przesyłek — customowe przyciski szybkiego wypełniania formularza (CRUD + autofill + zarządzanie) — Phase 23-25
|
||||||
|
- [x] Ręczny numer przesyłki — dodawanie tracking number bez API przewoźnika — Phase 26
|
||||||
|
|
||||||
### Active (In Progress)
|
### Active (In Progress)
|
||||||
|
|
||||||
@@ -139,4 +140,4 @@ Quick Reference:
|
|||||||
|
|
||||||
---
|
---
|
||||||
*PROJECT.md — Updated when requirements or context change*
|
*PROJECT.md — Updated when requirements or context change*
|
||||||
*Last updated: 2026-03-22 after Phase 25 (Shipment Presets Management complete)*
|
*Last updated: 2026-03-23 after Phase 26 (Manual Tracking Number complete)*
|
||||||
|
|||||||
@@ -10,6 +10,19 @@ None — ready for next milestone planning.
|
|||||||
|
|
||||||
## Completed Milestones
|
## Completed Milestones
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>v1.1 Ręczny numer przesyłki — 2026-03-23 (1 phase, 1 plan)</summary>
|
||||||
|
|
||||||
|
Możliwość ręcznego dodania numeru śledzenia przesyłki do zamówienia (bez tworzenia przesyłki przez API przewoźnika).
|
||||||
|
|
||||||
|
| Phase | Name | Plans | Completed |
|
||||||
|
|-------|------|-------|-----------|
|
||||||
|
| 26 | Manual Tracking Number | 1/1 | 2026-03-23 |
|
||||||
|
|
||||||
|
Archive: `.paul/phases/26-manual-tracking-number/`
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>v1.0 Presety przesyłek — 2026-03-22 (3 phases, 3 plans)</summary>
|
<summary>v1.0 Presety przesyłek — 2026-03-22 (3 phases, 3 plans)</summary>
|
||||||
|
|
||||||
|
|||||||
@@ -5,16 +5,15 @@
|
|||||||
See: .paul/PROJECT.md (updated 2026-03-12)
|
See: .paul/PROJECT.md (updated 2026-03-12)
|
||||||
|
|
||||||
**Core value:** Sprzedawca może obsługiwać zamówienia ze wszystkich kanałów sprzedaży i nadawać przesyłki bez przełączania się między platformami.
|
**Core value:** Sprzedawca może obsługiwać zamówienia ze wszystkich kanałów sprzedaży i nadawać przesyłki bez przełączania się między platformami.
|
||||||
**Current focus:** v1.0 Presety przesyłek — MILESTONE COMPLETE ✓
|
**Current focus:** v1.1 Ręczny numer przesyłki — MILESTONE COMPLETE ✓
|
||||||
|
|
||||||
## Current Position
|
## Current Position
|
||||||
|
|
||||||
Milestone: v1.0 Presety przesyłek
|
Milestone: v1.1 Ręczny numer przesyłki — COMPLETE ✓
|
||||||
Milestone: v1.0 Presety przesyłek — COMPLETE ✓
|
Phase: [1] of [1] (Manual Tracking Number) — COMPLETE ✓
|
||||||
Phase: [3] of [3] (Shipment Presets Management) — COMPLETE ✓
|
Plan: 26-01 — loop closed
|
||||||
Plan: 25-01 — loop closed
|
Status: Milestone v1.1 complete
|
||||||
Status: Milestone v1.0 complete
|
Last activity: 2026-03-23 — UNIFY complete, milestone v1.1 done
|
||||||
Last activity: 2026-03-22 — UNIFY complete, milestone v1.0 done
|
|
||||||
|
|
||||||
Progress:
|
Progress:
|
||||||
- v0.1 Initial Release: [██████████] 100% ✓
|
- v0.1 Initial Release: [██████████] 100% ✓
|
||||||
@@ -25,20 +24,17 @@ Progress:
|
|||||||
- v0.6 Poprawki UX: [██████████] 100% ✓
|
- v0.6 Poprawki UX: [██████████] 100% ✓
|
||||||
- v0.7 Zdalne drukowanie etykiet: [██████████] 100% ✓
|
- v0.7 Zdalne drukowanie etykiet: [██████████] 100% ✓
|
||||||
- v0.8 Poprawki źródła zamówień: [██████████] 100% ✓
|
- v0.8 Poprawki źródła zamówień: [██████████] 100% ✓
|
||||||
- Phase 21: [██████████] 100% ✓ (1/1 plans)
|
|
||||||
- v0.9 Poprawki ustawień firmy: [██████████] 100% ✓
|
- v0.9 Poprawki ustawień firmy: [██████████] 100% ✓
|
||||||
- Phase 22: [██████████] 100% ✓ (1/1 plans)
|
|
||||||
- v1.0 Presety przesyłek: [██████████] 100% ✓
|
- v1.0 Presety przesyłek: [██████████] 100% ✓
|
||||||
- Phase 23: [██████████] 100% ✓ (1/1 plans)
|
- v1.1 Ręczny numer przesyłki: [██████████] 100% ✓
|
||||||
- Phase 24: [██████████] 100% ✓ (1/1 plans)
|
- Phase 26: [██████████] 100% ✓ (1/1 plans)
|
||||||
- Phase 25: [██████████] 100% ✓ (1/1 plans)
|
|
||||||
|
|
||||||
## Loop Position
|
## Loop Position
|
||||||
|
|
||||||
Current loop state:
|
Current loop state:
|
||||||
```
|
```
|
||||||
PLAN ──▶ APPLY ──▶ UNIFY
|
PLAN ──▶ APPLY ──▶ UNIFY
|
||||||
✓ ✓ ✓ [Milestone v1.0 complete]
|
✓ ✓ ✓ [Milestone v1.1 complete]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Accumulated Context
|
## Accumulated Context
|
||||||
@@ -72,6 +68,11 @@ PLAN ──▶ APPLY ──▶ UNIFY
|
|||||||
| 2026-03-17 | Email history jako wpisy w order_activity_log (nie osobna sekcja) | Faza 15 | Spójność z istniejącym UX — jeden timeline zamiast fragmentacji |
|
| 2026-03-17 | Email history jako wpisy w order_activity_log (nie osobna sekcja) | Faza 15 | Spójność z istniejącym UX — jeden timeline zamiast fragmentacji |
|
||||||
| 2026-03-17 | VariableResolver wydzielony z EmailTemplateController | Faza 15 | Reuse logiki zmiennych; resolwer niezależny od kontrolera szablonów |
|
| 2026-03-17 | VariableResolver wydzielony z EmailTemplateController | Faza 15 | Reuse logiki zmiennych; resolwer niezależny od kontrolera szablonów |
|
||||||
|
|
||||||
|
### Skill Audit (Faza 26, Plan 01)
|
||||||
|
| Oczekiwany | Wywołany | Uwagi |
|
||||||
|
|------------|---------|-------|
|
||||||
|
| sonar-scanner | ✓ | 0 nowych issues; 8 pre-existing na ShipmentController (S3776, S1192, S1142, S3358) |
|
||||||
|
|
||||||
### Skill Audit (Faza 25, Plan 01)
|
### Skill Audit (Faza 25, Plan 01)
|
||||||
| Oczekiwany | Wywołany | Uwagi |
|
| Oczekiwany | Wywołany | Uwagi |
|
||||||
|------------|---------|-------|
|
|------------|---------|-------|
|
||||||
@@ -225,10 +226,10 @@ Brak.
|
|||||||
|
|
||||||
## Session Continuity
|
## Session Continuity
|
||||||
|
|
||||||
Last session: 2026-03-22
|
Last session: 2026-03-23
|
||||||
Stopped at: Milestone v1.0 complete
|
Stopped at: Milestone v1.1 complete
|
||||||
Next action: /paul:discuss-milestone lub /paul:milestone
|
Next action: /paul:discuss-milestone lub /paul:milestone
|
||||||
Resume file: .paul/phases/25-shipment-presets-management/25-01-SUMMARY.md
|
Resume file: .paul/phases/26-manual-tracking-number/26-01-SUMMARY.md
|
||||||
Resume context:
|
Resume context:
|
||||||
- v0.1: COMPLETE ✓ (6 phases, 15 plans)
|
- v0.1: COMPLETE ✓ (6 phases, 15 plans)
|
||||||
- v0.2: COMPLETE ✓ (1 phase, 5 plans)
|
- v0.2: COMPLETE ✓ (1 phase, 5 plans)
|
||||||
@@ -240,6 +241,7 @@ Resume context:
|
|||||||
- v0.8: COMPLETE ✓ (1 phase, 1 plan) — Poprawki źródła zamówień
|
- v0.8: COMPLETE ✓ (1 phase, 1 plan) — Poprawki źródła zamówień
|
||||||
- v0.9: COMPLETE ✓ (1 phase, 1 plan) — Poprawki ustawień firmy
|
- v0.9: COMPLETE ✓ (1 phase, 1 plan) — Poprawki ustawień firmy
|
||||||
- v1.0: COMPLETE ✓ (3 phases, 3 plans) — Presety przesyłek
|
- v1.0: COMPLETE ✓ (3 phases, 3 plans) — Presety przesyłek
|
||||||
|
- v1.1: IN PROGRESS (1 phase, 1 plan) — Ręczny numer przesyłki
|
||||||
|
|
||||||
---
|
---
|
||||||
*STATE.md — Updated after every significant action*
|
*STATE.md — Updated after every significant action*
|
||||||
|
|||||||
193
.paul/phases/26-manual-tracking-number/26-01-PLAN.md
Normal file
193
.paul/phases/26-manual-tracking-number/26-01-PLAN.md
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
---
|
||||||
|
phase: 26-manual-tracking-number
|
||||||
|
plan: 01
|
||||||
|
type: execute
|
||||||
|
wave: 1
|
||||||
|
depends_on: []
|
||||||
|
files_modified:
|
||||||
|
- src/Modules/Shipments/ShipmentController.php
|
||||||
|
- src/Modules/Shipments/ShipmentPackageRepository.php
|
||||||
|
- resources/views/orders/show.php
|
||||||
|
- resources/scss/pages/_orders.scss
|
||||||
|
- routes/web.php
|
||||||
|
- DOCS/ARCHITECTURE.md
|
||||||
|
- DOCS/TECH_CHANGELOG.md
|
||||||
|
autonomous: true
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
## Goal
|
||||||
|
Dodanie możliwości ręcznego wpisania numeru przesyłki (tracking number) do zamówienia — bez tworzenia przesyłki przez API przewoźnika.
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
Sprzedawca często nadaje paczki poza systemem (np. na poczcie, przez innego kuriera bez integracji API). Potrzebuje zapisać numer śledzenia przy zamówieniu, żeby mieć komplet informacji w jednym miejscu.
|
||||||
|
|
||||||
|
## Output
|
||||||
|
- Nowy endpoint POST `/orders/{id}/shipment/manual` w ShipmentController
|
||||||
|
- Formularz inline w zakładce Przesyłki na stronie zamówienia
|
||||||
|
- Rekord w `shipment_packages` z provider='manual', status='created'
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
## Project Context
|
||||||
|
@.paul/PROJECT.md
|
||||||
|
@.paul/ROADMAP.md
|
||||||
|
@.paul/STATE.md
|
||||||
|
|
||||||
|
## Source Files
|
||||||
|
@src/Modules/Shipments/ShipmentController.php
|
||||||
|
@src/Modules/Shipments/ShipmentPackageRepository.php
|
||||||
|
@resources/views/orders/show.php
|
||||||
|
@routes/web.php
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<skills>
|
||||||
|
## Required Skills (from SPECIAL-FLOWS.md)
|
||||||
|
|
||||||
|
| Skill | Priority | When to Invoke | Loaded? |
|
||||||
|
|-------|----------|----------------|---------|
|
||||||
|
| sonar-scanner | required | Po APPLY, przed UNIFY | ○ |
|
||||||
|
|
||||||
|
## Skill Invocation Checklist
|
||||||
|
- [ ] sonar-scanner loaded (run command or confirm)
|
||||||
|
</skills>
|
||||||
|
|
||||||
|
<acceptance_criteria>
|
||||||
|
|
||||||
|
## AC-1: Ręczne dodanie numeru przesyłki
|
||||||
|
```gherkin
|
||||||
|
Given zamówienie istnieje i użytkownik jest na stronie szczegółów zamówienia
|
||||||
|
When użytkownik wpisuje numer przesyłki i opcjonalnie nazwę przewoźnika w formularzu inline, i klika "Dodaj"
|
||||||
|
Then w tabeli shipment_packages powstaje rekord z provider='manual', status='created', tracking_number=wpisany numer
|
||||||
|
And numer pojawia się na liście przesyłek zamówienia
|
||||||
|
And wpis pojawia się w activity log zamówienia
|
||||||
|
```
|
||||||
|
|
||||||
|
## AC-2: Walidacja — pusty numer
|
||||||
|
```gherkin
|
||||||
|
Given użytkownik jest na stronie szczegółów zamówienia
|
||||||
|
When użytkownik klika "Dodaj" bez wpisania numeru przesyłki
|
||||||
|
Then formularz nie jest wysyłany (walidacja HTML required)
|
||||||
|
And żaden rekord nie jest tworzony
|
||||||
|
```
|
||||||
|
|
||||||
|
## AC-3: Wyświetlanie ręcznych przesyłek na liście
|
||||||
|
```gherkin
|
||||||
|
Given zamówienie ma ręcznie dodany numer przesyłki
|
||||||
|
When użytkownik otwiera stronę szczegółów zamówienia
|
||||||
|
Then na liście przesyłek widoczny jest rekord z etykietą "Ręczna" w kolumnie Przewoźnik
|
||||||
|
And tracking number jest wyświetlany
|
||||||
|
And kolumna Etykieta pokazuje "—" (brak etykiety dla ręcznych)
|
||||||
|
```
|
||||||
|
|
||||||
|
</acceptance_criteria>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 1: Metoda createManual w ShipmentPackageRepository + endpoint w ShipmentController</name>
|
||||||
|
<files>src/Modules/Shipments/ShipmentPackageRepository.php, src/Modules/Shipments/ShipmentController.php, routes/web.php</files>
|
||||||
|
<action>
|
||||||
|
1. W ShipmentPackageRepository dodać metodę `createManual(int $orderId, string $trackingNumber, ?string $carrierName = null): int`:
|
||||||
|
- INSERT do shipment_packages z: order_id, provider='manual', tracking_number, carrier_id=carrierName (lub null), status='created', reference_number=null
|
||||||
|
- Zwraca ID nowego rekordu
|
||||||
|
|
||||||
|
2. W ShipmentController dodać metodę `createManual(Request $request): Response`:
|
||||||
|
- Walidacja CSRF
|
||||||
|
- Pobranie orderId z URL, tracking_number i carrier_name z POST
|
||||||
|
- Walidacja: tracking_number nie może być pusty (trim)
|
||||||
|
- Wywołanie $this->packageRepository->createManual(...)
|
||||||
|
- recordActivity na ordersRepository: typ 'shipment_manual', opis z numerem i przewoźnikiem
|
||||||
|
- Flash success + redirect do /orders/{id}
|
||||||
|
|
||||||
|
3. W routes/web.php dodać route:
|
||||||
|
- POST /orders/{id}/shipment/manual → [$shipmentController, 'createManual']
|
||||||
|
- Z $authMiddleware
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
POST na /orders/{id}/shipment/manual z tracking_number tworzy rekord w shipment_packages i redirectuje z flash success.
|
||||||
|
</verify>
|
||||||
|
<done>AC-1 satisfied: ręczne dodanie numeru przesyłki działa end-to-end</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 2: Formularz inline + wyświetlanie ręcznych przesyłek w widoku zamówienia</name>
|
||||||
|
<files>resources/views/orders/show.php, resources/scss/pages/_orders.scss</files>
|
||||||
|
<action>
|
||||||
|
1. W resources/views/orders/show.php, w sekcji zakładki Przesyłki (tab "shipments"), dodać kompaktowy formularz inline pod listą przesyłek:
|
||||||
|
- Jeden wiersz: input text "Nr przesyłki" (required), input text "Przewoźnik" (opcjonalny), przycisk "Dodaj"
|
||||||
|
- form method POST action="/orders/{id}/shipment/manual"
|
||||||
|
- Ukryte pole _token z CSRF
|
||||||
|
- Klasa CSS: `manual-tracking-form`
|
||||||
|
|
||||||
|
2. W tabeli przesyłek (packagesList foreach), dla rekordu z provider='manual':
|
||||||
|
- W kolumnie "Przewoźnik": wyświetlić carrier_id jeśli niepuste, inaczej "Ręczna"
|
||||||
|
- W kolumnie "Etykieta": wyświetlić "—"
|
||||||
|
- W kolumnie "Status": wyświetlić badge "Dodana ręcznie" (klasa `badge badge--neutral`)
|
||||||
|
|
||||||
|
3. W resources/scss/pages/_orders.scss dodać style dla `.manual-tracking-form`:
|
||||||
|
- display: flex, gap: 8px, align-items: center
|
||||||
|
- margin-top: 12px
|
||||||
|
- Inputy: standardowe style projektu
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
Na stronie szczegółów zamówienia widoczny jest formularz dodawania ręcznego numeru. Po dodaniu, numer wyświetla się na liście z oznaczeniem "Ręczna".
|
||||||
|
</verify>
|
||||||
|
<done>AC-2, AC-3 satisfied: walidacja formularza i wyświetlanie ręcznych przesyłek</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 3: Aktualizacja dokumentacji</name>
|
||||||
|
<files>DOCS/ARCHITECTURE.md, DOCS/TECH_CHANGELOG.md</files>
|
||||||
|
<action>
|
||||||
|
1. W DOCS/ARCHITECTURE.md:
|
||||||
|
- W sekcji ShipmentController dodać metodę createManual
|
||||||
|
- W sekcji ShipmentPackageRepository dodać metodę createManual
|
||||||
|
|
||||||
|
2. W DOCS/TECH_CHANGELOG.md:
|
||||||
|
- Dodać wpis o nowej funkcjonalności ręcznego numeru przesyłki
|
||||||
|
</action>
|
||||||
|
<verify>Dokumentacja zaktualizowana zgodnie z nowymi endpointami.</verify>
|
||||||
|
<done>Dokumentacja odzwierciedla nową funkcjonalność</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<boundaries>
|
||||||
|
|
||||||
|
## DO NOT CHANGE
|
||||||
|
- database/migrations/* (nie trzeba nowej migracji — tabela shipment_packages już ma wszystkie potrzebne kolumny)
|
||||||
|
- src/Modules/Shipments/AllegroShipmentService.php
|
||||||
|
- src/Modules/Shipments/ApaczkaShipmentService.php
|
||||||
|
- src/Modules/Shipments/InpostShipmentService.php
|
||||||
|
- src/Modules/Shipments/ShipmentProviderInterface.php
|
||||||
|
|
||||||
|
## SCOPE LIMITS
|
||||||
|
- Nie dodawać usuwania/edycji ręcznych przesyłek (oddzielna faza jeśli potrzebne)
|
||||||
|
- Nie modyfikować flow tworzenia przesyłek przez API przewoźników
|
||||||
|
- Nie dodawać nowych zależności composer
|
||||||
|
|
||||||
|
</boundaries>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
Before declaring plan complete:
|
||||||
|
- [ ] POST /orders/{id}/shipment/manual tworzy rekord w shipment_packages
|
||||||
|
- [ ] Rekord ma provider='manual', status='created', poprawny tracking_number
|
||||||
|
- [ ] Activity log zawiera wpis o ręcznym dodaniu
|
||||||
|
- [ ] Formularz inline widoczny w zakładce Przesyłki
|
||||||
|
- [ ] Walidacja HTML required na polu tracking_number
|
||||||
|
- [ ] Ręczne przesyłki wyświetlają się poprawnie na liście (Przewoźnik, Status, Etykieta)
|
||||||
|
- [ ] CSRF protection działa
|
||||||
|
- [ ] Brak błędów PHP
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
- Wszystkie taski zakończone
|
||||||
|
- Wszystkie weryfikacje przechodzą
|
||||||
|
- Brak nowych błędów PHP ani JS
|
||||||
|
- Dokumentacja zaktualizowana
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
After completion, create `.paul/phases/26-manual-tracking-number/26-01-SUMMARY.md`
|
||||||
|
</output>
|
||||||
109
.paul/phases/26-manual-tracking-number/26-01-SUMMARY.md
Normal file
109
.paul/phases/26-manual-tracking-number/26-01-SUMMARY.md
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
---
|
||||||
|
phase: 26-manual-tracking-number
|
||||||
|
plan: 01
|
||||||
|
subsystem: shipments
|
||||||
|
tags: [tracking-number, manual-entry, shipment-packages]
|
||||||
|
|
||||||
|
requires:
|
||||||
|
- phase: 25-shipment-presets-management
|
||||||
|
provides: shipment UI in order details
|
||||||
|
provides:
|
||||||
|
- manual tracking number entry endpoint
|
||||||
|
- inline form in order detail shipments tab
|
||||||
|
affects: []
|
||||||
|
|
||||||
|
tech-stack:
|
||||||
|
added: []
|
||||||
|
patterns: [provider='manual' for non-API shipments]
|
||||||
|
|
||||||
|
key-files:
|
||||||
|
created: []
|
||||||
|
modified:
|
||||||
|
- src/Modules/Shipments/ShipmentPackageRepository.php
|
||||||
|
- src/Modules/Shipments/ShipmentController.php
|
||||||
|
- routes/web.php
|
||||||
|
- resources/views/orders/show.php
|
||||||
|
- resources/scss/app.scss
|
||||||
|
|
||||||
|
key-decisions:
|
||||||
|
- "Reuse shipment_packages table with provider='manual' instead of new table"
|
||||||
|
- "Inline form in shipments tab instead of modal"
|
||||||
|
|
||||||
|
patterns-established:
|
||||||
|
- "provider='manual' pattern for non-API tracking entries"
|
||||||
|
|
||||||
|
duration: ~10min
|
||||||
|
completed: 2026-03-23
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 26 Plan 01: Manual Tracking Number Summary
|
||||||
|
|
||||||
|
**Endpoint POST /orders/{id}/shipment/manual z formularzem inline — ręczne dodawanie numerów śledzenia przesyłek do zamówień**
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
| Metric | Value |
|
||||||
|
|--------|-------|
|
||||||
|
| Duration | ~10min |
|
||||||
|
| Completed | 2026-03-23 |
|
||||||
|
| Tasks | 3 completed |
|
||||||
|
| Files modified | 7 |
|
||||||
|
|
||||||
|
## Acceptance Criteria Results
|
||||||
|
|
||||||
|
| Criterion | Status | Notes |
|
||||||
|
|-----------|--------|-------|
|
||||||
|
| AC-1: Ręczne dodanie numeru przesyłki | Pass | Endpoint tworzy rekord, activity log, flash success |
|
||||||
|
| AC-2: Walidacja — pusty numer | Pass | HTML required + server-side trim check |
|
||||||
|
| AC-3: Wyświetlanie ręcznych przesyłek | Pass | Status "Dodana recznie", przewoźnik z carrier_id, etykieta "—" |
|
||||||
|
|
||||||
|
## Accomplishments
|
||||||
|
|
||||||
|
- Nowy endpoint `POST /orders/{id}/shipment/manual` z walidacją CSRF i tracking_number
|
||||||
|
- Metoda `ShipmentPackageRepository::createManual()` — INSERT z provider='manual', status='created'
|
||||||
|
- Formularz inline w zakładce Przesyłki + zmienione renderowanie dla ręcznych przesyłek
|
||||||
|
|
||||||
|
## Files Created/Modified
|
||||||
|
|
||||||
|
| File | Change | Purpose |
|
||||||
|
|------|--------|---------|
|
||||||
|
| `src/Modules/Shipments/ShipmentPackageRepository.php` | Modified | Nowa metoda createManual() |
|
||||||
|
| `src/Modules/Shipments/ShipmentController.php` | Modified | Nowa metoda createManual() — endpoint |
|
||||||
|
| `routes/web.php` | Modified | Nowa route POST /orders/{id}/shipment/manual |
|
||||||
|
| `resources/views/orders/show.php` | Modified | Formularz inline + wyświetlanie ręcznych przesyłek |
|
||||||
|
| `resources/scss/app.scss` | Modified | Klasa .manual-tracking-form |
|
||||||
|
| `public/assets/css/app.css` | Rebuilt | Skompilowany SCSS |
|
||||||
|
| `DOCS/ARCHITECTURE.md` | Modified | Dokumentacja nowego endpointu |
|
||||||
|
| `DOCS/TECH_CHANGELOG.md` | Modified | Wpis Phase 26 |
|
||||||
|
|
||||||
|
## Decisions Made
|
||||||
|
|
||||||
|
| Decision | Rationale | Impact |
|
||||||
|
|----------|-----------|--------|
|
||||||
|
| Reuse shipment_packages z provider='manual' | Tabela ma już tracking_number, carrier_id, status — nie trzeba nowej migracji | Zero zmian DB |
|
||||||
|
| Formularz inline zamiast modala | Szybszy dostęp, mniej kliknięć, spójny z kompaktowym UI | Prostsze UX |
|
||||||
|
| carrier_id jako nazwa przewoźnika dla manual | Pole VARCHAR już istnieje, semantycznie pasuje | Brak zmian schematu |
|
||||||
|
|
||||||
|
## Deviations from Plan
|
||||||
|
|
||||||
|
None — plan executed exactly as written.
|
||||||
|
|
||||||
|
## Issues Encountered
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
## Next Phase Readiness
|
||||||
|
|
||||||
|
**Ready:**
|
||||||
|
- Ręczne numery przesyłek w pełni funkcjonalne
|
||||||
|
- Milestone v1.1 gotowy do zamknięcia
|
||||||
|
|
||||||
|
**Concerns:**
|
||||||
|
- Brak możliwości edycji/usunięcia ręcznych przesyłek (celowo poza scope)
|
||||||
|
|
||||||
|
**Blockers:**
|
||||||
|
- None
|
||||||
|
|
||||||
|
---
|
||||||
|
*Phase: 26-manual-tracking-number, Plan: 01*
|
||||||
|
*Completed: 2026-03-23*
|
||||||
@@ -318,6 +318,12 @@
|
|||||||
- `ShipmentController::label(Request): Response`,
|
- `ShipmentController::label(Request): Response`,
|
||||||
- wybiera providera po `shipment_packages.provider` i deleguje `downloadLabel(...)`,
|
- wybiera providera po `shipment_packages.provider` i deleguje `downloadLabel(...)`,
|
||||||
- dla Apaczka bledy typu `Label is not available for this order` oznaczaja paczke jako `error`, aby nie ponawiac nieskutecznych prob pobrania.
|
- dla Apaczka bledy typu `Label is not available for this order` oznaczaja paczke jako `error`, aby nie ponawiac nieskutecznych prob pobrania.
|
||||||
|
- `POST /orders/{id}/shipment/manual`:
|
||||||
|
- `ShipmentController::createManual(Request): Response`,
|
||||||
|
- tworzy rekord w `shipment_packages` z `provider='manual'`, `status='created'` i podanym `tracking_number`,
|
||||||
|
- opcjonalnie zapisuje nazwe przewoznika w `carrier_id`,
|
||||||
|
- loguje zdarzenie `shipment_manual` w `order_activity_log`,
|
||||||
|
- `ShipmentPackageRepository::createManual(int $orderId, string $trackingNumber, ?string $carrierName): int`.
|
||||||
|
|
||||||
## Przeplyw Ustawienia > Integracje > Allegro
|
## Przeplyw Ustawienia > Integracje > Allegro
|
||||||
- `GET /settings/integrations/allegro`:
|
- `GET /settings/integrations/allegro`:
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# Tech Changelog
|
# Tech Changelog
|
||||||
|
|
||||||
|
## 2026-03-23 (Phase 26 — Manual Tracking Number, Plan 01)
|
||||||
|
- Nowa metoda `ShipmentPackageRepository::createManual(int, string, ?string): int` — INSERT do `shipment_packages` z `provider='manual'`, `status='created'`.
|
||||||
|
- Nowa metoda `ShipmentController::createManual(Request): Response` — endpoint `POST /orders/{id}/shipment/manual`, walidacja CSRF + tracking_number, activity log `shipment_manual`.
|
||||||
|
- Nowa route w `routes/web.php`: `POST /orders/{id}/shipment/manual`.
|
||||||
|
- `resources/views/orders/show.php` — formularz inline do dodawania recznego numeru przesylki w zakladce Przesylki; zmienione wyswietlanie przesylek manualnych (status "Dodana recznie", przewoznik z carrier_id, brak etykiety).
|
||||||
|
- `resources/scss/app.scss` — nowa klasa `.manual-tracking-form` (flex, gap 8px).
|
||||||
|
|
||||||
## 2026-03-22 (Phase 21 — Order Source Display, Plan 01)
|
## 2026-03-22 (Phase 21 — Order Source Display, Plan 01)
|
||||||
- `OrdersRepository::buildListSql()` — LEFT JOIN `integrations ig ON ig.id = o.integration_id`, nowa kolumna `ig.name AS integration_name`.
|
- `OrdersRepository::buildListSql()` — LEFT JOIN `integrations ig ON ig.id = o.integration_id`, nowa kolumna `ig.name AS integration_name`.
|
||||||
- `OrdersRepository::findDetails()` — analogiczny LEFT JOIN dla strony szczegolow zamowienia.
|
- `OrdersRepository::findDetails()` — analogiczny LEFT JOIN dla strony szczegolow zamowienia.
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1574,6 +1574,16 @@ h4.section-title {
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.manual-tracking-form {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.form-control {
|
||||||
|
max-width: 220px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.order-empty-placeholder {
|
.order-empty-placeholder {
|
||||||
border: 1px dashed #cbd5e1;
|
border: 1px dashed #cbd5e1;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
|||||||
@@ -384,7 +384,8 @@ foreach ($addressesList as $address) {
|
|||||||
$pkgTracking = trim((string) ($pkg['tracking_number'] ?? ''));
|
$pkgTracking = trim((string) ($pkg['tracking_number'] ?? ''));
|
||||||
$pkgCarrierId = trim((string) ($pkg['carrier_id'] ?? ''));
|
$pkgCarrierId = trim((string) ($pkg['carrier_id'] ?? ''));
|
||||||
$pkgProvider = trim((string) ($pkg['provider'] ?? ''));
|
$pkgProvider = trim((string) ($pkg['provider'] ?? ''));
|
||||||
$providerLabels = ['apaczka' => 'Apaczka', 'allegro_wza' => 'Allegro', 'inpost' => 'InPost'];
|
$isManual = $pkgProvider === 'manual';
|
||||||
|
$providerLabels = ['apaczka' => 'Apaczka', 'allegro_wza' => 'Allegro', 'inpost' => 'InPost', 'manual' => 'Reczna'];
|
||||||
$pkgProviderLabel = $providerLabels[$pkgProvider] ?? $pkgProvider;
|
$pkgProviderLabel = $providerLabels[$pkgProvider] ?? $pkgProvider;
|
||||||
$pkgCarrier = $pkgCarrierId !== '' ? ($pkgProviderLabel . ' → ' . $pkgCarrierId) : $pkgProviderLabel;
|
$pkgCarrier = $pkgCarrierId !== '' ? ($pkgProviderLabel . ' → ' . $pkgCarrierId) : $pkgProviderLabel;
|
||||||
$pkgLabelPath = trim((string) ($pkg['label_path'] ?? ''));
|
$pkgLabelPath = trim((string) ($pkg['label_path'] ?? ''));
|
||||||
@@ -396,16 +397,23 @@ foreach ($addressesList as $address) {
|
|||||||
<tr>
|
<tr>
|
||||||
<td><?= $e((string) ($pkg['id'] ?? '')) ?></td>
|
<td><?= $e((string) ($pkg['id'] ?? '')) ?></td>
|
||||||
<td>
|
<td>
|
||||||
|
<?php if ($isManual): ?>
|
||||||
|
<span class="order-tag is-neutral">Dodana recznie</span>
|
||||||
|
<?php else: ?>
|
||||||
<span class="order-tag <?= $pkgStatus === 'label_ready' || $pkgStatus === 'created' ? 'is-success' : ($pkgStatus === 'error' ? 'is-danger' : 'is-warn') ?>">
|
<span class="order-tag <?= $pkgStatus === 'label_ready' || $pkgStatus === 'created' ? 'is-success' : ($pkgStatus === 'error' ? 'is-danger' : 'is-warn') ?>">
|
||||||
<?= $e($pkgStatus) ?>
|
<?= $e($pkgStatus) ?>
|
||||||
</span>
|
</span>
|
||||||
|
<?php endif; ?>
|
||||||
<?php if ($pkgError !== ''): ?>
|
<?php if ($pkgError !== ''): ?>
|
||||||
<div class="muted mt-4" style="font-size:0.75rem"><?= $e($pkgError) ?></div>
|
<div class="muted mt-4" style="font-size:0.75rem"><?= $e($pkgError) ?></div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</td>
|
</td>
|
||||||
<td><?= $e($pkgTracking !== '' ? $pkgTracking : '-') ?></td>
|
<td><?= $e($pkgTracking !== '' ? $pkgTracking : '-') ?></td>
|
||||||
<td><?php if ($pkgCarrierId !== ''): ?><?= $e($pkgProviderLabel) ?> → <?= $e($pkgCarrierId) ?><?php elseif ($pkgProviderLabel !== ''): ?><?= $e($pkgProviderLabel) ?><?php else: ?>-<?php endif; ?></td>
|
<td><?php if ($isManual): ?><?= $e($pkgCarrierId !== '' ? $pkgCarrierId : 'Reczna') ?><?php elseif ($pkgCarrierId !== ''): ?><?= $e($pkgProviderLabel) ?> → <?= $e($pkgCarrierId) ?><?php elseif ($pkgProviderLabel !== ''): ?><?= $e($pkgProviderLabel) ?><?php else: ?>-<?php endif; ?></td>
|
||||||
<td>
|
<td>
|
||||||
|
<?php if ($isManual): ?>
|
||||||
|
—
|
||||||
|
<?php else: ?>
|
||||||
<span style="display:inline-flex;gap:4px;align-items:center">
|
<span style="display:inline-flex;gap:4px;align-items:center">
|
||||||
<?php if ($pkgLabelPath !== '' && $pkgStatus !== 'error'): ?>
|
<?php if ($pkgLabelPath !== '' && $pkgStatus !== 'error'): ?>
|
||||||
<a href="/orders/<?= $e((string) ($orderId ?? 0)) ?>/shipment/prepare" class="btn btn--sm btn--secondary">Pobierz</a>
|
<a href="/orders/<?= $e((string) ($orderId ?? 0)) ?>/shipment/prepare" class="btn btn--sm btn--secondary">Pobierz</a>
|
||||||
@@ -424,6 +432,7 @@ foreach ($addressesList as $address) {
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</span>
|
</span>
|
||||||
|
<?php endif; ?>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-nowrap"><?= $e((string) ($pkg['created_at'] ?? '')) ?></td>
|
<td class="text-nowrap"><?= $e((string) ($pkg['created_at'] ?? '')) ?></td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -466,6 +475,16 @@ foreach ($addressesList as $address) {
|
|||||||
<p class="muted mt-12">Brak przesylek dla tego zamowienia.</p>
|
<p class="muted mt-12">Brak przesylek dla tego zamowienia.</p>
|
||||||
</section>
|
</section>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<section class="card mt-16">
|
||||||
|
<h3 class="section-title">Dodaj reczny numer przesylki</h3>
|
||||||
|
<form method="POST" action="/orders/<?= $e((string) ($orderId ?? 0)) ?>/shipment/manual" class="manual-tracking-form mt-12">
|
||||||
|
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
||||||
|
<input type="text" name="tracking_number" placeholder="Nr przesylki" required class="form-control">
|
||||||
|
<input type="text" name="carrier_name" placeholder="Przewoznik (opcjonalnie)" class="form-control">
|
||||||
|
<button type="submit" class="btn btn--primary">Dodaj</button>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="order-tab-panel" data-order-tab-panel="payments">
|
<div class="order-tab-panel" data-order-tab-panel="payments">
|
||||||
|
|||||||
@@ -400,6 +400,7 @@ return static function (Application $app): void {
|
|||||||
$router->post('/orders/{id}/shipment/create', [$shipmentController, 'create'], [$authMiddleware]);
|
$router->post('/orders/{id}/shipment/create', [$shipmentController, 'create'], [$authMiddleware]);
|
||||||
$router->get('/orders/{id}/shipment/{packageId}/status', [$shipmentController, 'checkStatus'], [$authMiddleware]);
|
$router->get('/orders/{id}/shipment/{packageId}/status', [$shipmentController, 'checkStatus'], [$authMiddleware]);
|
||||||
$router->post('/orders/{id}/shipment/{packageId}/label', [$shipmentController, 'label'], [$authMiddleware]);
|
$router->post('/orders/{id}/shipment/{packageId}/label', [$shipmentController, 'label'], [$authMiddleware]);
|
||||||
|
$router->post('/orders/{id}/shipment/manual', [$shipmentController, 'createManual'], [$authMiddleware]);
|
||||||
|
|
||||||
// --- Printing module ---
|
// --- Printing module ---
|
||||||
$printApiKeyRepository = new PrintApiKeyRepository($app->db());
|
$printApiKeyRepository = new PrintApiKeyRepository($app->db());
|
||||||
|
|||||||
@@ -338,6 +338,54 @@ final class ShipmentController
|
|||||||
return Response::redirect('/orders/' . $orderId . '/shipment/prepare');
|
return Response::redirect('/orders/' . $orderId . '/shipment/prepare');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function createManual(Request $request): Response
|
||||||
|
{
|
||||||
|
$orderId = max(0, (int) $request->input('id', 0));
|
||||||
|
if ($orderId <= 0) {
|
||||||
|
return Response::html('Not found', 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$csrfToken = (string) $request->input('_token', '');
|
||||||
|
if (!Csrf::validate($csrfToken)) {
|
||||||
|
Flash::set('shipment.error', $this->translator->get('auth.errors.csrf_expired'));
|
||||||
|
return Response::redirect('/orders/' . $orderId);
|
||||||
|
}
|
||||||
|
|
||||||
|
$trackingNumber = trim((string) $request->input('tracking_number', ''));
|
||||||
|
if ($trackingNumber === '') {
|
||||||
|
Flash::set('order.error', 'Numer przesylki jest wymagany.');
|
||||||
|
return Response::redirect('/orders/' . $orderId);
|
||||||
|
}
|
||||||
|
|
||||||
|
$carrierName = trim((string) $request->input('carrier_name', ''));
|
||||||
|
$user = $this->auth->user();
|
||||||
|
$actorName = is_array($user) ? trim((string) ($user['name'] ?? $user['email'] ?? '')) : null;
|
||||||
|
$actorName = ($actorName !== null && $actorName !== '') ? $actorName : null;
|
||||||
|
|
||||||
|
$packageId = $this->packageRepository->createManual(
|
||||||
|
$orderId,
|
||||||
|
$trackingNumber,
|
||||||
|
$carrierName !== '' ? $carrierName : null
|
||||||
|
);
|
||||||
|
|
||||||
|
$description = 'Dodano reczny numer przesylki: ' . $trackingNumber;
|
||||||
|
if ($carrierName !== '') {
|
||||||
|
$description .= ' (' . $carrierName . ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->ordersRepository->recordActivity(
|
||||||
|
$orderId,
|
||||||
|
'shipment_manual',
|
||||||
|
$description,
|
||||||
|
['package_id' => $packageId, 'tracking_number' => $trackingNumber, 'carrier_name' => $carrierName],
|
||||||
|
'user',
|
||||||
|
$actorName
|
||||||
|
);
|
||||||
|
|
||||||
|
Flash::set('order.success', 'Numer przesylki dodany.');
|
||||||
|
return Response::redirect('/orders/' . $orderId);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<string, mixed>|null $deliveryAddr
|
* @param array<string, mixed>|null $deliveryAddr
|
||||||
* @param array<string, mixed>|null $customerAddr
|
* @param array<string, mixed>|null $customerAddr
|
||||||
|
|||||||
@@ -121,6 +121,24 @@ final class ShipmentPackageRepository
|
|||||||
/**
|
/**
|
||||||
* @return array<string, mixed>|null
|
* @return array<string, mixed>|null
|
||||||
*/
|
*/
|
||||||
|
public function createManual(int $orderId, string $trackingNumber, ?string $carrierName = null): int
|
||||||
|
{
|
||||||
|
$statement = $this->pdo->prepare(
|
||||||
|
'INSERT INTO shipment_packages (order_id, provider, tracking_number, carrier_id, status)
|
||||||
|
VALUES (:order_id, :provider, :tracking_number, :carrier_id, :status)'
|
||||||
|
);
|
||||||
|
|
||||||
|
$statement->execute([
|
||||||
|
'order_id' => $orderId,
|
||||||
|
'provider' => 'manual',
|
||||||
|
'tracking_number' => $trackingNumber,
|
||||||
|
'carrier_id' => $carrierName !== null && trim($carrierName) !== '' ? trim($carrierName) : null,
|
||||||
|
'status' => 'created',
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (int) $this->pdo->lastInsertId();
|
||||||
|
}
|
||||||
|
|
||||||
public function findByCommandId(string $commandId): ?array
|
public function findByCommandId(string $commandId): ?array
|
||||||
{
|
{
|
||||||
$statement = $this->pdo->prepare('SELECT * FROM shipment_packages WHERE command_id = :command_id LIMIT 1');
|
$statement = $this->pdo->prepare('SELECT * FROM shipment_packages WHERE command_id = :command_id LIMIT 1');
|
||||||
|
|||||||
Reference in New Issue
Block a user