feat(108): delivery status management

Phase 108 complete (v3.2 milestone):

Plan 108-01 — Delivery Status DB & CRUD:
- Tabela delivery_statuses z seedem 11 statusow systemowych
- DeliveryStatusRepository (CRUD + per-request static cache)
- DeliveryStatus::setRepository() — DB fallback dla static final class
- Panel /settings/delivery-statuses (zakladki Statusy + Mapowanie)
- Sidebar przebudowany: Statusy zamowien + Statusy przesylek

Plan 108-02 — Automation Dropdowns z DB + UI Refactor:
- Dropdowny automatyzacji ladowane z DB (warunek shipment_status + akcja update_shipment_status)
- Walidacja przez DeliveryStatus::getAllStatuses()
- Osobna podstrona formularza CRUD (delivery-status-form.php)
- Lista uproszczona: rename Terminal -> Koncowy, usunieta kolumna Typ
- BREAKING: drop backward compat dla starych grupowych kluczy automatyzacji
- Bug fix: path params w DeliveryStatusesController via \$request->input('id')

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-27 22:10:24 +02:00
parent d8daf61de6
commit 0063402897
30 changed files with 2045 additions and 299 deletions

View File

@@ -11,6 +11,7 @@ use App\Core\Support\Flash;
use App\Core\View\Template;
use App\Modules\Auth\AuthService;
use App\Modules\Settings\ReceiptConfigRepository;
use App\Modules\Shipments\DeliveryStatus;
use Throwable;
final class AutomationController
@@ -33,17 +34,6 @@ final class AutomationController
'online' => 'Karta / platnosc online',
'other' => 'Inna',
];
private const SHIPMENT_STATUS_OPTIONS = [
'registered' => ['label' => 'Przesylka zarejestrowana', 'statuses' => ['created', 'confirmed']],
'courier_pickup' => ['label' => 'Odebrana przez kuriera / nadana w paczkomacie', 'statuses' => ['picked_up']],
'ready_for_pickup' => ['label' => 'Przesylka do odbioru', 'statuses' => ['ready_for_pickup']],
'dropped_at_point' => ['label' => 'Przesylka nadana w punkcie', 'statuses' => ['confirmed', 'in_transit']],
'picked_up' => ['label' => 'Przesylka odebrana', 'statuses' => ['delivered']],
'cancelled' => ['label' => 'Przesylka anulowana', 'statuses' => ['cancelled']],
'unclaimed' => ['label' => 'Przesylka nieodebrana', 'statuses' => ['problem']],
'picked_up_return' => ['label' => 'Przesylka odebrana (zwrot)', 'statuses' => ['returned']],
];
public function __construct(
private readonly Template $template,
private readonly Translator $translator,
@@ -256,7 +246,7 @@ final class AutomationController
'receiptConfigs' => $this->listActiveReceiptConfigs(),
'receiptIssueDateModes' => self::ALLOWED_RECEIPT_ISSUE_DATE_MODES,
'receiptDuplicatePolicies' => self::ALLOWED_RECEIPT_DUPLICATE_POLICIES,
'shipmentStatusOptions' => self::SHIPMENT_STATUS_OPTIONS,
'shipmentStatusOptions' => $this->buildShipmentStatusOptions(),
'paymentStatusOptions' => self::PAYMENT_STATUS_OPTIONS,
'paymentMethodOptions' => self::PAYMENT_METHOD_OPTIONS,
'orderStatusOptions' => $this->repository->listActiveOrderStatuses(),
@@ -427,7 +417,7 @@ final class AutomationController
$keys = [];
}
$allowedKeys = array_keys(self::SHIPMENT_STATUS_OPTIONS);
$allowedKeys = DeliveryStatus::getAllStatuses();
$statusKeys = array_values(array_filter(
array_map(static fn (mixed $key): string => trim((string) $key), $keys),
static fn (string $key): bool => $key !== '' && in_array($key, $allowedKeys, true)
@@ -570,7 +560,7 @@ final class AutomationController
if ($type === 'update_shipment_status') {
$statusKey = trim((string) ($action['shipment_status_key'] ?? ''));
if (!array_key_exists($statusKey, self::SHIPMENT_STATUS_OPTIONS)) {
if ($statusKey === '' || !in_array($statusKey, DeliveryStatus::getAllStatuses(), true)) {
return null;
}
@@ -598,6 +588,19 @@ final class AutomationController
return null;
}
/**
* @return array<string, array{label:string}>
*/
private function buildShipmentStatusOptions(): array
{
$options = [];
foreach (DeliveryStatus::getAllOptions() as $key => $label) {
$options[(string) $key] = ['label' => (string) $label];
}
return $options;
}
/**
* @return list<array{id:int,name:string,number_format:string}>
*/

View File

@@ -21,17 +21,6 @@ final class AutomationService
private const MAX_CHAIN_DEPTH = 8;
private const MAX_CHAIN_EXECUTIONS = 200;
private const SHIPMENT_STATUS_OPTION_MAP = [
'registered' => ['created', 'confirmed'],
'courier_pickup' => ['picked_up'],
'ready_for_pickup' => ['ready_for_pickup'],
'dropped_at_point' => ['confirmed', 'in_transit'],
'picked_up' => ['delivered'],
'cancelled' => ['cancelled'],
'unclaimed' => ['problem'],
'picked_up_return' => ['returned'],
];
public function __construct(
private readonly AutomationRepository $repository,
private readonly AutomationExecutionLogRepository $executionLogs,
@@ -189,12 +178,10 @@ final class AutomationService
$allowedStatuses = [];
foreach ($statusKeys as $statusKeyRaw) {
$statusKey = trim((string) $statusKeyRaw);
if ($statusKey === '' || !isset(self::SHIPMENT_STATUS_OPTION_MAP[$statusKey])) {
if ($statusKey === '') {
continue;
}
foreach (self::SHIPMENT_STATUS_OPTION_MAP[$statusKey] as $mappedStatus) {
$allowedStatuses[$mappedStatus] = true;
}
$allowedStatuses[$statusKey] = true;
}
if ($allowedStatuses === []) {
@@ -609,21 +596,15 @@ final class AutomationService
private function resolveStatusFromActionKey(string $statusKey): ?string
{
if ($statusKey === '' || !isset(self::SHIPMENT_STATUS_OPTION_MAP[$statusKey])) {
if ($statusKey === '') {
return null;
}
$mappedStatuses = self::SHIPMENT_STATUS_OPTION_MAP[$statusKey];
if ($mappedStatuses === []) {
if (!in_array($statusKey, DeliveryStatus::getAllStatuses(), true)) {
return null;
}
$candidate = trim((string) $mappedStatuses[0]);
if ($candidate === '' || !in_array($candidate, DeliveryStatus::ALL_STATUSES, true)) {
return null;
}
return $candidate;
return $statusKey;
}
/**

View File

@@ -12,11 +12,12 @@ use App\Core\View\Template;
use App\Modules\Auth\AuthService;
use App\Modules\Shipments\DeliveryStatus;
use App\Modules\Shipments\DeliveryStatusMappingRepository;
use App\Modules\Shipments\DeliveryStatusRepository;
use Throwable;
final class DeliveryStatusMappingController
{
private const REDIRECT_PATH = '/settings/delivery-status-mappings';
private const REDIRECT_PATH = '/settings/delivery-statuses?tab=mapping';
private const PROVIDERS = [
'inpost' => 'InPost',
@@ -28,7 +29,8 @@ final class DeliveryStatusMappingController
private readonly Template $template,
private readonly Translator $translator,
private readonly AuthService $auth,
private readonly DeliveryStatusMappingRepository $repository
private readonly DeliveryStatusMappingRepository $repository,
private readonly DeliveryStatusRepository $deliveryStatusRepository
) {
}
@@ -86,7 +88,7 @@ final class DeliveryStatusMappingController
'providers' => self::PROVIDERS,
'mappings' => $mappings,
'unmappedRawStatuses' => $unmappedRawStatuses,
'normalizedOptions' => DeliveryStatus::LABEL_PL,
'normalizedOptions' => $this->deliveryStatusRepository->getAllAsOptions(),
'errorMessage' => (string) Flash::get('dsm_error', ''),
'successMessage' => (string) Flash::get('dsm_success', ''),
], 'layouts/app');
@@ -108,12 +110,12 @@ final class DeliveryStatusMappingController
if ($provider === '' || $rawStatus === '') {
Flash::set('dsm_error', 'Brakuje wymaganych pól.');
return Response::redirect(self::REDIRECT_PATH . '?provider=' . rawurlencode($provider));
return Response::redirect(self::REDIRECT_PATH . '&provider=' . rawurlencode($provider));
}
if (!in_array($normalizedStatus, DeliveryStatus::ALL_STATUSES, true)) {
if ($this->deliveryStatusRepository->getByKey($normalizedStatus) === null) {
Flash::set('dsm_error', 'Nieprawidłowy status znormalizowany.');
return Response::redirect(self::REDIRECT_PATH . '?provider=' . rawurlencode($provider));
return Response::redirect(self::REDIRECT_PATH . '&provider=' . rawurlencode($provider));
}
try {
@@ -123,7 +125,7 @@ final class DeliveryStatusMappingController
Flash::set('dsm_error', 'Błąd zapisu: ' . $exception->getMessage());
}
return Response::redirect(self::REDIRECT_PATH . '?provider=' . rawurlencode($provider));
return Response::redirect(self::REDIRECT_PATH . '&provider=' . rawurlencode($provider));
}
public function saveBulk(Request $request): Response
@@ -140,7 +142,7 @@ final class DeliveryStatusMappingController
if (!is_array($rawStatuses) || !is_array($normalizedStatuses) || !is_array($descriptions)) {
Flash::set('dsm_error', 'Nieprawidłowe dane formularza.');
return Response::redirect(self::REDIRECT_PATH . '?provider=' . rawurlencode($provider));
return Response::redirect(self::REDIRECT_PATH . '&provider=' . rawurlencode($provider));
}
try {
@@ -156,7 +158,7 @@ final class DeliveryStatusMappingController
$normalizedStatus = trim((string) ($normalizedStatuses[$index] ?? ''));
$description = trim((string) ($descriptions[$index] ?? ''));
if (!in_array($normalizedStatus, DeliveryStatus::ALL_STATUSES, true)) {
if ($this->deliveryStatusRepository->getByKey($normalizedStatus) === null) {
continue;
}
@@ -178,7 +180,7 @@ final class DeliveryStatusMappingController
Flash::set('dsm_error', 'Błąd zapisu: ' . $exception->getMessage());
}
return Response::redirect(self::REDIRECT_PATH . '?provider=' . rawurlencode($provider));
return Response::redirect(self::REDIRECT_PATH . '&provider=' . rawurlencode($provider));
}
public function reset(Request $request): Response
@@ -193,7 +195,7 @@ final class DeliveryStatusMappingController
if ($provider === '' || $rawStatus === '') {
Flash::set('dsm_error', 'Brakuje wymaganych pól.');
return Response::redirect(self::REDIRECT_PATH . '?provider=' . rawurlencode($provider));
return Response::redirect(self::REDIRECT_PATH . '&provider=' . rawurlencode($provider));
}
try {
@@ -203,7 +205,7 @@ final class DeliveryStatusMappingController
Flash::set('dsm_error', 'Błąd resetu: ' . $exception->getMessage());
}
return Response::redirect(self::REDIRECT_PATH . '?provider=' . rawurlencode($provider));
return Response::redirect(self::REDIRECT_PATH . '&provider=' . rawurlencode($provider));
}
public function resetAll(Request $request): Response
@@ -226,7 +228,7 @@ final class DeliveryStatusMappingController
Flash::set('dsm_error', 'Błąd resetu: ' . $exception->getMessage());
}
return Response::redirect(self::REDIRECT_PATH . '?provider=' . rawurlencode($provider));
return Response::redirect(self::REDIRECT_PATH . '&provider=' . rawurlencode($provider));
}
private function validateCsrf(string $token): ?Response

View File

@@ -0,0 +1,286 @@
<?php
declare(strict_types=1);
namespace App\Modules\Settings;
use App\Core\Http\Request;
use App\Core\Http\Response;
use App\Core\I18n\Translator;
use App\Core\Security\Csrf;
use App\Core\Support\Flash;
use App\Core\View\Template;
use App\Modules\Auth\AuthService;
use App\Modules\Shipments\DeliveryStatus;
use App\Modules\Shipments\DeliveryStatusMappingRepository;
use App\Modules\Shipments\DeliveryStatusRepository;
use Throwable;
final class DeliveryStatusesController
{
private const REDIRECT_STATUSES = '/settings/delivery-statuses?tab=statuses';
private const PROVIDERS = [
'inpost' => 'InPost',
'apaczka' => 'Apaczka',
'allegro_wza' => 'Allegro',
];
public function __construct(
private readonly Template $template,
private readonly Translator $translator,
private readonly AuthService $auth,
private readonly DeliveryStatusRepository $deliveryStatusRepository,
private readonly DeliveryStatusMappingRepository $deliveryStatusMappingRepository
) {
}
public function index(Request $request): Response
{
$tab = (string) ($request->input('tab', 'statuses') ?? 'statuses');
if (!in_array($tab, ['statuses', 'mapping'], true)) {
$tab = 'statuses';
}
$data = [
'title' => 'Statusy przesyłek',
'activeMenu' => 'settings',
'activeSettings' => 'delivery-statuses',
'user' => $this->auth->user(),
'csrfToken' => Csrf::token(),
'tab' => $tab,
'statuses' => $this->deliveryStatusRepository->getAll(),
'errorMessage' => (string) Flash::get('ds_error', ''),
'successMessage' => (string) Flash::get('ds_success', ''),
];
if ($tab === 'mapping') {
$data = array_merge($data, $this->buildMappingData($request));
}
$html = $this->template->render('settings/delivery-statuses', $data, 'layouts/app');
return Response::html($html);
}
public function create(Request $request): Response
{
return $this->renderForm(null);
}
public function edit(Request $request): Response
{
$id = max(0, (int) $request->input('id', 0));
$row = null;
foreach ($this->deliveryStatusRepository->getAll() as $candidate) {
if ((int) ($candidate['id'] ?? 0) === $id) {
$row = $candidate;
break;
}
}
if ($row === null) {
Flash::set('ds_error', 'Status nie istnieje.');
return Response::redirect(self::REDIRECT_STATUSES);
}
if ((int) ($row['is_system'] ?? 0) === 1) {
Flash::set('ds_error', 'Statusów systemowych nie można edytować.');
return Response::redirect(self::REDIRECT_STATUSES);
}
return $this->renderForm($row);
}
public function store(Request $request): Response
{
$csrfError = $this->validateCsrf((string) $request->input('_token', ''));
if ($csrfError !== null) {
return $csrfError;
}
$key = trim((string) $request->input('key', ''));
$labelPl = trim((string) $request->input('label_pl', ''));
$color = trim((string) $request->input('color', '#6c757d'));
$sortOrder = (int) $request->input('sort_order', 0);
$isTerminal = $request->input('is_terminal', '') !== '' ? 1 : 0;
$validationError = $this->validateFields($key, $labelPl, $color, true);
if ($validationError !== null) {
Flash::set('ds_error', $validationError);
return Response::redirect(self::REDIRECT_STATUSES);
}
try {
$this->deliveryStatusRepository->create([
'key' => $key,
'label_pl' => $labelPl,
'color' => $color,
'sort_order' => $sortOrder,
'is_terminal' => $isTerminal,
]);
Flash::set('ds_success', 'Status dodany.');
} catch (Throwable $exception) {
Flash::set('ds_error', 'Błąd: ' . $exception->getMessage());
}
return Response::redirect(self::REDIRECT_STATUSES);
}
public function update(Request $request): Response
{
$csrfError = $this->validateCsrf((string) $request->input('_token', ''));
if ($csrfError !== null) {
return $csrfError;
}
$id = max(0, (int) $request->input('id', 0));
$labelPl = trim((string) $request->input('label_pl', ''));
$color = trim((string) $request->input('color', '#6c757d'));
$sortOrder = (int) $request->input('sort_order', 0);
$isTerminal = $request->input('is_terminal', '') !== '' ? 1 : 0;
$validationError = $this->validateFields('valid_key', $labelPl, $color, false);
if ($validationError !== null) {
Flash::set('ds_error', $validationError);
return Response::redirect(self::REDIRECT_STATUSES);
}
try {
$this->deliveryStatusRepository->update($id, [
'label_pl' => $labelPl,
'color' => $color,
'sort_order' => $sortOrder,
'is_terminal' => $isTerminal,
]);
Flash::set('ds_success', 'Status zaktualizowany.');
} catch (Throwable $exception) {
Flash::set('ds_error', 'Błąd: ' . $exception->getMessage());
}
return Response::redirect(self::REDIRECT_STATUSES);
}
public function destroy(Request $request): Response
{
$csrfError = $this->validateCsrf((string) $request->input('_token', ''));
if ($csrfError !== null) {
return $csrfError;
}
$id = max(0, (int) $request->input('id', 0));
try {
$this->deliveryStatusRepository->delete($id);
Flash::set('ds_success', 'Status usunięty.');
} catch (Throwable $exception) {
Flash::set('ds_error', 'Błąd: ' . $exception->getMessage());
}
return Response::redirect(self::REDIRECT_STATUSES);
}
/**
* @param array<string, mixed>|null $row
*/
private function renderForm(?array $row): Response
{
$isEdit = $row !== null && isset($row['id']);
$html = $this->template->render('settings/delivery-status-form', [
'title' => $isEdit ? 'Edytuj status przesyłki' : 'Nowy status przesyłki',
'activeMenu' => 'settings',
'activeSettings' => 'delivery-statuses',
'user' => $this->auth->user(),
'csrfToken' => Csrf::token(),
'row' => $row,
'isEdit' => $isEdit,
'errorMessage' => (string) Flash::get('ds_error', ''),
], 'layouts/app');
return Response::html($html);
}
private function validateCsrf(string $token): ?Response
{
if (Csrf::validate($token)) {
return null;
}
Flash::set('ds_error', $this->translator->get('auth.errors.csrf_expired'));
return Response::redirect(self::REDIRECT_STATUSES);
}
private function validateFields(string $key, string $labelPl, string $color, bool $validateKey): ?string
{
if ($validateKey) {
if ($key === '' || !preg_match('/^[a-z][a-z0-9_]{0,49}$/', $key)) {
return 'Klucz statusu jest nieprawidłowy (tylko małe litery, cyfry, _, max 50 znaków, zaczyna się literą).';
}
}
if ($labelPl === '' || mb_strlen($labelPl) > 100) {
return 'Etykieta jest wymagana i nie może przekraczać 100 znaków.';
}
if (!preg_match('/^#[0-9a-fA-F]{6}$/', $color)) {
return 'Kolor musi być w formacie #RRGGBB.';
}
return null;
}
/**
* @return array<string, mixed>
*/
private function buildMappingData(Request $request): array
{
$provider = strtolower(trim((string) $request->input('provider', 'inpost')));
if (!isset(self::PROVIDERS[$provider])) {
$provider = 'inpost';
}
$defaults = DeliveryStatus::getDefaultMappings($provider);
$overrides = $this->deliveryStatusMappingRepository->listByProvider($provider);
$overrideMap = [];
foreach ($overrides as $row) {
$overrideMap[$row['raw_status']] = $row;
}
$mappings = [];
$knownRawStatuses = [];
foreach ($defaults as $rawStatus => $default) {
$isCustom = isset($overrideMap[$rawStatus]);
$mappings[] = [
'raw_status' => $rawStatus,
'description' => $isCustom ? $overrideMap[$rawStatus]['description'] : $default['description'],
'normalized_status' => $isCustom ? $overrideMap[$rawStatus]['normalized_status'] : $default['normalized'],
'is_custom' => $isCustom,
];
$knownRawStatuses[$rawStatus] = true;
}
foreach ($overrideMap as $rawStatus => $row) {
if (isset($knownRawStatuses[$rawStatus])) {
continue;
}
$mappings[] = [
'raw_status' => $rawStatus,
'description' => $row['description'],
'normalized_status' => $row['normalized_status'],
'is_custom' => true,
];
$knownRawStatuses[$rawStatus] = true;
}
$unmappedRawStatuses = $this->deliveryStatusMappingRepository->listUnmappedRawStatuses($provider, $knownRawStatuses);
return [
'provider' => $provider,
'providers' => self::PROVIDERS,
'mappings' => $mappings,
'unmappedRawStatuses' => $unmappedRawStatuses,
'normalizedOptions' => $this->deliveryStatusRepository->getAllAsOptions(),
];
}
}

View File

@@ -5,6 +5,8 @@ namespace App\Modules\Shipments;
final class DeliveryStatus
{
private static ?DeliveryStatusRepository $repository = null;
public const UNKNOWN = 'unknown';
public const CREATED = 'created';
public const CONFIRMED = 'confirmed';
@@ -379,8 +381,46 @@ final class DeliveryStatus
return $map[$rawStatus] ?? $rawStatus;
}
public static function setRepository(DeliveryStatusRepository $repo): void
{
self::$repository = $repo;
}
public static function getAllStatuses(): array
{
if (self::$repository !== null) {
return array_column(self::$repository->getAll(), 'key');
}
return self::ALL_STATUSES;
}
public static function getAllOptions(): array
{
if (self::$repository !== null) {
return self::$repository->getAllAsOptions();
}
return self::LABEL_PL;
}
public static function getColor(string $key): string
{
if (self::$repository !== null) {
$row = self::$repository->getByKey($key);
if ($row !== null) {
return (string) $row['color'];
}
}
return '#6c757d';
}
public static function label(string $status): string
{
if (self::$repository !== null) {
$row = self::$repository->getByKey($status);
if ($row !== null) {
return (string) $row['label_pl'];
}
}
return self::LABEL_PL[$status] ?? 'Nieznany';
}

View File

@@ -0,0 +1,157 @@
<?php
declare(strict_types=1);
namespace App\Modules\Shipments;
use PDO;
final class DeliveryStatusRepository
{
private static ?array $cache = null;
public function __construct(private readonly PDO $db)
{
}
public function getAll(): array
{
if (self::$cache !== null) {
return self::$cache;
}
$statement = $this->db->prepare(
'SELECT * FROM delivery_statuses ORDER BY sort_order ASC'
);
$statement->execute();
$rows = $statement->fetchAll(PDO::FETCH_ASSOC);
self::$cache = is_array($rows) ? $rows : [];
return self::$cache;
}
public function getByKey(string $key): ?array
{
foreach ($this->getAll() as $row) {
if ($row['key'] === $key) {
return $row;
}
}
return null;
}
public function getAllAsOptions(): array
{
$options = [];
foreach ($this->getAll() as $row) {
$options[$row['key']] = $row['label_pl'];
}
return $options;
}
public function create(array $data): int
{
if ($this->getByKey($data['key']) !== null) {
throw new \RuntimeException('Status o tym kluczu już istnieje.');
}
$statement = $this->db->prepare(
'INSERT INTO delivery_statuses (`key`, label_pl, color, sort_order, is_terminal)
VALUES (:key, :label_pl, :color, :sort_order, :is_terminal)'
);
$statement->bindValue(':key', $data['key']);
$statement->bindValue(':label_pl', $data['label_pl']);
$statement->bindValue(':color', $data['color']);
$statement->bindValue(':sort_order', $data['sort_order'], PDO::PARAM_INT);
$statement->bindValue(':is_terminal', $data['is_terminal'], PDO::PARAM_INT);
$statement->execute();
$this->clearCache();
return (int) $this->db->lastInsertId();
}
public function update(int $id, array $data): void
{
$row = $this->findById($id);
if ($row === null) {
throw new \RuntimeException('Status nie istnieje.');
}
if ((int) $row['is_system'] === 1) {
throw new \RuntimeException('Statusów systemowych nie można edytować.');
}
$statement = $this->db->prepare(
'UPDATE delivery_statuses
SET label_pl = :label_pl, color = :color, sort_order = :sort_order, is_terminal = :is_terminal
WHERE id = :id'
);
$statement->bindValue(':label_pl', $data['label_pl']);
$statement->bindValue(':color', $data['color']);
$statement->bindValue(':sort_order', $data['sort_order'], PDO::PARAM_INT);
$statement->bindValue(':is_terminal', $data['is_terminal'], PDO::PARAM_INT);
$statement->bindValue(':id', $id, PDO::PARAM_INT);
$statement->execute();
$this->clearCache();
}
public function delete(int $id): void
{
$row = $this->findById($id);
if ($row === null) {
throw new \RuntimeException('Status nie istnieje.');
}
if ((int) $row['is_system'] === 1) {
throw new \RuntimeException('Statusów systemowych nie można usunąć.');
}
$key = (string) $row['key'];
$stmtMappings = $this->db->prepare(
'SELECT COUNT(*) FROM delivery_status_mappings WHERE normalized_status = :key'
);
$stmtMappings->bindValue(':key', $key);
$stmtMappings->execute();
$countMappings = (int) $stmtMappings->fetchColumn();
$stmtPackages = $this->db->prepare(
'SELECT COUNT(*) FROM shipment_packages WHERE delivery_status = :key'
);
$stmtPackages->bindValue(':key', $key);
$stmtPackages->execute();
$countPackages = (int) $stmtPackages->fetchColumn();
if ($countMappings > 0 || $countPackages > 0) {
throw new \RuntimeException('Status jest używany i nie może być usunięty.');
}
$statement = $this->db->prepare(
'DELETE FROM delivery_statuses WHERE id = :id'
);
$statement->bindValue(':id', $id, PDO::PARAM_INT);
$statement->execute();
$this->clearCache();
}
public function clearCache(): void
{
self::$cache = null;
}
private function findById(int $id): ?array
{
foreach ($this->getAll() as $row) {
if ((int) $row['id'] === $id) {
return $row;
}
}
return null;
}
}