mdb = $mdb; else { global $mdb; $this -> mdb = $mdb; } } public function ensureTables() { if ( $this -> tablesReady ) return; $this -> mdb -> query( 'CREATE TABLE IF NOT EXISTS `fakturownia_client_mappings` ( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `external_client_key` VARCHAR(191) NOT NULL, `external_name` VARCHAR(255) NOT NULL, `crm_client_id` INT UNSIGNED NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uniq_external_client_key` (`external_client_key`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8' ); $this -> mdb -> query( 'CREATE TABLE IF NOT EXISTS `fakturownia_item_mappings` ( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `external_item_key` VARCHAR(191) NOT NULL, `external_name` VARCHAR(255) NOT NULL, `finance_category_id` INT UNSIGNED NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uniq_external_item_key` (`external_item_key`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8' ); $this -> mdb -> query( 'CREATE TABLE IF NOT EXISTS `fakturownia_skipped_positions` ( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `external_id` VARCHAR(64) NOT NULL, `document_type` VARCHAR(32) NOT NULL, `external_item_key` VARCHAR(191) NOT NULL, `item_name` VARCHAR(255) NOT NULL, `created_at` DATETIME NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uniq_doc_item` (`external_id`, `document_type`, `external_item_key`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8' ); $this -> mdb -> query( 'CREATE TABLE IF NOT EXISTS `fakturownia_imported_documents` ( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `external_document_key` VARCHAR(191) NOT NULL, `document_type` VARCHAR(32) NOT NULL, `external_id` VARCHAR(64) NOT NULL, `source_date` DATE NULL, `amount` DECIMAL(12,2) NOT NULL DEFAULT 0.00, `finance_operation_ids` TEXT NULL, `meta_json` LONGTEXT NULL, `imported_at` DATETIME NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uniq_external_document_key` (`external_document_key`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8' ); $this -> mdb -> query( 'CREATE TABLE IF NOT EXISTS `fakturownia_unmapped_queue` ( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `queue_type` VARCHAR(32) NOT NULL, `external_key` VARCHAR(191) NOT NULL, `external_name` VARCHAR(255) NOT NULL, `payload_json` LONGTEXT NULL, `hits` INT UNSIGNED NOT NULL DEFAULT 1, `resolved` TINYINT(1) NOT NULL DEFAULT 0, `first_seen_at` DATETIME NOT NULL, `last_seen_at` DATETIME NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uniq_type_key` (`queue_type`, `external_key`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8' ); $this -> mdb -> query( 'CREATE TABLE IF NOT EXISTS `fakturownia_import_state` ( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `state_key` VARCHAR(191) NOT NULL, `state_value` LONGTEXT NULL, `updated_at` DATETIME NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uniq_state_key` (`state_key`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8' ); $this -> tablesReady = true; } public function getClientMapping( $externalClientKey ) { $this -> ensureTables(); return $this -> mdb -> get( 'fakturownia_client_mappings', '*', [ 'external_client_key' => $externalClientKey ] ); } public function saveClientMapping( $externalClientKey, $externalName, $crmClientId ) { $this -> ensureTables(); $current = $this -> getClientMapping( $externalClientKey ); $now = date( 'Y-m-d H:i:s' ); if ( $current ) { $this -> mdb -> update( 'fakturownia_client_mappings', [ 'external_name' => $externalName, 'crm_client_id' => (int)$crmClientId, 'updated_at' => $now ], [ 'id' => (int)$current['id'] ] ); } else { $this -> mdb -> insert( 'fakturownia_client_mappings', [ 'external_client_key' => $externalClientKey, 'external_name' => $externalName, 'crm_client_id' => (int)$crmClientId, 'created_at' => $now, 'updated_at' => $now ] ); } $this -> resolveQueueItem( 'client', $externalClientKey ); } public function getItemMapping( $externalItemKey ) { $this -> ensureTables(); return $this -> mdb -> get( 'fakturownia_item_mappings', '*', [ 'external_item_key' => $externalItemKey ] ); } public function saveItemMapping( $externalItemKey, $externalName, $financeCategoryId ) { $this -> ensureTables(); $current = $this -> getItemMapping( $externalItemKey ); $now = date( 'Y-m-d H:i:s' ); if ( $current ) { $this -> mdb -> update( 'fakturownia_item_mappings', [ 'external_name' => $externalName, 'finance_category_id' => (int)$financeCategoryId, 'updated_at' => $now ], [ 'id' => (int)$current['id'] ] ); } else { $this -> mdb -> insert( 'fakturownia_item_mappings', [ 'external_item_key' => $externalItemKey, 'external_name' => $externalName, 'finance_category_id' => (int)$financeCategoryId, 'created_at' => $now, 'updated_at' => $now ] ); } $this -> resolveQueueItem( 'item', $externalItemKey ); } public function markDocumentPositionSkipped( $externalId, $documentType, $externalItemKey, $itemName ) { $this -> ensureTables(); $existing = $this -> mdb -> get( 'fakturownia_skipped_positions', 'id', [ 'AND' => [ 'external_id' => (string)$externalId, 'document_type' => (string)$documentType, 'external_item_key' => (string)$externalItemKey ] ] ); if ( $existing ) return; $this -> mdb -> insert( 'fakturownia_skipped_positions', [ 'external_id' => (string)$externalId, 'document_type' => (string)$documentType, 'external_item_key' => (string)$externalItemKey, 'item_name' => (string)$itemName, 'created_at' => date( 'Y-m-d H:i:s' ) ] ); } public function isDocumentPositionSkipped( $externalId, $documentType, $externalItemKey ) { $this -> ensureTables(); return (bool)$this -> mdb -> has( 'fakturownia_skipped_positions', [ 'AND' => [ 'external_id' => (string)$externalId, 'document_type' => (string)$documentType, 'external_item_key' => (string)$externalItemKey ] ] ); } public function removeOccurrenceFromItemQueue( $externalItemKey, $externalId ) { $this -> ensureTables(); $row = $this -> mdb -> get( 'fakturownia_unmapped_queue', '*', [ 'AND' => [ 'queue_type' => 'item', 'external_key' => (string)$externalItemKey ] ] ); if ( !$row ) return; $payload = []; if ( isset( $row['payload_json'] ) && $row['payload_json'] ) { $decoded = json_decode( $row['payload_json'], true ); if ( is_array( $decoded ) ) $payload = $decoded; } $occurrences = []; if ( isset( $payload['occurrences'] ) && is_array( $payload['occurrences'] ) ) { foreach ( $payload['occurrences'] as $occ ) { if ( !is_array( $occ ) ) continue; if ( (string)( $occ['document_id'] ?? '' ) === (string)$externalId ) continue; $occurrences[] = $occ; } } if ( empty( $occurrences ) ) { $this -> mdb -> delete( 'fakturownia_unmapped_queue', [ 'id' => (int)$row['id'] ] ); return; } $payload['occurrences'] = $occurrences; $this -> mdb -> update( 'fakturownia_unmapped_queue', [ 'payload_json' => json_encode( $payload, JSON_UNESCAPED_UNICODE ), 'hits' => count( $occurrences ), 'last_seen_at' => date( 'Y-m-d H:i:s' ) ], [ 'id' => (int)$row['id'] ] ); } public function isDocumentImported( $externalDocumentKey ) { $this -> ensureTables(); return (bool)$this -> mdb -> has( 'fakturownia_imported_documents', [ 'external_document_key' => $externalDocumentKey ] ); } public function markDocumentImported( $externalDocumentKey, $documentType, $externalId, $sourceDate, $amount, $operationIds, $meta ) { $this -> ensureTables(); $now = date( 'Y-m-d H:i:s' ); $data = [ 'external_document_key' => $externalDocumentKey, 'document_type' => $documentType, 'external_id' => (string)$externalId, 'source_date' => $sourceDate, 'amount' => (float)$amount, 'finance_operation_ids' => implode( ',', $operationIds ), 'meta_json' => json_encode( $meta, JSON_UNESCAPED_UNICODE ), 'imported_at' => $now ]; $existing = $this -> mdb -> get( 'fakturownia_imported_documents', 'id', [ 'external_document_key' => $externalDocumentKey ] ); if ( $existing ) { $this -> mdb -> update( 'fakturownia_imported_documents', $data, [ 'id' => (int)$existing ] ); return; } $this -> mdb -> insert( 'fakturownia_imported_documents', $data ); } public function queueUnmapped( $queueType, $externalKey, $externalName, $payload ) { $this -> ensureTables(); $existing = $this -> mdb -> get( 'fakturownia_unmapped_queue', '*', [ 'AND' => [ 'queue_type' => $queueType, 'external_key' => $externalKey ] ] ); $now = date( 'Y-m-d H:i:s' ); // Dla queue_type='item' agregujemy occurrences per (document_id, item_key). // Kazde wystapienie pozycji na innej fakturze to osobny wpis w payload.occurrences, // zeby UI mogl pokazac liste i pozwolic na pomijanie per konkretna faktura. if ( $queueType === 'item' ) { $occurrence = $payload; $payloadToStore = [ 'occurrences' => [ $occurrence ] ]; if ( $existing ) { $existingPayload = []; if ( isset( $existing['payload_json'] ) && $existing['payload_json'] ) { $decoded = json_decode( $existing['payload_json'], true ); if ( is_array( $decoded ) ) $existingPayload = $decoded; } $occurrences = ( isset( $existingPayload['occurrences'] ) && is_array( $existingPayload['occurrences'] ) ) ? $existingPayload['occurrences'] : []; // Dedup po document_id — powtorny import nadpisuje istniejace wystapienie. $occurrences = array_values( array_filter( $occurrences, function( $occ ) use ( $occurrence ) { if ( !is_array( $occ ) ) return false; return (string)( $occ['document_id'] ?? '' ) !== (string)( $occurrence['document_id'] ?? '' ); } ) ); $occurrences[] = $occurrence; $payloadToStore = [ 'occurrences' => $occurrences ]; $this -> mdb -> update( 'fakturownia_unmapped_queue', [ 'external_name' => $externalName, 'payload_json' => json_encode( $payloadToStore, JSON_UNESCAPED_UNICODE ), 'hits' => count( $occurrences ), 'resolved' => 0, 'last_seen_at' => $now ], [ 'id' => (int)$existing['id'] ] ); return; } $this -> mdb -> insert( 'fakturownia_unmapped_queue', [ 'queue_type' => $queueType, 'external_key' => $externalKey, 'external_name' => $externalName, 'payload_json' => json_encode( $payloadToStore, JSON_UNESCAPED_UNICODE ), 'hits' => 1, 'resolved' => 0, 'first_seen_at' => $now, 'last_seen_at' => $now ] ); return; } // Pozostale queue_type (client) — zachowanie bez zmian. $payloadJson = json_encode( $payload, JSON_UNESCAPED_UNICODE ); if ( $existing ) { $this -> mdb -> update( 'fakturownia_unmapped_queue', [ 'external_name' => $externalName, 'payload_json' => $payloadJson, 'hits[+]' => 1, 'resolved' => 0, 'last_seen_at' => $now ], [ 'id' => (int)$existing['id'] ] ); return; } $this -> mdb -> insert( 'fakturownia_unmapped_queue', [ 'queue_type' => $queueType, 'external_key' => $externalKey, 'external_name' => $externalName, 'payload_json' => $payloadJson, 'hits' => 1, 'resolved' => 0, 'first_seen_at' => $now, 'last_seen_at' => $now ] ); } public function pendingClientMappings() { $this -> ensureTables(); return $this -> mdb -> select( 'fakturownia_unmapped_queue', '*', [ 'AND' => [ 'queue_type' => 'client', 'resolved' => 0 ], 'ORDER' => [ 'last_seen_at' => 'DESC' ] ] ); } public function pendingItemMappings() { $this -> ensureTables(); return $this -> mdb -> select( 'fakturownia_unmapped_queue', '*', [ 'AND' => [ 'queue_type' => 'item', 'resolved' => 0, 'external_key[!]' => 'name:faktura bez pozycji' ], 'ORDER' => [ 'last_seen_at' => 'DESC' ] ] ); } public function saveState( $stateKey, $stateValue ) { $this -> ensureTables(); $existing = $this -> mdb -> get( 'fakturownia_import_state', '*', [ 'state_key' => $stateKey ] ); $now = date( 'Y-m-d H:i:s' ); if ( $existing ) { $this -> mdb -> update( 'fakturownia_import_state', [ 'state_value' => $stateValue, 'updated_at' => $now ], [ 'id' => (int)$existing['id'] ] ); return; } $this -> mdb -> insert( 'fakturownia_import_state', [ 'state_key' => $stateKey, 'state_value' => $stateValue, 'updated_at' => $now ] ); } public function getState( $stateKey ) { $this -> ensureTables(); return $this -> mdb -> get( 'fakturownia_import_state', 'state_value', [ 'state_key' => $stateKey ] ); } private function resolveQueueItem( $queueType, $externalKey ) { $this -> mdb -> update( 'fakturownia_unmapped_queue', [ 'resolved' => 1, 'last_seen_at' => date( 'Y-m-d H:i:s' ) ], [ 'AND' => [ 'queue_type' => $queueType, 'external_key' => $externalKey ] ] ); } }