baseUrl = rtrim( (string)$baseUrl, '/' ); $this -> apiToken = (string)$apiToken; $this -> pageLimit = max( 1, (int)$pageLimit ); $this -> timeout = max( 5, (int)$timeout ); } public function fetchSalesDocuments( $startDate, $page = 1 ) { // period=more + date_from: Fakturownia filtruje po stronie API po issue_date. // Dzieki temu nie pobieramy historycznych faktur i paginacja konczy sie naturalnie. $query = $this -> buildDateRangeQuery( $startDate, $page ); return $this -> requestList( '/invoices.json', $query ); } public function fetchCostDocuments( $startDate, $page = 1 ) { // W Fakturowni "faktura kosztowa" moze byc zapisana jako: // a) zwykla faktura z income=false (widoczna w /invoices.json?income=no, URL /invoices/ID) — primary source // b) koszt w oddzielnym module /costs.json // c) wydatek w /expenses.json (starsze API) // Odpytujemy wszystkie trzy i merge'ujemy po id, zeby zadna faktura nie przepadla. // Pusta odpowiedz HTTP 200 z ktoregokolwiek endpointu NIE konczy pobierania — // idziemy dalej do kolejnych sciezek (to byl bug przed 05-04). // period=more + date_from filtruje po stronie API, wiec paginacja konczy sie naturalnie. $baseQuery = $this -> buildDateRangeQuery( $startDate, $page ); $attempts = [ [ 'path' => '/invoices.json', 'query' => array_merge( $baseQuery, [ 'income' => 'no' ] ) ], [ 'path' => '/costs.json', 'query' => $baseQuery ], [ 'path' => '/expenses.json', 'query' => $baseQuery ] ]; $merged = []; $seenIds = []; $hadAnySuccess = false; $lastError = null; foreach ( $attempts as $attempt ) { try { $response = $this -> requestList( $attempt['path'], $attempt['query'], true ); } catch ( \Throwable $e ) { $lastError = $e; continue; } if ( !$response['ok'] ) { $lastError = new \RuntimeException( 'Blad HTTP dla ' . $attempt['path'] ); continue; } $hadAnySuccess = true; foreach ( $response['data'] as $document ) { $id = $this -> extractDocumentId( $document ); if ( $id === '' ) { $merged[] = $document; continue; } if ( isset( $seenIds[ $id ] ) ) continue; $seenIds[ $id ] = true; $merged[] = $document; } } if ( !$hadAnySuccess && $lastError !== null ) throw new \RuntimeException( 'Nie udalo sie pobrac faktur kosztowych z API Fakturowni: ' . $lastError -> getMessage() ); return $merged; } private function buildDateRangeQuery( $startDate, $page ) { $query = [ 'page' => (int)$page, 'per_page' => $this -> pageLimit ]; if ( is_string( $startDate ) && preg_match( '/^\d{4}-\d{2}-\d{2}$/', $startDate ) ) { $query['period'] = 'more'; $query['date_from'] = $startDate; $query['date_to'] = date( 'Y-m-d' ); } return $query; } private function extractDocumentId( $document ) { if ( !is_array( $document ) ) return ''; if ( isset( $document['invoice'] ) && is_array( $document['invoice'] ) ) $document = $document['invoice']; if ( isset( $document['id'] ) && (string)$document['id'] !== '' ) return (string)$document['id']; return ''; } public function fetchInvoiceDetails( $invoiceId ) { $invoiceId = (int)$invoiceId; if ( $invoiceId <= 0 ) return null; $result = $this -> request( '/invoices/' . $invoiceId . '.json', [] ); if ( $result['http_code'] >= 400 ) return null; $data = json_decode( $result['body'], true ); return is_array( $data ) ? $data : null; } private function requestList( $path, $query, $softFail = false ) { $result = $this -> request( $path, $query ); if ( $result['http_code'] >= 400 ) { if ( $softFail ) return [ 'ok' => false, 'data' => [] ]; throw new \RuntimeException( 'Blad API Fakturowni: HTTP ' . $result['http_code'] . ' dla ' . $path ); } $data = json_decode( $result['body'], true ); if ( !is_array( $data ) ) { if ( $softFail ) return [ 'ok' => false, 'data' => [] ]; throw new \RuntimeException( 'API Fakturowni zwrocilo niepoprawny JSON.' ); } $list = $this -> extractList( $data ); return $softFail ? [ 'ok' => true, 'data' => $list ] : $list; } protected function request( $path, $query ) { $query['api_token'] = $this -> apiToken; $url = $this -> baseUrl . $path . '?' . http_build_query( $query ); $ch = curl_init( $url ); curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true ); curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT, $this -> timeout ); curl_setopt( $ch, CURLOPT_TIMEOUT, $this -> timeout ); curl_setopt( $ch, CURLOPT_HTTPHEADER, [ 'Accept: application/json' ] ); $body = curl_exec( $ch ); $httpCode = (int)curl_getinfo( $ch, CURLINFO_HTTP_CODE ); $error = curl_error( $ch ); curl_close( $ch ); if ( $body === false ) throw new \RuntimeException( 'Blad polaczenia z API Fakturowni: ' . $error ); return [ 'http_code' => $httpCode, 'body' => $body ]; } private function extractList( $data ) { if ( $this -> isList( $data ) ) return $data; $keys = [ 'invoices', 'costs', 'expenses', 'data' ]; foreach ( $keys as $key ) { if ( isset( $data[ $key ] ) && is_array( $data[ $key ] ) ) { if ( $this -> isList( $data[ $key ] ) ) return $data[ $key ]; } } return []; } private function isList( $value ) { if ( !is_array( $value ) ) return false; if ( $value === [] ) return true; return array_keys( $value ) === range( 0, count( $value ) - 1 ); } }