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 { $rows = $this->db->select('pp_shop_statuses', ['id', 'status'], [ 'ORDER' => ['o' => 'ASC'], ]); if (!is_array($rows)) { return []; } $result = []; foreach ($rows as $row) { $id = (int)($row['id'] ?? 0); if ($id < 0) { continue; } $result[$id] = (string)($row['status'] ?? ''); } return $result; } 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]); 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, ]); 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; } // --- 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'); $results = $this->db->query( 'SELECT MAX( CONVERT( substring_index( substring_index( number, \'/\', -1 ), \' \', -1 ), UNSIGNED INTEGER) ) FROM pp_shop_orders WHERE date_order LIKE \'' . $date . '%\'' )->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 ); $basket_summary = \Domain\Basket\BasketCalculator::summaryPrice($basket, $coupon); $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'), ]); $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)) { foreach ($basket as $basket_position) { $attributes = ''; $productRepo = new \Domain\Product\ProductRepository($this->db); $product = $productRepo->findCached($basket_position['product-id'], $lang_id); if (is_array($basket_position['attributes'])) { foreach ($basket_position['attributes'] as $row) { $row = explode('-', $row); $attributeRepo = new \Domain\Attribute\AttributeRepository($this->db); $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 = (new \Domain\Product\ProductRepository($this->db))->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); $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' => $product_price_tmp['price'], 'price_brutto_promo' => $product_price_tmp['price_new'], '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]); } public function setAsUnpaid(int $orderId): void { $this->db->update('pp_shop_orders', ['paid' => 0], ['id' => $orderId]); } public function updateOrderStatus(int $orderId, int $status): bool { return (bool)$this->db->update('pp_shop_orders', ['status' => $status], ['id' => $orderId]); } 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]); } 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; } }