db = $db; } /** * @return array{items: array>, total: int} */ public function listForAdmin( array $filters, string $sortColumn = 'date_order', string $sortDir = 'DESC', int $page = 1, int $perPage = 15 ): array { $allowedSortColumns = [ 'id' => 'q1.id', 'number' => 'q1.number', 'date_order' => 'q1.date_order', 'status' => 'q1.status', 'summary' => 'q1.summary', 'client' => 'q1.client', 'order_email' => 'q1.order_email', 'client_phone' => 'q1.client_phone', 'transport' => 'q1.transport', 'payment_method' => 'q1.payment_method', 'total_orders' => 'shop_order.total_orders', 'paid' => 'q1.paid', ]; $sortSql = $allowedSortColumns[$sortColumn] ?? 'q1.date_order'; $sortDir = strtoupper(trim($sortDir)) === 'ASC' ? 'ASC' : 'DESC'; $page = max(1, $page); $perPage = min(self::MAX_PER_PAGE, max(1, $perPage)); $offset = ($page - 1) * $perPage; $where = []; $params = []; $number = $this->normalizeTextFilter($filters['number'] ?? ''); if ($number !== '') { $where[] = 'q1.number LIKE :number'; $params[':number'] = '%' . $number . '%'; } $dateFrom = $this->normalizeDateFilter($filters['date_from'] ?? ''); if ($dateFrom !== null) { $where[] = 'q1.date_order >= :date_from'; $params[':date_from'] = $dateFrom . ' 00:00:00'; } $dateTo = $this->normalizeDateFilter($filters['date_to'] ?? ''); if ($dateTo !== null) { $where[] = 'q1.date_order <= :date_to'; $params[':date_to'] = $dateTo . ' 23:59:59'; } $status = trim((string)($filters['status'] ?? '')); if ($status !== '' && is_numeric($status)) { $where[] = 'q1.status = :status'; $params[':status'] = (int)$status; } $client = $this->normalizeTextFilter($filters['client'] ?? ''); if ($client !== '') { $where[] = 'q1.client LIKE :client'; $params[':client'] = '%' . $client . '%'; } $address = $this->normalizeTextFilter($filters['address'] ?? ''); if ($address !== '') { $where[] = 'q1.address LIKE :address'; $params[':address'] = '%' . $address . '%'; } $email = $this->normalizeTextFilter($filters['order_email'] ?? ''); if ($email !== '') { $where[] = 'q1.order_email LIKE :order_email'; $params[':order_email'] = '%' . $email . '%'; } $phone = $this->normalizeTextFilter($filters['client_phone'] ?? ''); if ($phone !== '') { $where[] = 'q1.client_phone LIKE :client_phone'; $params[':client_phone'] = '%' . $phone . '%'; } $transport = $this->normalizeTextFilter($filters['transport'] ?? ''); if ($transport !== '') { $where[] = 'q1.transport LIKE :transport'; $params[':transport'] = '%' . $transport . '%'; } $payment = $this->normalizeTextFilter($filters['payment_method'] ?? ''); if ($payment !== '') { $where[] = 'q1.payment_method LIKE :payment_method'; $params[':payment_method'] = '%' . $payment . '%'; } $whereSql = ''; if (!empty($where)) { $whereSql = ' WHERE ' . implode(' AND ', $where); } $baseSql = " FROM ( SELECT id, number, date_order, CONCAT(client_name, ' ', client_surname) AS client, client_email AS order_email, CONCAT(client_street, ', ', client_postal_code, ' ', client_city) AS address, status, client_phone, transport, payment_method, summary, paid FROM pp_shop_orders AS pso ) AS q1 LEFT JOIN ( SELECT client_email, COUNT(*) AS total_orders FROM pp_shop_orders WHERE client_name IS NOT NULL AND client_surname IS NOT NULL AND client_email IS NOT NULL GROUP BY client_email ) AS shop_order ON q1.order_email = shop_order.client_email "; $sqlCount = 'SELECT COUNT(0) ' . $baseSql . $whereSql; $stmtCount = $this->db->query($sqlCount, $params); $countRows = $stmtCount ? $stmtCount->fetchAll() : []; $total = 0; if (is_array($countRows) && isset($countRows[0]) && is_array($countRows[0])) { $firstRow = $countRows[0]; $firstValue = reset($firstRow); $total = $firstValue !== false ? (int)$firstValue : 0; } $sql = ' SELECT q1.*, COALESCE(shop_order.total_orders, 0) AS total_orders ' . $baseSql . $whereSql . ' ORDER BY ' . $sortSql . ' ' . $sortDir . ', q1.id DESC' . ' LIMIT ' . $perPage . ' OFFSET ' . $offset; $stmt = $this->db->query($sql, $params); if (!$stmt) { return [ 'items' => [], 'total' => $total, ]; } $items = $stmt ? $stmt->fetchAll() : []; if (!is_array($items)) { $items = []; } foreach ($items as &$item) { $item['id'] = (int)($item['id'] ?? 0); $item['status'] = (int)($item['status'] ?? 0); $item['paid'] = (int)($item['paid'] ?? 0); $item['summary'] = (float)($item['summary'] ?? 0); $item['total_orders'] = (int)($item['total_orders'] ?? 0); $item['number'] = (string)($item['number'] ?? ''); $item['date_order'] = (string)($item['date_order'] ?? ''); $item['client'] = trim((string)($item['client'] ?? '')); $item['order_email'] = (string)($item['order_email'] ?? ''); $item['address'] = trim((string)($item['address'] ?? '')); $item['client_phone'] = (string)($item['client_phone'] ?? ''); $item['transport'] = (string)($item['transport'] ?? ''); $item['payment_method'] = (string)($item['payment_method'] ?? ''); } unset($item); return [ 'items' => $items, 'total' => $total, ]; } public function findForAdmin(int $orderId): array { if ($orderId <= 0) { return []; } $order = $this->db->get('pp_shop_orders', '*', ['id' => $orderId]); if (!is_array($order)) { return []; } $order['id'] = (int)($order['id'] ?? 0); $order['status'] = (int)($order['status'] ?? 0); $order['paid'] = (int)($order['paid'] ?? 0); $order['summary'] = (float)($order['summary'] ?? 0); $order['transport_cost'] = (float)($order['transport_cost'] ?? 0); $order['products'] = $this->orderProducts($orderId); $order['statuses'] = $this->orderStatusHistory($orderId); return $order; } public function orderProducts(int $orderId): array { if ($orderId <= 0) { return []; } $rows = $this->db->select('pp_shop_order_products', '*', [ 'order_id' => $orderId, ]); return is_array($rows) ? $rows : []; } public function orderStatusHistory(int $orderId): array { if ($orderId <= 0) { return []; } $rows = $this->db->select('pp_shop_order_statuses', '*', [ 'order_id' => $orderId, 'ORDER' => ['id' => 'DESC'], ]); return is_array($rows) ? $rows : []; } public function orderStatuses(): array { $data = $this->orderStatusData(); return $data['names']; } /** * Zwraca nazwy i kolory statusów w jednym zapytaniu. * * @return array{names: array, colors: array} */ public function orderStatusData(): array { $rows = $this->db->select('pp_shop_statuses', ['id', 'status', 'color'], [ 'ORDER' => ['o' => 'ASC'], ]); $names = []; $colors = []; if (!is_array($rows)) { return ['names' => $names, 'colors' => $colors]; } foreach ($rows as $row) { $id = (int)($row['id'] ?? 0); if ($id < 0) { continue; } $names[$id] = (string)($row['status'] ?? ''); $color = trim((string)($row['color'] ?? '')); if ($color !== '' && preg_match('/^#[0-9a-fA-F]{3,6}$/', $color)) { $colors[$id] = $color; } } return ['names' => $names, 'colors' => $colors]; } public function nextOrderId(int $orderId): ?int { if ($orderId <= 0) { return null; } $next = $this->db->get('pp_shop_orders', 'id', [ 'id[>]' => $orderId, 'ORDER' => ['id' => 'ASC'], 'LIMIT' => 1, ]); if (!$next) { return null; } return (int)$next; } public function prevOrderId(int $orderId): ?int { if ($orderId <= 0) { return null; } $prev = $this->db->get('pp_shop_orders', 'id', [ 'id[<]' => $orderId, 'ORDER' => ['id' => 'DESC'], 'LIMIT' => 1, ]); if (!$prev) { return null; } return (int)$prev; } public function saveNotes(int $orderId, string $notes): bool { if ($orderId <= 0) { return false; } $this->db->update('pp_shop_orders', ['notes' => $notes], ['id' => $orderId]); $this->touchUpdatedAt($orderId); return true; } public function saveOrderByAdmin( int $orderId, string $clientName, string $clientSurname, string $clientStreet, string $clientPostalCode, string $clientCity, string $clientEmail, string $firmName, string $firmStreet, string $firmPostalCode, string $firmCity, string $firmNip, int $transportId, string $inpostPaczkomat, int $paymentMethodId ): bool { if ($orderId <= 0) { return false; } $transportName = $this->db->get('pp_shop_transports', 'name_visible', ['id' => $transportId]); $transportCost = $this->db->get('pp_shop_transports', 'cost', ['id' => $transportId]); $transportDescription = $this->db->get('pp_shop_transports', 'description', ['id' => $transportId]); $paymentMethodName = $this->db->get('pp_shop_payment_methods', 'name', ['id' => $paymentMethodId]); $this->db->update('pp_shop_orders', [ 'client_name' => $clientName, 'client_surname' => $clientSurname, 'client_street' => $clientStreet, 'client_postal_code' => $clientPostalCode, 'client_city' => $clientCity, 'client_email' => $clientEmail, 'firm_name' => $this->nullableString($firmName), 'firm_street' => $this->nullableString($firmStreet), 'firm_postal_code' => $this->nullableString($firmPostalCode), 'firm_city' => $this->nullableString($firmCity), 'firm_nip' => $this->nullableString($firmNip), 'transport_id' => $transportId, 'transport' => $transportName ?: null, 'transport_cost' => $transportCost !== null ? $transportCost : 0, 'transport_description' => $transportDescription ?: null, 'inpost_paczkomat' => $inpostPaczkomat, 'payment_method_id' => $paymentMethodId, 'payment_method' => $paymentMethodName ?: null, ], [ 'id' => $orderId, ]); $this->db->update('pp_shop_orders', [ 'summary' => $this->calculateOrderSummaryByAdmin($orderId), ], [ 'id' => $orderId, ]); $this->touchUpdatedAt($orderId); return true; } public function calculateOrderSummaryByAdmin(int $orderId): float { if ($orderId <= 0) { return 0.0; } $rows = $this->db->select('pp_shop_order_products', [ 'price_brutto', 'price_brutto_promo', 'quantity', ], [ 'order_id' => $orderId, ]); $summary = 0.0; if (is_array($rows)) { foreach ($rows as $row) { $quantity = (float)($row['quantity'] ?? 0); $pricePromo = (float)($row['price_brutto_promo'] ?? 0); $price = (float)($row['price_brutto'] ?? 0); if ($pricePromo > 0) { $summary += $pricePromo * $quantity; } else { $summary += $price * $quantity; } } } $transportCost = (float)$this->db->get('pp_shop_orders', 'transport_cost', ['id' => $orderId]); return (float)$summary + $transportCost; } public function toggleTrustmateSend(int $orderId): ?int { if ($orderId <= 0) { return null; } $order = $this->db->get('pp_shop_orders', ['trustmate_send'], ['id' => $orderId]); if (!is_array($order)) { return null; } $newValue = ((int)($order['trustmate_send'] ?? 0) === 1) ? 0 : 1; $this->db->update('pp_shop_orders', ['trustmate_send' => $newValue], ['id' => $orderId]); return $newValue; } public function deleteOrder(int $orderId): bool { if ($orderId <= 0) { return false; } $this->db->delete('pp_shop_orders', ['id' => $orderId]); return true; } // --- Order product CRUD (admin) --- public function getOrderProduct(int $orderProductId): ?array { if ($orderProductId <= 0) { return null; } $row = $this->db->get('pp_shop_order_products', '*', ['id' => $orderProductId]); return is_array($row) ? $row : null; } public function addOrderProduct(int $orderId, array $data): ?int { if ($orderId <= 0) { return null; } $this->db->insert('pp_shop_order_products', [ 'order_id' => $orderId, 'product_id' => (int)($data['product_id'] ?? 0), 'parent_product_id' => (int)($data['parent_product_id'] ?? 0), 'name' => (string)($data['name'] ?? ''), 'attributes' => (string)($data['attributes'] ?? ''), 'vat' => (float)($data['vat'] ?? 0), 'price_brutto' => (float)($data['price_brutto'] ?? 0), 'price_brutto_promo' => (float)($data['price_brutto_promo'] ?? 0), 'quantity' => max(1, (int)($data['quantity'] ?? 1)), 'message' => (string)($data['message'] ?? ''), 'custom_fields' => (string)($data['custom_fields'] ?? ''), ]); $id = $this->db->id(); return $id ? (int)$id : null; } public function updateOrderProduct(int $orderProductId, array $data): bool { if ($orderProductId <= 0) { return false; } $update = []; if (array_key_exists('quantity', $data)) { $update['quantity'] = max(1, (int)$data['quantity']); } if (array_key_exists('price_brutto', $data)) { $update['price_brutto'] = (float)$data['price_brutto']; } if (array_key_exists('price_brutto_promo', $data)) { $update['price_brutto_promo'] = (float)$data['price_brutto_promo']; } if (empty($update)) { return false; } $this->db->update('pp_shop_order_products', $update, ['id' => $orderProductId]); return true; } public function deleteOrderProduct(int $orderProductId): bool { if ($orderProductId <= 0) { return false; } $this->db->delete('pp_shop_order_products', ['id' => $orderProductId]); return true; } public function updateTransportCost(int $orderId, float $cost): void { if ($orderId <= 0) { return; } $this->db->update('pp_shop_orders', ['transport_cost' => $cost], ['id' => $orderId]); } // --- Frontend methods --- public function findIdByHash(string $hash) { $hash = trim($hash); if ($hash === '') { return null; } $id = $this->db->get('pp_shop_orders', 'id', ['hash' => $hash]); return $id ? (int)$id : null; } public function findHashById(int $orderId) { if ($orderId <= 0) { return null; } $hash = $this->db->get('pp_shop_orders', 'hash', ['id' => $orderId]); return $hash ? (string)$hash : null; } public function orderDetailsFrontend($orderId = null, $hash = '', $przelewy24Hash = '') { $order = null; if ($orderId) { $order = $this->db->get('pp_shop_orders', '*', ['id' => $orderId]); if (is_array($order)) { $order['products'] = $this->db->select('pp_shop_order_products', '*', ['order_id' => $orderId]); if (!is_array($order['products'])) { $order['products'] = []; } } } if ($hash) { $order = $this->db->get('pp_shop_orders', '*', ['hash' => $hash]); if (is_array($order)) { $order['products'] = $this->db->select('pp_shop_order_products', '*', ['order_id' => $order['id']]); if (!is_array($order['products'])) { $order['products'] = []; } } } if ($przelewy24Hash) { $order = $this->db->get('pp_shop_orders', '*', ['przelewy24_hash' => $przelewy24Hash]); if (is_array($order)) { $order['products'] = $this->db->select('pp_shop_order_products', '*', ['order_id' => $order['id']]); if (!is_array($order['products'])) { $order['products'] = []; } } } return is_array($order) ? $order : null; } public function generateOrderNumber() { $date = date('Y-m'); $stmt = $this->db->query( 'SELECT MAX( CONVERT( substring_index( substring_index( number, \'/\', -1 ), \' \', -1 ), UNSIGNED INTEGER) ) FROM pp_shop_orders WHERE date_order LIKE \'' . $date . '%\'' ); $results = $stmt ? $stmt->fetchAll() : []; $nr = 0; if (is_array($results) && count($results)) { foreach ($results as $row) { $nr = ++$row[0]; } } if (!$nr) { $nr = 1; } if ($nr < 10) { $nr = '00' . $nr; } elseif ($nr < 100) { $nr = '0' . $nr; } return date('Y/m', strtotime($date)) . '/' . $nr; } public function createFromBasket( $client_id, $basket, $transport_id, $payment_id, $email, $phone, $name, $surname, $street, $postal_code, $city, $firm_name, $firm_street, $firm_postal_code, $firm_city, $firm_nip, $inpost_info, $orlen_point_id, $orlen_point_info, $coupon, $basket_message ) { global $lang_id, $settings; if ($client_id) { $email = (new \Domain\Client\ClientRepository($this->db))->clientEmail((int)$client_id); } if (!is_array($basket) || !$transport_id || !$payment_id || !$email || !$phone || !$name || !$surname) { return false; } $transport = ( new \Domain\Transport\TransportRepository( $this->db ) )->findActiveByIdCached( $transport_id ); $payment_method = ( new \Domain\PaymentMethod\PaymentMethodRepository( $this->db ) )->findActiveById( (int)$payment_id ); $productRepo = new \Domain\Product\ProductRepository($this->db); $basket_summary = \Domain\Basket\BasketCalculator::summaryPrice($basket, $coupon, $lang_id, $productRepo); $order_number = $this->generateOrderNumber(); $order_date = date('Y-m-d H:i:s'); $hash = md5($order_number . time()); if ($transport['delivery_free'] == 1 && $basket_summary >= $settings['free_delivery']) { $transport_cost = '0.00'; } else { $transport_cost = $transport['cost']; } $this->db->insert('pp_shop_orders', [ 'number' => $order_number, 'client_id' => $client_id ? $client_id : null, 'date_order' => $order_date, 'comment' => null, 'client_name' => $name, 'client_surname' => $surname, 'client_email' => $email, 'client_street' => $street, 'client_postal_code' => $postal_code, 'client_city' => $city, 'client_phone' => $phone, 'firm_name' => $firm_name ? $firm_name : null, 'firm_street' => $firm_street ? $firm_street : null, 'firm_postal_code' => $firm_postal_code ? $firm_postal_code : null, 'firm_city' => $firm_city ? $firm_city : null, 'firm_nip' => $firm_nip ? $firm_nip : null, 'transport_id' => $transport_id, 'transport' => $transport['name_visible'], 'transport_cost' => $transport_cost, 'transport_description' => $transport['description'], 'orlen_point' => ($orlen_point_id) ? $orlen_point_id . ' | ' . $orlen_point_info : null, 'inpost_paczkomat' => ($transport_id == 1 || $transport_id == 2) ? $inpost_info : null, 'payment_method' => $payment_method['name'], 'payment_method_id' => $payment_id, 'hash' => $hash, 'summary' => \Shared\Helpers\Helpers::normalize_decimal($basket_summary + $transport_cost), 'coupon_id' => $coupon ? $coupon->id : null, 'message' => $basket_message ? $basket_message : null, 'apilo_order_status_date' => date('Y-m-d H:i:s'), 'updated_at' => $order_date, ]); $order_id = $this->db->id(); if (!$order_id) { return false; } if ($coupon) { (new \Domain\Coupon\CouponRepository($this->db))->incrementUsedCount((int)$coupon->id); } // ustawienie statusu zamówienia $this->db->insert('pp_shop_order_statuses', ['order_id' => $order_id, 'status_id' => 0, 'mail' => 1]); if (is_array($basket)) { $attributeRepo = new \Domain\Attribute\AttributeRepository($this->db); foreach ($basket as $basket_position) { $attributes = ''; $product = $productRepo->findCached($basket_position['product-id'], $lang_id); if (is_array($basket_position['attributes'])) { foreach ($basket_position['attributes'] as $row) { $row = explode('-', $row); $attribute = $attributeRepo->frontAttributeDetails((int)$row[0], $lang_id); $value = $attributeRepo->frontValueDetails((int)$row[1], $lang_id); if ($attributes) { $attributes .= '
'; } $attributes .= '' . $attribute['language']['name'] . ': '; $attributes .= $value['language']['name']; } } // custom fields $product_custom_fields = ''; if (is_array($basket_position['custom_fields'])) { foreach ($basket_position['custom_fields'] as $key => $val) { $custom_field = $productRepo->findCustomFieldCached($key); if ($product_custom_fields) { $product_custom_fields .= '
'; } $product_custom_fields .= '' . $custom_field['name'] . ': ' . $val; } } $product_price_tmp = \Domain\Basket\BasketCalculator::calculateBasketProductPrice((float)$product['price_brutto_promo'], (float)$product['price_brutto'], $coupon, $basket_position, $productRepo); // Cena promo = 0 gdy taka sama jak cena bazowa (brak realnej promocji/kuponu). // Porównujemy po zaokrągleniu do 2 miejsc, żeby uniknąć artefaktów float (IEEE 754). $effectivePromoPrice = round((float)$product_price_tmp['price_new'], 2); $effectiveBasePrice = round((float)$product_price_tmp['price'], 2); $promoPrice = ($effectivePromoPrice > 0 && $effectivePromoPrice < $effectiveBasePrice) ? $effectivePromoPrice : 0; $this->db->insert('pp_shop_order_products', [ 'order_id' => $order_id, 'product_id' => $basket_position['product-id'], 'parent_product_id' => $basket_position['parent_id'] ? $basket_position['parent_id'] : $basket_position['product-id'], 'name' => $product['language']['name'], 'attributes' => $attributes, 'vat' => $product['vat'], 'price_brutto' => $effectiveBasePrice, 'price_brutto_promo' => $promoPrice, 'quantity' => $basket_position['quantity'], 'message' => $basket_position['message'], 'custom_fields' => $product_custom_fields, ]); $product_quantity = $productRepo->getQuantity($basket_position['product-id']); if ($product_quantity != null) { $this->db->update('pp_shop_products', ['quantity[-]' => $basket_position['quantity']], ['id' => $basket_position['product-id']]); } else { $this->db->update('pp_shop_products', ['quantity[-]' => $basket_position['quantity']], ['id' => $basket_position['parent_id']]); } $this->db->update('pp_shop_products', ['quantity' => 0], ['quantity[<]' => 0]); } } if ($coupon && $coupon->is_one_time()) { $coupon->set_as_used(); } $order = $this->orderDetailsFrontend($order_id); $mail_order = \Shared\Tpl\Tpl::view('shop-order/mail-summary', [ 'settings' => $settings, 'order' => $order, 'coupon' => $coupon, ]); $settings['ssl'] ? $base = 'https' : $base = 'http'; $regex = "-(]+src\s*=\s*['\"])(((?!'|\"|https?://).)*)(['\"][^>]*>)-i"; $mail_order = preg_replace($regex, "$1" . $base . "://" . $_SERVER['SERVER_NAME'] . "$2$4", $mail_order); $regex = "-(]+href\s*=\s*['\"])(((?!'|\"|https?://).)*)(['\"][^>]*>)-i"; $mail_order = preg_replace($regex, "$1" . $base . "://" . $_SERVER['SERVER_NAME'] . "$2$4", $mail_order); \Shared\Helpers\Helpers::send_email($email, \Shared\Helpers\Helpers::lang('potwierdzenie-zamowienia-ze-sklepu') . ' ' . $settings['firm_name'], $mail_order); \Shared\Helpers\Helpers::send_email($settings['contact_email'], 'Nowe zamówienie / ' . $settings['firm_name'] . ' / ' . $order['number'] . ' - ' . $order['client_surname'] . ' ' . $order['client_name'], $mail_order); // zmiana statusu w realizacji jeżeli płatność przy odbiorze if ($payment_id == 3) { $this->updateOrderStatus($order_id, 4); $this->insertStatusHistory($order_id, 4, 1); } return $order_id; } // ========================================================================= // Low-level helpers (used by OrderAdminService) // ========================================================================= public function getDb() { return $this->db; } public function findRawById(int $orderId): ?array { if ($orderId <= 0) return null; $result = $this->db->get('pp_shop_orders', '*', ['id' => $orderId]); return is_array($result) ? $result : null; } public function findRawByHash(string $hash): ?array { if ($hash === '') return null; $result = $this->db->get('pp_shop_orders', '*', ['hash' => $hash]); return is_array($result) ? $result : null; } public function findRawByPrzelewy24Hash(string $hash): ?array { if ($hash === '') return null; $result = $this->db->get('pp_shop_orders', '*', ['przelewy24_hash' => $hash]); return is_array($result) ? $result : null; } public function setAsPaid(int $orderId): void { $this->db->update('pp_shop_orders', ['paid' => 1], ['id' => $orderId]); $this->touchUpdatedAt($orderId); } public function setAsUnpaid(int $orderId): void { $this->db->update('pp_shop_orders', ['paid' => 0], ['id' => $orderId]); $this->touchUpdatedAt($orderId); } public function updateOrderStatus(int $orderId, int $status): bool { $result = (bool)$this->db->update('pp_shop_orders', ['status' => $status], ['id' => $orderId]); if ($result) { $this->touchUpdatedAt($orderId); } return $result; } public function insertStatusHistory(int $orderId, int $statusId, int $mail): void { $this->db->insert('pp_shop_order_statuses', [ 'order_id' => $orderId, 'status_id' => $statusId, 'mail' => $mail, ]); } public function updateApiloStatusDate(int $orderId, string $date): void { $this->db->update('pp_shop_orders', ['apilo_order_status_date' => $date], ['id' => $orderId]); } // ========================================================================= // API methods (for ordersPRO) // ========================================================================= public function listForApi(array $filters, int $page = 1, int $perPage = 50): array { $page = max(1, $page); $perPage = min(self::MAX_PER_PAGE, max(1, $perPage)); $offset = ($page - 1) * $perPage; $where = []; $params = []; $status = trim((string)($filters['status'] ?? '')); if ($status !== '' && is_numeric($status)) { $where[] = 'o.status = :status'; $params[':status'] = (int)$status; } $paid = trim((string)($filters['paid'] ?? '')); if ($paid !== '' && is_numeric($paid)) { $where[] = 'o.paid = :paid'; $params[':paid'] = (int)$paid; } $dateFrom = $this->normalizeDateFilter($filters['date_from'] ?? ''); if ($dateFrom !== null) { $where[] = 'o.date_order >= :date_from'; $params[':date_from'] = $dateFrom . ' 00:00:00'; } $dateTo = $this->normalizeDateFilter($filters['date_to'] ?? ''); if ($dateTo !== null) { $where[] = 'o.date_order <= :date_to'; $params[':date_to'] = $dateTo . ' 23:59:59'; } $updatedSince = trim((string)($filters['updated_since'] ?? '')); if ($updatedSince !== '' && preg_match('/^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}$/', $updatedSince)) { $where[] = 'o.updated_at >= :updated_since'; $params[':updated_since'] = $updatedSince; } $number = $this->normalizeTextFilter($filters['number'] ?? ''); if ($number !== '') { $where[] = 'o.number LIKE :number'; $params[':number'] = '%' . $number . '%'; } $client = $this->normalizeTextFilter($filters['client'] ?? ''); if ($client !== '') { $where[] = "(o.client_name LIKE :client OR o.client_surname LIKE :client2 OR o.client_email LIKE :client3)"; $params[':client'] = '%' . $client . '%'; $params[':client2'] = '%' . $client . '%'; $params[':client3'] = '%' . $client . '%'; } $whereSql = ''; if (!empty($where)) { $whereSql = ' WHERE ' . implode(' AND ', $where); } $sqlCount = 'SELECT COUNT(0) FROM pp_shop_orders AS o' . $whereSql; $stmtCount = $this->db->query($sqlCount, $params); $countRows = $stmtCount ? $stmtCount->fetchAll() : []; $total = 0; if (is_array($countRows) && isset($countRows[0]) && is_array($countRows[0])) { $firstValue = reset($countRows[0]); $total = $firstValue !== false ? (int)$firstValue : 0; } $sql = 'SELECT o.id, o.number, o.date_order, o.updated_at, o.status, o.paid,' . ' o.client_name, o.client_surname, o.client_email, o.client_phone,' . ' o.client_street, o.client_postal_code, o.client_city,' . ' o.firm_name, o.firm_nip,' . ' o.transport, o.transport_cost, o.payment_method, o.summary' . ' FROM pp_shop_orders AS o' . $whereSql . ' ORDER BY o.updated_at DESC, o.id DESC' . ' LIMIT ' . $perPage . ' OFFSET ' . $offset; $stmt = $this->db->query($sql, $params); $items = ($stmt) ? $stmt->fetchAll() : []; if (!is_array($items)) { $items = []; } foreach ($items as &$item) { $item['id'] = (int)($item['id'] ?? 0); $item['status'] = (int)($item['status'] ?? 0); $item['paid'] = (int)($item['paid'] ?? 0); $item['summary'] = (float)($item['summary'] ?? 0); $item['transport_cost'] = (float)($item['transport_cost'] ?? 0); } unset($item); return [ 'items' => $items, 'total' => $total, 'page' => $page, 'per_page' => $perPage, ]; } public function findForApi(int $orderId): ?array { if ($orderId <= 0) { return null; } $order = $this->db->get('pp_shop_orders', '*', ['id' => $orderId]); if (!is_array($order)) { return null; } $order['id'] = (int)($order['id'] ?? 0); $order['status'] = (int)($order['status'] ?? 0); $order['paid'] = (int)($order['paid'] ?? 0); $order['summary'] = (float)($order['summary'] ?? 0); $order['transport_cost'] = (float)($order['transport_cost'] ?? 0); $order['products'] = $this->orderProducts($orderId); $order['statuses'] = $this->orderStatusHistory($orderId); return $order; } public function touchUpdatedAt(int $orderId): void { if ($orderId <= 0) { return; } $this->db->update('pp_shop_orders', [ 'updated_at' => date('Y-m-d H:i:s'), ], [ 'id' => $orderId, ]); } private function nullableString(string $value): ?string { $value = trim($value); return $value === '' ? null : $value; } private function normalizeTextFilter($value): string { $value = trim((string)$value); if ($value === '') { return ''; } if (strlen($value) > 255) { return substr($value, 0, 255); } return $value; } private function normalizeDateFilter($value): ?string { $value = trim((string)$value); if ($value === '') { return null; } if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $value)) { return null; } return $value; } }