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:
2026-03-23 18:17:21 +01:00
parent 686429d9cf
commit c59d431083
13 changed files with 3473 additions and 20 deletions

View File

@@ -338,6 +338,54 @@ final class ShipmentController
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 $customerAddr

View File

@@ -121,6 +121,24 @@ final class ShipmentPackageRepository
/**
* @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
{
$statement = $this->pdo->prepare('SELECT * FROM shipment_packages WHERE command_id = :command_id LIMIT 1');