prefix . 'esi_peppol_invoices'; } /** * Récupère les derniers enregistrements pour l'affichage dans l'admin. * * @param int $limit Nombre maximum de lignes à retourner. * * @return array Liste d'objets (stdClass). */ public static function get_recent(int $limit = 50): array { global $wpdb; $table_name = self::get_table_name(); $limit = max(1, $limit); $sql = $wpdb->prepare( "SELECT * FROM {$table_name} ORDER BY date_add DESC LIMIT %d", $limit ); return (array) $wpdb->get_results($sql); } /** * Crée la table esi_peppol_invoices si elle n'existe pas * * À appeler lors de l'activation du plugin. * * @return void */ public static function create_table(): void { global $wpdb; $table_name = self::get_table_name(); $charset_collate = $wpdb->get_charset_collate(); $sql = "CREATE TABLE {$table_name} ( id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, id_order BIGINT(20) UNSIGNED NOT NULL, data_sent LONGTEXT NULL, response_data LONGTEXT NULL, document_id VARCHAR(100) NOT NULL, peppol_document_id VARCHAR(191) NOT NULL, status VARCHAR(50) NOT NULL, success TINYINT(1) NOT NULL DEFAULT 0, message TEXT NULL, http_code INT(11) NULL, date_add DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, date_update DATETIME NULL DEFAULT NULL, PRIMARY KEY (id), KEY id_order (id_order), KEY document_id (document_id), KEY peppol_document_id (peppol_document_id) ) {$charset_collate};"; require_once \ABSPATH . 'wp-admin/includes/upgrade.php'; \dbDelta($sql); } /** * Crée un enregistrement PEPPOL pour une commande WooCommerce. * * @param int $order_id ID de la commande WooCommerce. * @param mixed $response_data Données retournées par l'API (array / objet / string). * @param mixed $data_sent Payload envoyé à l'API (array / objet / string). * @param string $document_id Identifiant / numéro du document (côté boutique). * @param string $peppol_document_id Identifiant du document dans Peppol. * @param string $status Statut métier (ex: 'created', 'sent', 'error'). * @param bool $success Indique si l'appel API est un succès. * @param string $message Message d'information / d'erreur. * @param int|null $http_code Code HTTP retourné par l'API, si disponible. * * @return int|null ID inséré ou null en cas d'erreur. */ public static function create( int $order_id, $response_data, $data_sent, string $document_id, string $peppol_document_id, string $status, bool $success, string $message = '', ?int $http_code = null ): ?int { global $wpdb; $table_name = self::get_table_name(); $serialized_response = \maybe_serialize($response_data); $serialized_data_sent = \maybe_serialize($data_sent); $now = \current_time('mysql'); $success_int = $success ? 1 : 0; $inserted = $wpdb->insert( $table_name, [ 'id_order' => $order_id, 'data_sent' => $serialized_data_sent, 'response_data' => $serialized_response, 'document_id' => $document_id, 'peppol_document_id' => $peppol_document_id, 'status' => $status, 'success' => $success_int, 'message' => $message, 'http_code' => $http_code, 'date_add' => $now, 'date_update' => $now, ], ['%d', '%s', '%s', '%s', '%s', '%s', '%d', '%s', '%d', '%s', '%s'] ); if ($inserted === false) { return null; } return (int) $wpdb->insert_id; } /** * Enregistre ou met à jour les données PEPPOL pour une commande WooCommerce. * * - 1 ligne par commande (id_order) : si une ligne existe déjà, elle est mise à jour. * - La réponse d'API est stockée en sérialisé dans response_data. * - Le payload envoyé est stocké dans data_sent. */ public static function save_for_order( int $order_id, $response_data, $data_sent, string $document_id, string $peppol_document_id, string $status, bool $success, string $message = '', ?int $http_code = null ): bool { global $wpdb; $table_name = self::get_table_name(); $serialized_response = \maybe_serialize($response_data); $serialized_data_sent = \maybe_serialize($data_sent); $now = \current_time('mysql'); $success_int = $success ? 1 : 0; // Vérifier si une ligne existe déjà pour cette commande $existing_id = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$table_name} WHERE id_order = %d LIMIT 1", $order_id ) ); if ($existing_id) { // Mise à jour $updated = $wpdb->update( $table_name, [ 'data_sent' => $serialized_data_sent, 'response_data' => $serialized_response, 'document_id' => $document_id, 'peppol_document_id' => $peppol_document_id, 'status' => $status, 'success' => $success_int, 'message' => $message, 'http_code' => $http_code, 'date_update' => $now, ], ['id' => (int) $existing_id], ['%s', '%s', '%s', '%s', '%s', '%d', '%s', '%d', '%s'], ['%d'] ); return $updated !== false; } // Pas d'enregistrement existant : on crée return self::create( $order_id, $response_data, $data_sent, $document_id, $peppol_document_id, $status, $success, $message, $http_code ) !== null; } /** * Récupère un enregistrement PEPPOL par ID de commande (id_order). * * La propriété response_data est désérialisée automatiquement. * La propriété data_sent est désérialisée automatiquement. */ public static function get_by_order_id(int $order_id): ?object { global $wpdb; $table_name = self::get_table_name(); $row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$table_name} WHERE id_order = %d LIMIT 1", $order_id ) ); if (!$row) { return null; } // Désérialiser la réponse API et le payload si nécessaire if (isset($row->response_data)) { $row->response_data = \maybe_unserialize($row->response_data); } if (isset($row->data_sent)) { $row->data_sent = \maybe_unserialize($row->data_sent); } return $row; } /** * Récupère un enregistrement PEPPOL par ID (id). * * La propriété response_data est désérialisée automatiquement. * La propriété data_sent est désérialisée automatiquement. * * @param int $id ID de l'enregistrement. * @return object|null */ public static function get_by_id(int $id): ?object { global $wpdb; $table_name = self::get_table_name(); $row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$table_name} WHERE id = %d LIMIT 1", $id ) ); if (!$row) { return null; } // Désérialiser la réponse API et le payload si nécessaire if (isset($row->response_data)) { $row->response_data = \maybe_unserialize($row->response_data); } if (isset($row->data_sent)) { $row->data_sent = \maybe_unserialize($row->data_sent); } return $row; } /** * Récupère un enregistrement PEPPOL par document_id (UUID retourné par l'API ESI Peppol). * * @param string $document_id Document ID (UUID). * @return object|null */ public static function get_by_document_id(string $document_id): ?object { global $wpdb; $table_name = self::get_table_name(); $row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$table_name} WHERE document_id = %s LIMIT 1", $document_id ) ); if (!$row) { return null; } // Désérialiser la réponse API et le payload si nécessaire if (isset($row->response_data)) { $row->response_data = \maybe_unserialize($row->response_data); } if (isset($row->data_sent)) { $row->data_sent = \maybe_unserialize($row->data_sent); } return $row; } /** * Met à jour le statut d'un document par son document_id (UUID). * * Utilisé lors de la réception des webhooks pour mettre à jour * le statut d'un document sans connaître l'ID de commande. * * @param string $document_id Document ID (UUID). * @param string $status Nouveau statut (sent, error, completed). * @param string $peppol_document_id Peppol document ID (optionnel). * @param string $message Message d'information / d'erreur (optionnel). * @param array|null $webhook_payload Payload complet du webhook pour stockage (optionnel). * * @return bool True si la mise à jour a réussi, false sinon. */ public static function update_by_document_id( string $document_id, string $status, string $peppol_document_id = '', string $message = '', ?array $webhook_payload = null ): bool { global $wpdb; $table_name = self::get_table_name(); $now = \current_time('mysql'); // Déterminer le success selon le statut $success = in_array($status, ['sent', 'completed'], true) ? 1 : 0; // Préparer les données de mise à jour $update_data = [ 'status' => $status, 'success' => $success, 'date_update' => $now, ]; $format = ['%s', '%d', '%s']; // Mettre à jour peppol_document_id si fourni if ($peppol_document_id !== '') { $update_data['peppol_document_id'] = $peppol_document_id; $format[] = '%s'; } // Mettre à jour le message si fourni if ($message !== '') { $update_data['message'] = $message; $format[] = '%s'; } // Mettre à jour response_data avec le payload du webhook si fourni if ($webhook_payload !== null) { $serialized_payload = \maybe_serialize($webhook_payload); $update_data['response_data'] = $serialized_payload; $format[] = '%s'; } // Effectuer la mise à jour $updated = $wpdb->update( $table_name, $update_data, ['document_id' => $document_id], $format, ['%s'] ); return $updated !== false && $updated > 0; } }