> */ public function findByOrderId(int $orderId): array { $statement = $this->pdo->prepare( 'SELECT i.*, ic.name AS config_name, ic.is_delegated AS config_is_delegated, ig.name AS integration_name, fis.account_prefix FROM invoices i LEFT JOIN invoice_configs ic ON ic.id = i.config_id LEFT JOIN integrations ig ON ig.id = ic.integration_id AND ig.type = "fakturownia" LEFT JOIN fakturownia_integration_settings fis ON fis.integration_id = ig.id WHERE i.order_id = :order_id ORDER BY i.created_at DESC' ); $statement->execute(['order_id' => $orderId]); $rows = $statement->fetchAll(PDO::FETCH_ASSOC); return is_array($rows) ? $rows : []; } /** * @return array|null */ public function findById(int $id): ?array { $statement = $this->pdo->prepare( 'SELECT i.*, ic.name AS config_name, ic.is_delegated AS config_is_delegated, ig.name AS integration_name, fis.account_prefix FROM invoices i LEFT JOIN invoice_configs ic ON ic.id = i.config_id LEFT JOIN integrations ig ON ig.id = ic.integration_id AND ig.type = "fakturownia" LEFT JOIN fakturownia_integration_settings fis ON fis.integration_id = ig.id WHERE i.id = :id LIMIT 1' ); $statement->execute(['id' => $id]); $row = $statement->fetch(PDO::FETCH_ASSOC); return is_array($row) ? $row : null; } /** * @param array $data */ public function insertLocal(array $data): int { return $this->insert($data, isDelegated: false); } /** * @param array $data */ public function insertDelegated(array $data): int { return $this->insert($data, isDelegated: true); } /** * @param array $data */ private function insert(array $data, bool $isDelegated): int { $statement = $this->pdo->prepare( 'INSERT INTO invoices ( order_id, config_id, invoice_number, issue_date, sale_date, payment_due_date, seller_data_json, buyer_data_json, items_json, total_net, total_gross, order_reference_value, external_invoice_id, external_pdf_url, kind, created_by ) VALUES ( :order_id, :config_id, :invoice_number, :issue_date, :sale_date, :payment_due_date, :seller_data_json, :buyer_data_json, :items_json, :total_net, :total_gross, :order_reference_value, :external_invoice_id, :external_pdf_url, :kind, :created_by )' ); $statement->execute([ 'order_id' => (int) $data['order_id'], 'config_id' => (int) $data['config_id'], 'invoice_number' => (string) $data['invoice_number'], 'issue_date' => (string) $data['issue_date'], 'sale_date' => (string) $data['sale_date'], 'payment_due_date' => $data['payment_due_date'] ?? null, 'seller_data_json' => (string) $data['seller_data_json'], 'buyer_data_json' => $data['buyer_data_json'], 'items_json' => (string) $data['items_json'], 'total_net' => (string) $data['total_net'], 'total_gross' => (string) $data['total_gross'], 'order_reference_value' => $data['order_reference_value'] ?? null, 'external_invoice_id' => $isDelegated ? ($data['external_invoice_id'] ?? null) : null, 'external_pdf_url' => $isDelegated ? ($data['external_pdf_url'] ?? null) : null, 'kind' => (string) ($data['kind'] ?? 'vat'), 'created_by' => $data['created_by'] ?? null, ]); return (int) $this->pdo->lastInsertId(); } public function nextLocalNumber(int $configId, string $numberFormat, string $numberingType): string { $year = (int) date('Y'); $month = $numberingType === 'yearly' ? null : (int) date('n'); if ($month === null) { $this->pdo->prepare( 'INSERT INTO invoice_number_counters (config_id, year, month, last_number) VALUES (:config_id, :year, NULL, 1) ON DUPLICATE KEY UPDATE last_number = last_number + 1' )->execute(['config_id' => $configId, 'year' => $year]); $stmt = $this->pdo->prepare( 'SELECT last_number FROM invoice_number_counters WHERE config_id = :config_id AND year = :year AND month IS NULL' ); $stmt->execute(['config_id' => $configId, 'year' => $year]); } else { $this->pdo->prepare( 'INSERT INTO invoice_number_counters (config_id, year, month, last_number) VALUES (:config_id, :year, :month, 1) ON DUPLICATE KEY UPDATE last_number = last_number + 1' )->execute(['config_id' => $configId, 'year' => $year, 'month' => $month]); $stmt = $this->pdo->prepare( 'SELECT last_number FROM invoice_number_counters WHERE config_id = :config_id AND year = :year AND month = :month' ); $stmt->execute(['config_id' => $configId, 'year' => $year, 'month' => $month]); } $lastNumber = (int) $stmt->fetchColumn(); return str_replace( ['%N', '%M', '%Y'], [ str_pad((string) $lastNumber, 3, '0', STR_PAD_LEFT), str_pad((string) ($month ?? 1), 2, '0', STR_PAD_LEFT), (string) $year, ], $numberFormat ); } /** * @param array $filters * @return array{items: list>, total: int, page: int, per_page: int} */ public function paginate(array $filters): array { $where = []; $params = []; $configId = (int) ($filters['config_id'] ?? 0); if ($configId > 0) { $where[] = 'i.config_id = :config_id'; $params['config_id'] = $configId; } $mode = (string) ($filters['mode'] ?? ''); if ($mode === 'local') { $where[] = 'i.external_invoice_id IS NULL'; } elseif ($mode === 'delegated') { $where[] = 'i.external_invoice_id IS NOT NULL'; } $dateFrom = trim((string) ($filters['date_from'] ?? '')); if ($dateFrom !== '' && strtotime($dateFrom) !== false) { $where[] = 'i.issue_date >= :date_from'; $params['date_from'] = $dateFrom; } $dateTo = trim((string) ($filters['date_to'] ?? '')); if ($dateTo !== '' && strtotime($dateTo) !== false) { $where[] = 'i.issue_date <= :date_to'; $params['date_to'] = $dateTo . ' 23:59:59'; } $search = trim((string) ($filters['search'] ?? '')); if ($search !== '') { $where[] = '(i.invoice_number LIKE :search OR o.internal_order_number LIKE :search2 OR o.external_order_id LIKE :search3)'; $params['search'] = '%' . $search . '%'; $params['search2'] = '%' . $search . '%'; $params['search3'] = '%' . $search . '%'; } $whereClause = $where !== [] ? 'WHERE ' . implode(' AND ', $where) : ''; $countStmt = $this->pdo->prepare( "SELECT COUNT(*) FROM invoices i LEFT JOIN orders o ON o.id = i.order_id {$whereClause}" ); $countStmt->execute($params); $total = (int) $countStmt->fetchColumn(); $page = max(1, (int) ($filters['page'] ?? 1)); $perPage = max(1, min(100, (int) ($filters['per_page'] ?? 50))); $offset = ($page - 1) * $perPage; $stmt = $this->pdo->prepare( "SELECT i.*, ic.name AS config_name, ic.is_delegated AS config_is_delegated, ig.name AS integration_name, fis.account_prefix, o.internal_order_number, o.external_order_id FROM invoices i LEFT JOIN invoice_configs ic ON ic.id = i.config_id LEFT JOIN integrations ig ON ig.id = ic.integration_id AND ig.type = 'fakturownia' LEFT JOIN fakturownia_integration_settings fis ON fis.integration_id = ig.id LEFT JOIN orders o ON o.id = i.order_id {$whereClause} ORDER BY i.issue_date DESC, i.id DESC LIMIT {$perPage} OFFSET {$offset}" ); $stmt->execute($params); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); return [ 'items' => is_array($rows) ? $rows : [], 'total' => $total, 'page' => $page, 'per_page' => $perPage, ]; } }