This commit is contained in:
2026-04-12 01:35:19 +02:00
parent 91a8b85f38
commit d04e02020c
70 changed files with 8634 additions and 207 deletions

View File

@@ -71,81 +71,98 @@ final class OrdersController
$tableRows = array_map(fn (array $row): array => $this->toTableRow($row, $statusLabelMap, $statusColorMap), (array) ($result['items'] ?? []));
$tableListData = [
'list_key' => 'orders',
'base_path' => '/orders/list',
'query' => $filters,
'filters' => [
[
'key' => 'search',
'label' => $this->translator->get('orders.filters.search'),
'type' => 'text',
'value' => $filters['search'],
],
[
'key' => 'source',
'label' => $this->translator->get('orders.filters.source'),
'type' => 'select',
'value' => $filters['source'],
'options' => ['' => $this->translator->get('orders.filters.any')] + $sourceOptions,
],
[
'key' => 'status',
'label' => $this->translator->get('orders.filters.status'),
'type' => 'select',
'value' => $filters['status'],
'options' => ['' => $this->translator->get('orders.filters.any')] + $statusOptions,
],
[
'key' => 'payment_status',
'label' => $this->translator->get('orders.filters.payment_status'),
'type' => 'select',
'value' => $filters['payment_status'],
'options' => $this->paymentStatusFilterOptions(),
],
[
'key' => 'date_from',
'label' => $this->translator->get('orders.filters.date_from'),
'type' => 'date',
'value' => $filters['date_from'],
],
[
'key' => 'date_to',
'label' => $this->translator->get('orders.filters.date_to'),
'type' => 'date',
'value' => $filters['date_to'],
],
],
'columns' => [
['key' => 'order_ref', 'label' => $this->translator->get('orders.fields.order_ref'), 'sortable' => true, 'sort_key' => 'source_order_id', 'raw' => true],
['key' => 'buyer', 'label' => $this->translator->get('orders.fields.buyer'), 'raw' => true],
['key' => 'status_badges', 'label' => $this->translator->get('orders.fields.status'), 'sortable' => true, 'sort_key' => 'status_code', 'raw' => true],
['key' => 'products', 'label' => $this->translator->get('orders.fields.products'), 'raw' => true],
['key' => 'totals', 'label' => $this->translator->get('orders.fields.totals'), 'sortable' => true, 'sort_key' => 'total_with_tax', 'raw' => true],
['key' => 'shipping', 'label' => $this->translator->get('orders.fields.shipping'), 'raw' => true],
['key' => 'ordered_at', 'label' => $this->translator->get('orders.fields.ordered_at'), 'sortable' => true, 'sort_key' => 'ordered_at'],
],
'rows' => $tableRows,
'pagination' => [
'page' => (int) ($result['page'] ?? 1),
'total_pages' => $totalPages,
'total' => (int) ($result['total'] ?? 0),
'per_page' => (int) ($result['per_page'] ?? 20),
],
'per_page_options' => [20, 50, 100],
'selectable' => true,
'select_name' => 'selected_ids[]',
'select_value_key' => 'id',
'header_actions' => [],
'empty_message' => $this->translator->get('orders.empty'),
'show_actions' => false,
];
if ($request->header('X-Requested-With') === 'XMLHttpRequest') {
$tableHtml = $this->template->render('components/table-list', [
'tableList' => $tableListData,
]);
$panelHtml = $this->template->render('components/order-status-panel', [
'statusPanelList' => $statusPanel,
'statusPanelTitle' => 'Statusy',
]);
return Response::json([
'tableHtml' => $tableHtml,
'panelHtml' => $panelHtml,
]);
}
$html = $this->template->render('orders/list', [
'title' => $this->translator->get('orders.title'),
'activeMenu' => 'orders',
'activeOrders' => 'list',
'user' => $this->auth->user(),
'csrfToken' => Csrf::token(),
'tableList' => [
'list_key' => 'orders',
'base_path' => '/orders/list',
'query' => $filters,
'filters' => [
[
'key' => 'search',
'label' => $this->translator->get('orders.filters.search'),
'type' => 'text',
'value' => $filters['search'],
],
[
'key' => 'source',
'label' => $this->translator->get('orders.filters.source'),
'type' => 'select',
'value' => $filters['source'],
'options' => ['' => $this->translator->get('orders.filters.any')] + $sourceOptions,
],
[
'key' => 'status',
'label' => $this->translator->get('orders.filters.status'),
'type' => 'select',
'value' => $filters['status'],
'options' => ['' => $this->translator->get('orders.filters.any')] + $statusOptions,
],
[
'key' => 'payment_status',
'label' => $this->translator->get('orders.filters.payment_status'),
'type' => 'select',
'value' => $filters['payment_status'],
'options' => $this->paymentStatusFilterOptions(),
],
[
'key' => 'date_from',
'label' => $this->translator->get('orders.filters.date_from'),
'type' => 'date',
'value' => $filters['date_from'],
],
[
'key' => 'date_to',
'label' => $this->translator->get('orders.filters.date_to'),
'type' => 'date',
'value' => $filters['date_to'],
],
],
'columns' => [
['key' => 'order_ref', 'label' => $this->translator->get('orders.fields.order_ref'), 'sortable' => true, 'sort_key' => 'source_order_id', 'raw' => true],
['key' => 'buyer', 'label' => $this->translator->get('orders.fields.buyer'), 'raw' => true],
['key' => 'status_badges', 'label' => $this->translator->get('orders.fields.status'), 'sortable' => true, 'sort_key' => 'external_status_id', 'raw' => true],
['key' => 'products', 'label' => $this->translator->get('orders.fields.products'), 'raw' => true],
['key' => 'totals', 'label' => $this->translator->get('orders.fields.totals'), 'sortable' => true, 'sort_key' => 'total_with_tax', 'raw' => true],
['key' => 'shipping', 'label' => $this->translator->get('orders.fields.shipping'), 'raw' => true],
['key' => 'ordered_at', 'label' => $this->translator->get('orders.fields.ordered_at'), 'sortable' => true, 'sort_key' => 'ordered_at'],
],
'rows' => $tableRows,
'pagination' => [
'page' => (int) ($result['page'] ?? 1),
'total_pages' => $totalPages,
'total' => (int) ($result['total'] ?? 0),
'per_page' => (int) ($result['per_page'] ?? 20),
],
'per_page_options' => [20, 50, 100],
'selectable' => true,
'select_name' => 'selected_ids[]',
'select_value_key' => 'id',
'header_actions' => [],
'empty_message' => $this->translator->get('orders.empty'),
'show_actions' => false,
],
'tableList' => $tableListData,
'stats' => $stats,
'statusPanel' => $statusPanel,
'allStatuses' => $this->buildAllStatusOptions($statusConfig),
@@ -173,7 +190,7 @@ final class OrdersController
$notes = is_array($details['notes'] ?? null) ? $details['notes'] : [];
$history = is_array($details['status_history'] ?? null) ? $details['status_history'] : [];
$activityLog = is_array($details['activity_log'] ?? null) ? $details['activity_log'] : [];
$statusCode = (string) (($order['effective_status_id'] ?? '') !== '' ? $order['effective_status_id'] : ($order['external_status_id'] ?? ''));
$statusCode = (string) (($order['effective_status_id'] ?? '') !== '' ? $order['effective_status_id'] : ($order['status_code'] ?? ''));
$statusCounts = $this->orders->statusCounts();
$statusConfig = $this->orders->statusPanelConfig();
$statusLabelMap = $this->statusLabelMap($statusConfig);
@@ -280,7 +297,7 @@ final class OrdersController
$oldDetails = $this->orders->findDetails($orderId);
$oldOrder = is_array($oldDetails['order'] ?? null) ? $oldDetails['order'] : [];
$oldStatus = strtolower(trim((string) ($oldOrder['external_status_id'] ?? '')));
$oldStatus = strtolower(trim((string) ($oldOrder['status_code'] ?? '')));
$success = $this->orders->updateOrderStatus($orderId, $newStatus, 'user', $actorName !== '' ? $actorName : null);
@@ -336,7 +353,7 @@ final class OrdersController
$buyerName = trim((string) ($row['buyer_name'] ?? ''));
$buyerEmail = trim((string) ($row['buyer_email'] ?? ''));
$buyerCity = trim((string) ($row['buyer_city'] ?? ''));
$status = trim((string) (($row['effective_status_id'] ?? '') !== '' ? $row['effective_status_id'] : ($row['external_status_id'] ?? '')));
$status = trim((string) (($row['effective_status_id'] ?? '') !== '' ? $row['effective_status_id'] : ($row['status_code'] ?? '')));
$currency = trim((string) ($row['currency'] ?? ''));
$totalWithTax = $row['total_with_tax'] !== null ? number_format((float) $row['total_with_tax'], 2, '.', ' ') : '-';
$totalPaid = $row['total_paid'] !== null ? number_format((float) $row['total_paid'], 2, '.', ' ') : '-';
@@ -349,11 +366,17 @@ final class OrdersController
$shipments = max(0, (int) ($row['shipments_count'] ?? 0));
$documents = max(0, (int) ($row['documents_count'] ?? 0));
$itemsPreview = is_array($row['items_preview'] ?? null) ? $row['items_preview'] : [];
$projectsDone = max(0, (int) ($row['projects_done'] ?? 0));
$projectsTotal = max(0, (int) ($row['projects_total'] ?? 0));
$previewBtn = '<button type="button" class="btn-icon js-order-preview-btn" data-order-id="' . (int) ($row['id'] ?? 0) . '" title="Podglad">'
. '<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>'
. '</button>';
return [
'id' => (int) ($row['id'] ?? 0),
'order_ref' => '<div class="orders-ref">'
. '<div class="orders-ref__main"><a href="/orders/' . (int) ($row['id'] ?? 0) . '">'
. '<div class="orders-ref__main">' . $previewBtn . '<a href="/orders/' . (int) ($row['id'] ?? 0) . '">'
. htmlspecialchars($internalOrderNumber !== '' ? $internalOrderNumber : ('#' . (string) ($row['id'] ?? 0)), ENT_QUOTES, 'UTF-8')
. '</a></div>'
. '<div class="orders-ref__meta">'
@@ -371,7 +394,7 @@ final class OrdersController
'status_badges' => '<div class="orders-status-wrap" data-order-id="' . (int) ($row['id'] ?? 0) . '" data-current-status="' . htmlspecialchars($status, ENT_QUOTES, 'UTF-8') . '">'
. $this->statusBadge($status, $this->statusLabel($status, $statusLabelMap), $statusColorMap[strtolower(trim($status))] ?? '')
. '</div>',
'products' => $this->productsHtml($itemsPreview, $itemsCount, $itemsQty),
'products' => $this->productsHtml($itemsPreview, $itemsCount, $itemsQty, $projectsDone, $projectsTotal),
'totals' => '<div class="orders-money">'
. '<div class="orders-money__main">' . htmlspecialchars($totalWithTax . ' ' . $currency, ENT_QUOTES, 'UTF-8') . ($isUnpaid ? ' <span class="order-tag is-unpaid">Nieopłacone</span>' : '') . '</div>'
. '<div class="orders-money__meta">' . ($isCod ? '<span class="order-tag is-cod">Za pobraniem</span>' : 'oplacono: ' . htmlspecialchars($totalPaid . ' ' . $currency, ENT_QUOTES, 'UTF-8')) . '</div>'
@@ -671,7 +694,7 @@ final class OrdersController
/**
* @param array<int, array<string, mixed>> $itemsPreview
*/
private function productsHtml(array $itemsPreview, int $itemsCount, string $itemsQty): string
private function productsHtml(array $itemsPreview, int $itemsCount, string $itemsQty, int $projectsDone = 0, int $projectsTotal = 0): string
{
if ($itemsPreview === []) {
return '<div class="orders-products">'
@@ -704,12 +727,37 @@ final class OrdersController
if ($itemsCount > count($itemsPreview)) {
$html .= '<div class="orders-products__more">+' . ($itemsCount - count($itemsPreview)) . ' pozycji</div>';
}
$html .= '<div class="orders-products__meta">' . $itemsCount . ' pozycji / ' . htmlspecialchars($itemsQty, ENT_QUOTES, 'UTF-8') . ' szt.</div>';
$html .= '<div class="orders-products__meta">' . $itemsCount . ' pozycji / ' . htmlspecialchars($itemsQty, ENT_QUOTES, 'UTF-8') . ' szt.'
. $this->projectBadge($projectsDone, $projectsTotal)
. '</div>';
$html .= '</div>';
return $html;
}
private function projectBadge(int $done, int $total): string
{
if ($total === 0) {
return '';
}
if ($done === $total) {
return ' <span class="project-badge project-badge--done" title="Wszystkie projekty wygenerowane (' . $done . '/' . $total . ')">'
. '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6L9 17l-5-5"/></svg>'
. '</span>';
}
if ($done > 0) {
return ' <span class="project-badge project-badge--partial" title="Projekty: ' . $done . '/' . $total . '">'
. $done . '/' . $total
. '</span>';
}
return ' <span class="project-badge project-badge--none" title="Brak wygenerowanych projektow (0/' . $total . ')">'
. '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/></svg>'
. '</span>';
}
private function shippingHtml(string $deliveryMethod, int $shipments, int $documents): string
{
$deliveryMethod = trim(html_entity_decode(strip_tags($deliveryMethod), ENT_QUOTES | ENT_HTML5, 'UTF-8'));
@@ -961,4 +1009,35 @@ final class OrdersController
return Response::json(['results' => $results]);
}
public function preview(Request $request): Response
{
$orderId = max(0, (int) $request->input('id', 0));
$details = $this->orders->findDetails($orderId);
if ($details === null) {
return Response::html('<div class="order-preview-error">Zamowienie nie znalezione.</div>', 404);
}
$order = is_array($details['order'] ?? null) ? $details['order'] : [];
$items = is_array($details['items'] ?? null) ? $details['items'] : [];
$addresses = is_array($details['addresses'] ?? null) ? $details['addresses'] : [];
$notes = is_array($details['notes'] ?? null) ? $details['notes'] : [];
$addressByType = ['customer' => null, 'delivery' => null, 'invoice' => null];
foreach ($addresses as $address) {
$type = (string) ($address['address_type'] ?? '');
if ($type !== '' && array_key_exists($type, $addressByType) && $addressByType[$type] === null) {
$addressByType[$type] = $address;
}
}
$html = $this->template->render('orders/partials/preview-content', [
'order' => $order,
'items' => $items,
'addressByType' => $addressByType,
'notes' => $notes,
]);
return Response::html($html);
}
}