feat(23-shipment-presets-backend): tabela DB, repository CRUD i JSON API dla presetów przesyłek
Phase 23 complete: - Migracja shipment_presets (16 kolumn: name, color, carrier, wymiary, waga, itp.) - ShipmentPresetRepository z findAll/findById/create/update/delete - ShipmentPresetController z 4 endpointami JSON API - Routing w routes/web.php z auth middleware Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
100
src/Modules/Shipments/ShipmentPresetController.php
Normal file
100
src/Modules/Shipments/ShipmentPresetController.php
Normal file
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Modules\Shipments;
|
||||
|
||||
use App\Core\Http\Request;
|
||||
use App\Core\Http\Response;
|
||||
|
||||
final class ShipmentPresetController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ShipmentPresetRepository $repository
|
||||
) {
|
||||
}
|
||||
|
||||
public function list(Request $request): Response
|
||||
{
|
||||
return Response::json(['presets' => $this->repository->findAll()]);
|
||||
}
|
||||
|
||||
public function store(Request $request): Response
|
||||
{
|
||||
$name = trim((string) $request->input('name', ''));
|
||||
if ($name === '') {
|
||||
return Response::json(['error' => 'Name is required'], 422);
|
||||
}
|
||||
|
||||
$data = $this->extractData($request);
|
||||
$id = $this->repository->create($data);
|
||||
|
||||
$preset = $this->repository->findById($id);
|
||||
|
||||
return Response::json(['preset' => $preset], 201);
|
||||
}
|
||||
|
||||
public function update(Request $request): Response
|
||||
{
|
||||
$id = (int) $request->input('id', '0');
|
||||
if ($id <= 0) {
|
||||
return Response::json(['error' => 'Invalid preset ID'], 400);
|
||||
}
|
||||
|
||||
$existing = $this->repository->findById($id);
|
||||
if ($existing === null) {
|
||||
return Response::json(['error' => 'Preset not found'], 404);
|
||||
}
|
||||
|
||||
$data = $this->extractData($request);
|
||||
if (trim($data['name']) === '') {
|
||||
return Response::json(['error' => 'Name is required'], 422);
|
||||
}
|
||||
|
||||
$this->repository->update($id, $data);
|
||||
|
||||
$preset = $this->repository->findById($id);
|
||||
|
||||
return Response::json(['preset' => $preset]);
|
||||
}
|
||||
|
||||
public function destroy(Request $request): Response
|
||||
{
|
||||
$id = (int) $request->input('id', '0');
|
||||
if ($id <= 0) {
|
||||
return Response::json(['error' => 'Invalid preset ID'], 400);
|
||||
}
|
||||
|
||||
$existing = $this->repository->findById($id);
|
||||
if ($existing === null) {
|
||||
return Response::json(['error' => 'Preset not found'], 404);
|
||||
}
|
||||
|
||||
$this->repository->delete($id);
|
||||
|
||||
return Response::json(['deleted' => true]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function extractData(Request $request): array
|
||||
{
|
||||
return [
|
||||
'name' => (string) $request->input('name', ''),
|
||||
'color' => (string) $request->input('color', '#3b82f6'),
|
||||
'carrier' => (string) $request->input('carrier', ''),
|
||||
'provider_code' => (string) $request->input('provider_code', ''),
|
||||
'delivery_method_id' => (string) $request->input('delivery_method_id', ''),
|
||||
'credentials_id' => (string) $request->input('credentials_id', ''),
|
||||
'carrier_id' => (string) $request->input('carrier_id', ''),
|
||||
'package_type' => (string) $request->input('package_type', 'PACKAGE'),
|
||||
'length_cm' => (string) $request->input('length_cm', '25'),
|
||||
'width_cm' => (string) $request->input('width_cm', '20'),
|
||||
'height_cm' => (string) $request->input('height_cm', '8'),
|
||||
'weight_kg' => (string) $request->input('weight_kg', '1'),
|
||||
'sender_point_id' => (string) $request->input('sender_point_id', ''),
|
||||
'label_format' => (string) $request->input('label_format', 'PDF'),
|
||||
'sort_order' => (string) $request->input('sort_order', '0'),
|
||||
];
|
||||
}
|
||||
}
|
||||
117
src/Modules/Shipments/ShipmentPresetRepository.php
Normal file
117
src/Modules/Shipments/ShipmentPresetRepository.php
Normal file
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Modules\Shipments;
|
||||
|
||||
use PDO;
|
||||
|
||||
final class ShipmentPresetRepository
|
||||
{
|
||||
public function __construct(
|
||||
private readonly PDO $pdo
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
public function findAll(): array
|
||||
{
|
||||
$statement = $this->pdo->prepare(
|
||||
'SELECT * FROM shipment_presets ORDER BY sort_order ASC, id ASC'
|
||||
);
|
||||
$statement->execute();
|
||||
return $statement->fetchAll(PDO::FETCH_ASSOC) ?: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>|null
|
||||
*/
|
||||
public function findById(int $id): ?array
|
||||
{
|
||||
$statement = $this->pdo->prepare('SELECT * FROM shipment_presets WHERE id = :id LIMIT 1');
|
||||
$statement->execute(['id' => $id]);
|
||||
$row = $statement->fetch(PDO::FETCH_ASSOC);
|
||||
return is_array($row) ? $row : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
public function create(array $data): int
|
||||
{
|
||||
$statement = $this->pdo->prepare(
|
||||
'INSERT INTO shipment_presets (
|
||||
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
|
||||
) VALUES (
|
||||
: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
|
||||
)'
|
||||
);
|
||||
|
||||
$statement->execute($this->mapParams($data));
|
||||
|
||||
return (int) $this->pdo->lastInsertId();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
public function update(int $id, array $data): void
|
||||
{
|
||||
$statement = $this->pdo->prepare(
|
||||
'UPDATE shipment_presets SET
|
||||
name = :name, color = :color, carrier = :carrier,
|
||||
provider_code = :provider_code, delivery_method_id = :delivery_method_id,
|
||||
credentials_id = :credentials_id, carrier_id = :carrier_id,
|
||||
package_type = :package_type,
|
||||
length_cm = :length_cm, width_cm = :width_cm,
|
||||
height_cm = :height_cm, weight_kg = :weight_kg,
|
||||
sender_point_id = :sender_point_id, label_format = :label_format,
|
||||
sort_order = :sort_order
|
||||
WHERE id = :id'
|
||||
);
|
||||
|
||||
$params = $this->mapParams($data);
|
||||
$params['id'] = $id;
|
||||
$statement->execute($params);
|
||||
}
|
||||
|
||||
public function delete(int $id): void
|
||||
{
|
||||
$statement = $this->pdo->prepare('DELETE FROM shipment_presets WHERE id = :id');
|
||||
$statement->execute(['id' => $id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function mapParams(array $data): array
|
||||
{
|
||||
return [
|
||||
'name' => trim((string) ($data['name'] ?? '')),
|
||||
'color' => trim((string) ($data['color'] ?? '#3b82f6')),
|
||||
'carrier' => trim((string) ($data['carrier'] ?? '')),
|
||||
'provider_code' => trim((string) ($data['provider_code'] ?? '')),
|
||||
'delivery_method_id' => trim((string) ($data['delivery_method_id'] ?? '')),
|
||||
'credentials_id' => trim((string) ($data['credentials_id'] ?? '')),
|
||||
'carrier_id' => trim((string) ($data['carrier_id'] ?? '')),
|
||||
'package_type' => trim((string) ($data['package_type'] ?? 'PACKAGE')),
|
||||
'length_cm' => max(0.1, (float) ($data['length_cm'] ?? 25.0)),
|
||||
'width_cm' => max(0.1, (float) ($data['width_cm'] ?? 20.0)),
|
||||
'height_cm' => max(0.1, (float) ($data['height_cm'] ?? 8.0)),
|
||||
'weight_kg' => max(0.001, (float) ($data['weight_kg'] ?? 1.0)),
|
||||
'sender_point_id' => trim((string) ($data['sender_point_id'] ?? '')),
|
||||
'label_format' => in_array(strtoupper(trim((string) ($data['label_format'] ?? ''))), ['PDF', 'ZPL'], true)
|
||||
? strtoupper(trim((string) $data['label_format']))
|
||||
: 'PDF',
|
||||
'sort_order' => max(0, (int) ($data['sort_order'] ?? 0)),
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user