--- phase: 23-shipment-presets-backend plan: 01 type: execute wave: 1 depends_on: [] files_modified: - database/migrations/20260322_000059_create_shipment_presets_table.sql - src/Modules/Shipments/ShipmentPresetRepository.php - src/Modules/Shipments/ShipmentPresetController.php - routes/web.php autonomous: true --- ## Goal Stworzyć tabelę DB `shipment_presets` oraz pełny backend CRUD (JSON API) do zarządzania presetami przesyłek. ## Purpose Presety przesyłek pozwalają użytkownikom jednym kliknięciem wypełnić formularz przygotowania przesyłki zapisanymi wcześniej parametrami (przewoźnik, usługa, wymiary, waga itp.). Ta faza buduje fundament danych i API — UI będzie w fazie 24. ## Output - Migracja SQL tworząca tabelę `shipment_presets` - `ShipmentPresetRepository` z metodami CRUD - `ShipmentPresetController` z endpointami JSON API - Routing w `routes/web.php` ## Project Context @.paul/PROJECT.md @.paul/ROADMAP.md @.paul/STATE.md ## Source Files @routes/web.php @src/Modules/Shipments/ShipmentController.php @src/Modules/Shipments/ShipmentPackageRepository.php @resources/views/shipments/prepare.php No specialized flows required for this plan (SPECIAL-FLOWS.md: sonar-scanner required post-APPLY). ## AC-1: Tabela shipment_presets istnieje w bazie ```gherkin Given migracja została uruchomiona When sprawdzam strukturę tabeli shipment_presets Then tabela zawiera kolumny: id, name, color, carrier, provider_code, delivery_method_id, credentials_id, carrier_id, package_type, length_cm, width_cm, height_cm, weight_kg, sender_point_id, label_format, sort_order, created_at, updated_at ``` ## AC-2: API zwraca listę presetów ```gherkin Given w tabeli istnieją presety When wysyłam GET /api/shipment-presets Then otrzymuję JSON z tablicą presetów posortowanych po sort_order ``` ## AC-3: API tworzy nowy preset ```gherkin Given wysyłam POST /api/shipment-presets z danymi formularza When dane są poprawne (name wymagane) Then preset zostaje zapisany w bazie i zwrócony jako JSON z id ``` ## AC-4: API aktualizuje preset ```gherkin Given istnieje preset o id=1 When wysyłam PUT /api/shipment-presets/1 z nowymi danymi Then preset zostaje zaktualizowany w bazie ``` ## AC-5: API usuwa preset ```gherkin Given istnieje preset o id=1 When wysyłam DELETE /api/shipment-presets/1 Then preset zostaje usunięty z bazy ``` Task 1: Migracja DB — tabela shipment_presets database/migrations/20260322_000059_create_shipment_presets_table.sql Utworzyć migrację SQL tworzącą tabelę `shipment_presets`: ```sql CREATE TABLE shipment_presets ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL, color VARCHAR(7) NOT NULL DEFAULT '#3b82f6', carrier VARCHAR(32) NOT NULL COMMENT 'allegro, inpost, apaczka', provider_code VARCHAR(32) NOT NULL COMMENT 'allegro_wza, apaczka, inpost', delivery_method_id VARCHAR(128) NOT NULL, credentials_id VARCHAR(128) NOT NULL DEFAULT '', carrier_id VARCHAR(64) NOT NULL DEFAULT '', package_type VARCHAR(16) NOT NULL DEFAULT 'PACKAGE', length_cm DECIMAL(8,1) NOT NULL DEFAULT 25.0, width_cm DECIMAL(8,1) NOT NULL DEFAULT 20.0, height_cm DECIMAL(8,1) NOT NULL DEFAULT 8.0, weight_kg DECIMAL(8,3) NOT NULL DEFAULT 1.000, sender_point_id VARCHAR(64) NOT NULL DEFAULT '', label_format VARCHAR(8) NOT NULL DEFAULT 'PDF', sort_order INT UNSIGNED NOT NULL DEFAULT 0, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; ``` Kolumna `color` przechowuje hex kolor przycisku (np. #3b82f6). Brak user_id — presety globalne. Uruchomić migrację na bazie: php -l na pliku SQL nie ma sensu, ale sprawdzić składnię wizualnie. Migracja zostanie uruchomiona ręcznie. AC-1 satisfied: tabela shipment_presets z pełnym zestawem kolumn Task 2: ShipmentPresetRepository — CRUD na tabeli src/Modules/Shipments/ShipmentPresetRepository.php Utworzyć klasę `ShipmentPresetRepository` w namespace `App\Modules\Shipments`: - Constructor: `__construct(private readonly PDO $pdo)` - `findAll(): array` — SELECT * ORDER BY sort_order ASC, id ASC - `findById(int $id): ?array` — SELECT WHERE id = :id - `create(array $data): int` — INSERT, return lastInsertId - `update(int $id, array $data): void` — UPDATE WHERE id = :id - `delete(int $id): void` — DELETE WHERE id = :id Wzorować się na istniejącym `ShipmentPackageRepository` — ten sam styl (PDO prepared statements, final class). Kolumny do zapisu w create/update: name, color, carrier, provider_code, delivery_method_id, credentials_id, carrier_id, package_type, length_cm, width_cm, height_cm, weight_kg, sender_point_id, label_format, sort_order Avoid: Medoo — ten moduł używa czystego PDO (jak ShipmentPackageRepository). php -l src/Modules/Shipments/ShipmentPresetRepository.php — brak błędów składniowych AC-2 partially (repository layer ready), AC-3, AC-4, AC-5 data layer ready Task 3: ShipmentPresetController + routing src/Modules/Shipments/ShipmentPresetController.php, routes/web.php 1. Utworzyć `ShipmentPresetController` w namespace `App\Modules\Shipments`: - Constructor: `__construct(private readonly ShipmentPresetRepository $repository)` - `list(Request $request): Response` — GET, zwraca JSON array presetów - `store(Request $request): Response` — POST, walidacja (name required), tworzy preset, zwraca JSON z id + 201 - `update(Request $request): Response` — PUT, walidacja, aktualizuje preset, zwraca JSON 200 - `destroy(Request $request): Response` — DELETE, usuwa preset, zwraca JSON 204 Dla store/update wyciągnąć dane z request: - name, color, carrier, provider_code, delivery_method_id, credentials_id, carrier_id, package_type, length_cm, width_cm, height_cm, weight_kg, sender_point_id, label_format, sort_order Walidacja: `name` nie może być pusty. Resztę ustawić z defaults. Brak CSRF dla JSON API — ale wymagać auth middleware. 2. W `routes/web.php`: - Dodać `use App\Modules\Shipments\ShipmentPresetRepository;` - Dodać `use App\Modules\Shipments\ShipmentPresetController;` - Instancja: `$presetRepository = new ShipmentPresetRepository($app->db());` - Instancja: `$presetController = new ShipmentPresetController($presetRepository);` - Routing (z $authMiddleware): ``` $router->get('/api/shipment-presets', [$presetController, 'list'], [$authMiddleware]); $router->post('/api/shipment-presets', [$presetController, 'store'], [$authMiddleware]); $router->put('/api/shipment-presets/{id}', [$presetController, 'update'], [$authMiddleware]); $router->delete('/api/shipment-presets/{id}', [$presetController, 'destroy'], [$authMiddleware]); ``` Umieścić routing w sekcji API (po print API routes, przed zamknięciem). Wzorować styl Response na istniejących API controllerach (PrintApiController — Response::json). php -l src/Modules/Shipments/ShipmentPresetController.php — brak błędów składniowych. Sprawdzić że routes/web.php nie ma syntax errors: php -l routes/web.php AC-2, AC-3, AC-4, AC-5 satisfied: pełny JSON API dla presetów ## DO NOT CHANGE - src/Modules/Shipments/ShipmentController.php (modyfikacja w fazie 24) - resources/views/shipments/prepare.php (modyfikacja w fazie 24) - src/Modules/Shipments/ShipmentPackageRepository.php (stabilne) - Istniejące migracje w database/migrations/ ## SCOPE LIMITS - Tylko backend (tabela + repository + controller + routing) - Brak zmian UI — to faza 24 - Brak walidacji CSRF w API JSON (auth middleware wystarczający) - Presety globalne (bez user_id) Before declaring plan complete: - [ ] Migracja SQL poprawna składniowo - [ ] php -l na ShipmentPresetRepository.php — OK - [ ] php -l na ShipmentPresetController.php — OK - [ ] php -l na routes/web.php — OK - [ ] Routing dodany z auth middleware - [ ] All acceptance criteria met - Tabela shipment_presets gotowa do migracji - Repository z pełnym CRUD - Controller z 4 endpointami JSON API - Routing zarejestrowany w routes/web.php - Brak błędów PHP After completion, create `.paul/phases/23-shipment-presets-backend/23-01-SUMMARY.md`