diff --git a/.env.example b/.env.example index 51fca21..108b020 100644 --- a/.env.example +++ b/.env.example @@ -67,3 +67,7 @@ VITE_APP_NAME="${APP_NAME}" # LOGISTICS_API_BASE_URL= # LOGISTICS_API_KEY= # LOGISTICS_API_FOLDER= +# LOGISTICS_API_TIMEOUT=30 +# LOGISTICS_API_CONNECT_TIMEOUT=10 +# LOGISTICS_API_RETRY_TIMES=3 +# LOGISTICS_API_RETRY_SLEEP_MS=500 diff --git a/app/Exceptions/LogisticsApiException.php b/app/Exceptions/LogisticsApiException.php new file mode 100644 index 0000000..3892143 --- /dev/null +++ b/app/Exceptions/LogisticsApiException.php @@ -0,0 +1,38 @@ +getMessage()}", + endpoint: $endpoint, + params: $params, + previous: $previous, + ); + } +} diff --git a/app/Filament/Pages/Articles.php b/app/Filament/Pages/Articles.php index 03fe649..18d58c9 100644 --- a/app/Filament/Pages/Articles.php +++ b/app/Filament/Pages/Articles.php @@ -2,6 +2,7 @@ namespace App\Filament\Pages; +use App\Exceptions\LogisticsApiException; use App\Services\LogisticsService; use Filament\Pages\Page; use Filament\Support\Icons\Heroicon; @@ -50,9 +51,12 @@ class Articles extends Page $this->data = $response['data'] ?? []; $this->metadata = $response['metadata'] ?? null; $this->errorMessage = $response['error'] ?? null; - } catch (\Throwable $e) { + } catch (LogisticsApiException $e) { $this->errorMessage = $e->getMessage(); $this->data = []; + } catch (\Throwable $e) { + $this->errorMessage = "Erreur inattendue : {$e->getMessage()}"; + $this->data = []; } } @@ -68,9 +72,12 @@ class Articles extends Page $this->stockData = $response['data'] ?? []; $this->errorMessage = $response['error'] ?? null; - } catch (\Throwable $e) { + } catch (LogisticsApiException $e) { $this->errorMessage = $e->getMessage(); $this->stockData = []; + } catch (\Throwable $e) { + $this->errorMessage = "Erreur inattendue : {$e->getMessage()}"; + $this->stockData = []; } } } diff --git a/app/Filament/Pages/Documents.php b/app/Filament/Pages/Documents.php index fcbbeb4..c1a2efc 100644 --- a/app/Filament/Pages/Documents.php +++ b/app/Filament/Pages/Documents.php @@ -2,6 +2,7 @@ namespace App\Filament\Pages; +use App\Exceptions\LogisticsApiException; use App\Services\LogisticsService; use Filament\Pages\Page; use Filament\Support\Icons\Heroicon; @@ -49,9 +50,12 @@ class Documents extends Page $this->data = $response['data'] ?? []; $this->metadata = $response['metadata'] ?? null; $this->errorMessage = $response['error'] ?? null; - } catch (\Throwable $e) { + } catch (LogisticsApiException $e) { $this->errorMessage = $e->getMessage(); $this->data = []; + } catch (\Throwable $e) { + $this->errorMessage = "Erreur inattendue : {$e->getMessage()}"; + $this->data = []; } } @@ -67,9 +71,12 @@ class Documents extends Page $this->detailData = $response['data'] ?? []; $this->errorMessage = $response['error'] ?? null; - } catch (\Throwable $e) { + } catch (LogisticsApiException $e) { $this->errorMessage = $e->getMessage(); $this->detailData = []; + } catch (\Throwable $e) { + $this->errorMessage = "Erreur inattendue : {$e->getMessage()}"; + $this->detailData = []; } } } diff --git a/app/Filament/Pages/Journaux.php b/app/Filament/Pages/Journaux.php index e8186bf..442443f 100644 --- a/app/Filament/Pages/Journaux.php +++ b/app/Filament/Pages/Journaux.php @@ -2,6 +2,7 @@ namespace App\Filament\Pages; +use App\Exceptions\LogisticsApiException; use App\Services\LogisticsService; use Filament\Pages\Page; use Filament\Support\Icons\Heroicon; @@ -46,9 +47,12 @@ class Journaux extends Page $this->data = $response['data'] ?? []; $this->metadata = $response['metadata'] ?? null; $this->errorMessage = $response['error'] ?? null; - } catch (\Throwable $e) { + } catch (LogisticsApiException $e) { $this->errorMessage = $e->getMessage(); $this->data = []; + } catch (\Throwable $e) { + $this->errorMessage = "Erreur inattendue : {$e->getMessage()}"; + $this->data = []; } } } diff --git a/app/Filament/Pages/TablesExplorer.php b/app/Filament/Pages/TablesExplorer.php index 964537e..4963945 100644 --- a/app/Filament/Pages/TablesExplorer.php +++ b/app/Filament/Pages/TablesExplorer.php @@ -2,6 +2,7 @@ namespace App\Filament\Pages; +use App\Exceptions\LogisticsApiException; use App\Services\LogisticsService; use Filament\Pages\Page; use Filament\Support\Icons\Heroicon; @@ -41,8 +42,10 @@ class TablesExplorer extends Page $this->tables = $response['data'] ?? []; $this->errorMessage = $response['error'] ?? null; - } catch (\Throwable $e) { + } catch (LogisticsApiException $e) { $this->errorMessage = $e->getMessage(); + } catch (\Throwable $e) { + $this->errorMessage = "Erreur inattendue : {$e->getMessage()}"; } } @@ -60,8 +63,10 @@ class TablesExplorer extends Page $this->columns = $response['data'] ?? []; $this->errorMessage = $response['error'] ?? null; - } catch (\Throwable $e) { + } catch (LogisticsApiException $e) { $this->errorMessage = $e->getMessage(); + } catch (\Throwable $e) { + $this->errorMessage = "Erreur inattendue : {$e->getMessage()}"; } } } diff --git a/app/Filament/Pages/Tiers.php b/app/Filament/Pages/Tiers.php index bf744fc..f7c62ba 100644 --- a/app/Filament/Pages/Tiers.php +++ b/app/Filament/Pages/Tiers.php @@ -2,6 +2,7 @@ namespace App\Filament\Pages; +use App\Exceptions\LogisticsApiException; use App\Services\LogisticsService; use Filament\Pages\Page; use Filament\Support\Icons\Heroicon; @@ -50,9 +51,12 @@ class Tiers extends Page $this->data = $response['data'] ?? []; $this->metadata = $response['metadata'] ?? null; $this->errorMessage = $response['error'] ?? null; - } catch (\Throwable $e) { + } catch (LogisticsApiException $e) { $this->errorMessage = $e->getMessage(); $this->data = []; + } catch (\Throwable $e) { + $this->errorMessage = "Erreur inattendue : {$e->getMessage()}"; + $this->data = []; } } @@ -68,9 +72,12 @@ class Tiers extends Page $this->historyData = $response['data'] ?? []; $this->errorMessage = $response['error'] ?? null; - } catch (\Throwable $e) { + } catch (LogisticsApiException $e) { $this->errorMessage = $e->getMessage(); $this->historyData = []; + } catch (\Throwable $e) { + $this->errorMessage = "Erreur inattendue : {$e->getMessage()}"; + $this->historyData = []; } } } diff --git a/app/Services/LogisticsService.php b/app/Services/LogisticsService.php index ca8656d..3709d1e 100644 --- a/app/Services/LogisticsService.php +++ b/app/Services/LogisticsService.php @@ -2,6 +2,8 @@ namespace App\Services; +use App\Exceptions\LogisticsApiException; +use Illuminate\Http\Client\ConnectionException; use Illuminate\Http\Client\Response; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Http; @@ -14,11 +16,23 @@ class LogisticsService private string $folder; + private int $timeout; + + private int $connectTimeout; + + private int $retryTimes; + + private int $retrySleepMs; + public function __construct() { $this->baseUrl = rtrim(config('logistics.base_url'), '/'); $this->apiKey = config('logistics.api_key'); $this->folder = config('logistics.folder'); + $this->timeout = config('logistics.timeout', 30); + $this->connectTimeout = config('logistics.connect_timeout', 10); + $this->retryTimes = config('logistics.retry.times', 3); + $this->retrySleepMs = config('logistics.retry.sleep_ms', 500); } public function tablesList(): array @@ -141,31 +155,62 @@ class LogisticsService /** * @return array{data: mixed, metadata: array, error: mixed} + * + * @throws LogisticsApiException */ private function post(string $endpoint, array $params = []): array { $url = "{$this->baseUrl}/{$this->folder}/{$endpoint}"; - $response = Http::withHeaders([ - 'X-API-KEY' => $this->apiKey, - ])->post($url, $params); + try { + $response = Http::withHeaders([ + 'X-API-KEY' => $this->apiKey, + ]) + ->timeout($this->timeout) + ->connectTimeout($this->connectTimeout) + ->retry($this->retryTimes, $this->retrySleepMs, fn (\Exception $e) => $e instanceof ConnectionException) + ->post($url, $params); - $this->logRequest($endpoint, $params, $response); + $this->logRequest($endpoint, $params, $response); - return $response->json() ?? [ - 'data' => null, - 'metadata' => ['rowcount' => 0, 'issuccess' => false], - 'error' => 'Empty response from API', - ]; + return $response->json() ?? [ + 'data' => null, + 'metadata' => ['rowcount' => 0, 'issuccess' => false], + 'error' => 'Empty response from API', + ]; + } catch (ConnectionException $e) { + $this->logFailedRequest($endpoint, $params, $e); + + throw LogisticsApiException::connectionTimeout($endpoint, $params, $e); + } catch (\Throwable $e) { + $this->logFailedRequest($endpoint, $params, $e); + + throw LogisticsApiException::requestFailed($endpoint, $params, $e); + } } private function logRequest(string $endpoint, array $params, Response $response): void { + $body = $response->body(); + $isValidJson = json_decode($body) !== null || $body === 'null'; + DB::table('api_request_logs')->insert([ 'endpoint' => $endpoint, 'parameters' => json_encode($params), 'response_status' => $response->status(), - 'response_data' => $response->body(), + 'response_data' => $isValidJson ? $body : json_encode(['raw' => $body]), + 'created_at' => now(), + 'updated_at' => now(), + ]); + } + + private function logFailedRequest(string $endpoint, array $params, \Throwable $exception): void + { + DB::table('api_request_logs')->insert([ + 'endpoint' => $endpoint, + 'parameters' => json_encode($params), + 'response_status' => 0, + 'response_data' => json_encode(['error' => $exception->getMessage()]), 'created_at' => now(), 'updated_at' => now(), ]); diff --git a/config/logistics.php b/config/logistics.php index 12b6c95..53b28e0 100644 --- a/config/logistics.php +++ b/config/logistics.php @@ -8,4 +8,13 @@ return [ 'folder' => env('LOGISTICS_API_FOLDER'), + 'timeout' => (int) env('LOGISTICS_API_TIMEOUT', 30), + + 'connect_timeout' => (int) env('LOGISTICS_API_CONNECT_TIMEOUT', 10), + + 'retry' => [ + 'times' => (int) env('LOGISTICS_API_RETRY_TIMES', 3), + 'sleep_ms' => (int) env('LOGISTICS_API_RETRY_SLEEP_MS', 500), + ], + ]; diff --git a/documentation/documentation_api_logistics.md b/documentation/documentation_api_logistics.md new file mode 100644 index 0000000..b8d7f2e --- /dev/null +++ b/documentation/documentation_api_logistics.md @@ -0,0 +1,517 @@ +# Documentation API Logistics (Flex/ESI Gescom) + +Derniere mise a jour : 2026-02-20 + +--- + +## Table des matieres + +- [Informations generales](#informations-generales) +- [Authentification](#authentification) +- [Structure de reponse](#structure-de-reponse) +- [Tables disponibles](#tables-disponibles) +- [Endpoints](#endpoints) + - [Structure](#structure) + - [Articles](#articles) + - [Journaux](#journaux) + - [Documents](#documents) + - [Tiers](#tiers) + - [Divers](#divers) +- [Endpoints non fonctionnels](#endpoints-non-fonctionnels) +- [Relations entre entites](#relations-entre-entites) +- [Remarques et points d'attention](#remarques-et-points-dattention) + +--- + +## Informations generales + +| Element | Valeur | +|---------|--------| +| Serveur | TSE-10-TEST | +| Hote | `tse-10-test.esiweb.pro` | +| Port HTTP | 5186 | +| Port HTTPS | 7126 | +| Methode | POST (pour tous les endpoints) | +| Format de reponse | JSON | + +L'URL de base de chaque requete suit le schema : + +``` +http://:// +``` + +Le nom du dossier **doit etre en minuscules** dans toutes les requetes. + +--- + +## Authentification + +Toutes les requetes sont protegees par une cle API. Elle doit etre transmise dans le header HTTP : + +| Header | Valeur | +|--------|--------| +| `X-API-KEY` | `` | + +Exemple : + +```http +POST /esigescom/tables_list HTTP/1.1 +Host: tse-10-test.esiweb.pro:5186 +X-API-KEY: votre-cle-api +Content-Type: application/json +``` + +--- + +## Structure de reponse + +Tous les endpoints retournent un objet JSON avec la meme structure : + +```json +{ + "data": "", + "metadata": { + "rowcount": 0, + "issuccess": true + }, + "error": null +} +``` + +| Cle | Type | Description | +|-----|------|-------------| +| `data` | mixed | Le resultat de la requete (tableau d'objets, objet, ou null) | +| `metadata.rowcount` | int | Nombre d'elements retournes | +| `metadata.issuccess` | bool | Indique si la requete a reussi | +| `error` | string/null | Message d'erreur en cas d'echec, `null` sinon | + +--- + +## Tables disponibles + +Les tables suivantes sont exposees par l'API. Leurs colonnes peuvent etre recuperees via l'endpoint `column_list` et utilisees dans le parametre `select` des endpoints de recherche. + +| Table | Description probable | Table | Description probable | +|-------|---------------------|-------|---------------------| +| `art` | Articles | `docpay` | Paiements documents | +| `attach` | Fichiers attaches | `file` | Fichiers | +| `barcode` | Codes-barres | `hist` | Historique | +| `category` | Categories | `incodes` | Codes internes | +| `codes` | Codes | `jnl` | Journaux | +| `cust` | Clients / Tiers | `pers` | Personnes | +| `docdet` | Detail documents | `price` | Prix | +| `dochead` | En-tete documents | `stk` | Stock | + +--- + +## Endpoints + +### Structure + +Ces endpoints permettent de decouvrir la structure de la base de donnees exposee par l'API. + +#### `tables_list` -- Liste des tables + +Retourne la liste de toutes les tables accessibles. + +| | | +|---|---| +| **URL** | `POST //tables_list` | +| **Parametres** | Aucun | + +Exemple de reponse : + +```json +{ + "data": ["art", "attach", "barcode", "category", "codes", "cust", "docdet", "dochead", "docpay", "file", "hist", "incodes", "jnl", "pers", "price", "stk"], + "metadata": { "rowcount": 16, "issuccess": true }, + "error": null +} +``` + +--- + +#### `column_list/{tablename}` -- Colonnes d'une table + +Retourne la liste des colonnes d'une table donnee. Utile pour connaitre les champs utilisables dans le parametre `select` des autres endpoints. + +| | | +|---|---| +| **URL** | `POST //column_list/{tablename}` | +| **Parametres** | `tablename` dans l'URL (nom de la table, issu de `tables_list`) | + +--- + +### Articles + +Un document contient un ou plusieurs articles. Les articles sont stockes dans la table `art`. + +#### `art_list` -- Recherche d'articles + +Retourne une liste d'articles correspondant aux criteres de recherche. + +| | | +|---|---| +| **URL** | `POST //art_list` | + +| Parametre | Type | Obligatoire | Description | +|-----------|------|:-----------:|-------------| +| `select` | string | Non | Colonnes a retourner (colonnes de la table `art`) | +| `results` | int | Non | Nombre de resultats a retourner | +| `search` | string | Non | Filtre de recherche | +| `barcode` | string | Non | Code-barres de l'article | + +Remarque : lors d'une recherche par `barcode`, le parametre `search` n'est pas requis. + +--- + +#### `art_getstk` -- Stock d'un article + +Retourne les informations de stock pour un article donne. + +| | | +|---|---| +| **URL** | `POST //art_getstk` | + +| Parametre | Type | Obligatoire | Description | +|-----------|------|:-----------:|-------------| +| `ARTID` | string | Oui | Identifiant de l'article | + +--- + +### Journaux + +Un journal contient un ou plusieurs documents. Les journaux sont stockes dans la table `jnl`. + +#### `jnl_list` -- Liste des journaux + +Retourne la liste des journaux correspondant aux criteres. + +| | | +|---|---| +| **URL** | `POST //jnl_list` | + +| Parametre | Type | Obligatoire | Description | +|-----------|------|:-----------:|-------------| +| `select` | string | Non | Colonnes a retourner (colonnes de la table `jnl`) | +| `results` | int | Non | Nombre de resultats a retourner | +| `TYPE` | string | Oui | Code de type de journal | + +--- + +### Documents + +Un journal contient un ou plusieurs documents, et un document contient un ou plusieurs articles. Les en-tetes de documents sont dans la table `dochead`, les lignes de detail dans `docdet`. + +#### `document_list` -- Liste des documents + +Retourne une liste de documents, eventuellement filtree par tiers. + +| | | +|---|---| +| **URL** | `POST //document_list` | + +| Parametre | Type | Obligatoire | Description | +|-----------|------|:-----------:|-------------| +| `select` | string | Non | Colonnes a retourner (colonnes de la table `dochead`) | +| `thirdid` | string | Non | Identifiant du tiers (`custid` de la table `cust`) | + +--- + +#### `document_detail` -- Detail d'un document + +Retourne le detail complet d'un document (en-tete + lignes). + +| | | +|---|---| +| **URL** | `POST //document_detail` | + +| Parametre | Type | Obligatoire | Description | +|-----------|------|:-----------:|-------------| +| `jnl` | string | Oui | Code du journal | +| `number` | string | Oui | Identifiant du document | + +--- + +#### `document_add` -- Ajout d'un document + +Cree un nouveau document dans un journal. + +| | | +|---|---| +| **URL** | `POST //document_add` | + +| Parametre | Type | Obligatoire | Description | +|-----------|------|:-----------:|-------------| +| `ThirdId` | string | Oui | Identifiant du tiers (`custid` de la table `cust`) | +| `Date` | string | Oui | Date d'encodage | +| `Artid` | array | Oui | Tableau d'identifiants d'articles (`artid` de la table `art`) | +| `Qty` | array | Oui | Tableau de quantites (correspond position par position a `Artid`) | +| `Saleprice` | array | Oui | Tableau des prix de vente unitaires | +| `JNL` | string | Oui | Code du journal affecte | +| `Discount` | array | Non | Tableau des reductions de prix | +| `Vatid` | array | Non | Tableau des identifiants de TVA | +| `Vatpc` | array | Non | Tableau des pourcentages de TVA | +| `Attachments` | array | Non | Liste de fichiers joints (voir structure ci-dessous) | + +Structure d'un element `Attachments` : + +| Cle | Type | Description | +|-----|------|-------------| +| `FileName` | string | Nom du fichier avec extension (ex: `facture.pdf`) | +| `FileDesc` | string | Description du fichier | +| `FileContentBase64` | string | Contenu du fichier encode en base64 | + +Exemple de body : + +```json +{ + "ThirdId": "CUST001", + "Date": "2026-02-20", + "Artid": ["ART001", "ART002"], + "Qty": ["2", "5"], + "Saleprice": ["10.00", "25.50"], + "JNL": "VEN", + "Attachments": [ + { + "FileName": "bon.pdf", + "FileDesc": "Bon de commande", + "FileContentBase64": "JVBERi0xLjQK..." + } + ] +} +``` + +--- + +#### `document_mod` -- Modification d'un document + +Modifie un document existant. Le parametre `number` identifie le document a modifier. + +| | | +|---|---| +| **URL** | `POST //document_mod` | + +| Parametre | Type | Obligatoire | Description | +|-----------|------|:-----------:|-------------| +| `number` | string | Oui | Identifiant du document a modifier | +| `JNL` | string | Oui | Code du journal lie au document | +| `Thirdid` | string | Non | Identifiant du tiers | +| `Artid` | array | Non | Tableau d'identifiants d'articles | +| `Qty` | array | Non | Tableau de quantites | +| `Saleprice` | array | Non | Tableau des prix de vente | +| `Attachments` | array | Non | Liste de fichiers joints (meme structure que `document_add`) | + +--- + +#### `Document_GetStatusList` -- Statuts d'un journal + +Retourne la liste des statuts disponibles pour les documents d'un journal. + +| | | +|---|---| +| **URL** | `POST //Document_GetStatusList` | + +| Parametre | Type | Obligatoire | Description | +|-----------|------|:-----------:|-------------| +| `jnl` | string | Oui | Code du journal | + +--- + +#### `Document_GetUnitPriceAndVat` -- Prix unitaire et TVA + +Retourne le prix unitaire et la TVA pour un article dans un contexte donne (journal, tiers, date). + +| | | +|---|---| +| **URL** | `POST //Document_GetUnitPriceAndVat` | + +| Parametre | Type | Obligatoire | Description | +|-----------|------|:-----------:|-------------| +| `ARTID` | string | Oui | Identifiant de l'article | +| `QTY` | string | Oui | Quantite (format string obligatoire) | +| `JNL` | string | Oui | Code du journal | +| `THIRDID` | string | Oui | Identifiant du tiers (`custid` de la table `cust`) | +| `DATE` | string | Oui | Date | + +--- + +#### `Document_GetDueDate` -- Echeance de paiement + +Calcule la date d'echeance a partir d'un type de delai de paiement et d'une date de depart. + +| | | +|---|---| +| **URL** | `POST //Document_GetDueDate` | + +| Parametre | Type | Obligatoire | Description | +|-----------|------|:-----------:|-------------| +| `paydelay` | string | Oui | Type de delai de paiement | +| `date` | string | Oui | Date de depart | + +--- + +#### `Document_GetAttachListThumbnail` -- Miniatures des annexes + +Retourne les miniatures des fichiers attaches a un document. Seuls les fichiers de type image sont concernes. + +| | | +|---|---| +| **URL** | `POST //Document_GetAttachListThumbnail` | + +| Parametre | Type | Obligatoire | Description | +|-----------|------|:-----------:|-------------| +| `JNL` | string | Oui | Code du journal | +| `NUMBER` | string | Oui | Identifiant du document | + +--- + +### Tiers + +Un tiers (client) possede un ou plusieurs documents. Les tiers sont stockes dans la table `cust`. + +#### `third_list` -- Liste des tiers + +Retourne une liste de tiers correspondant au filtre de recherche. + +| | | +|---|---| +| **URL** | `POST //third_list` | + +| Parametre | Type | Obligatoire | Description | +|-----------|------|:-----------:|-------------| +| `select` | string | Non | Colonnes a retourner (colonnes de la table `cust`) | +| `results` | int | Non | Nombre de resultats a retourner | +| `search` | string | Oui | Filtre de recherche | + +--- + +#### `third_GetArtHistory` -- Historique articles d'un tiers + +Retourne l'historique des articles des documents associes a un tiers. + +| | | +|---|---| +| **URL** | `POST //third_GetArtHistory` | + +| Parametre | Type | Obligatoire | Description | +|-----------|------|:-----------:|-------------| +| `thirdid` | string | Oui | Identifiant du tiers | + +--- + +### Divers + +#### `getserialnumber` -- Numero de serie + +Retourne le numero de serie du dossier. + +| | | +|---|---| +| **URL** | `POST //getserialnumber` | +| **Parametres** | Aucun | + +--- + +#### `codes_list` -- Donnees par code + +Retourne des donnees associees a un code interne. Les codes proviennent de la table `incodes`. + +| | | +|---|---| +| **URL** | `POST //codes_list` | + +| Parametre | Type | Obligatoire | Description | +|-----------|------|:-----------:|-------------| +| `code` | string | Oui | Debut de code (de la table `incodes`) | + +Remarque : les valeurs retournees contiennent des champs `vala1`, `vala2`, etc. dont la signification precise reste a documenter. + +--- + +## Endpoints non fonctionnels + +Les endpoints suivants ont ete testes mais ne fonctionnent pas ou necessitent des parametres inconnus. + +#### `Document_GetPDF` -- Generation de PDF + +| | | +|---|---| +| **URL** | `POST //Document_GetPDF` | +| **Statut** | Non fonctionnel | + +| Parametre | Type | Description | +|-----------|------|-------------| +| `JNL` | string | Code du journal | +| `NUMBER` | string | Identifiant du document | +| `LAYOUT` | string | Inconnu -- parametre requis mais valeur non documentee | + +Probleme : la valeur attendue pour le parametre `LAYOUT` est inconnue. L'endpoint retourne une erreur sans ce parametre. + +--- + +#### `custom_geninv_updatestock` -- Mise a jour de l'inventaire + +| | | +|---|---| +| **URL** | `POST //custom_geninv_updatestock` | +| **Statut** | Non fonctionnel | + +| Parametre | Type | Description | +|-----------|------|-------------| +| `ARTID` | string | Identifiant de l'article | +| `STKID` | string | Identifiant du stock -- valeur inconnue | +| `QTY` | string | Quantite a mettre a jour | +| `TOCHECK` | string | Signification inconnue (prix ?) | +| `TOCHECKDETAIL` | string | Signification inconnue (remarques ?) | +| `MODE` | string | Signification inconnue | + +Probleme : la valeur attendue pour `STKID` est inconnue, et la signification de plusieurs parametres reste a clarifier. + +--- + +## Relations entre entites + +``` +Tiers (cust) + | + +-- possede un ou plusieurs --> Documents (dochead / docdet) + | + +-- appartient a un --> Journal (jnl) + | + +-- contient un ou plusieurs --> Articles (art) + | + +-- peut avoir des --> Fichiers attaches (attach) +``` + +- Un **tiers** (`cust`) est un client qui peut avoir plusieurs documents. +- Un **journal** (`jnl`) regroupe plusieurs documents du meme type. +- Un **document** (`dochead` pour l'en-tete, `docdet` pour les lignes) appartient a un journal et est associe a un tiers. +- Un **article** (`art`) est une reference produit. Chaque ligne de document fait reference a un article avec une quantite et un prix. +- Le **stock** (`stk`) contient les quantites en stock par article. + +--- + +## Remarques et points d'attention + +1. **Dossier en minuscules** : le nom du dossier dans les URLs doit toujours etre en minuscules. Exemple : `esigescom`, pas `EsiGescom`. + +2. **Methode POST uniquement** : tous les endpoints utilisent la methode POST, meme pour les operations de lecture. + +3. **Parametre `QTY`** : dans l'endpoint `Document_GetUnitPriceAndVat`, le parametre `QTY` doit etre au format string, pas numerique. + +4. **Parametre `search` obligatoire** : pour `third_list`, le parametre `search` est obligatoire. Un appel sans filtre retournera une erreur. + +5. **Parametre `results`** : limite le nombre de resultats retournes. Par defaut, l'API retourne un nombre reduit de resultats (environ 5 a 10). + +6. **Parametre `select`** : permet de specifier les colonnes a retourner. Les noms de colonnes disponibles pour chaque table peuvent etre obtenus via `column_list/{tablename}`. + +7. **Fichiers attaches** : lors de l'ajout ou la modification de documents, les fichiers doivent etre encodes en base64 dans le champ `FileContentBase64`. + +8. **Identifiant tiers** : les parametres `thirdid`, `Thirdid` et `THIRDID` font tous reference au champ `custid` de la table `cust`, mais la casse varie selon l'endpoint. + +--- + +## Ressources externes + +- [Documentation Postman](https://documenter.getpostman.com/view/40440561/2sB2qaj2Pz) diff --git a/memory-bank/README.md b/memory-bank/README.md index a281ff1..44879ff 100644 --- a/memory-bank/README.md +++ b/memory-bank/README.md @@ -1,6 +1,6 @@ # Memory Bank -Derniere mise a jour : 2026-02-19 +Derniere mise a jour : 2026-02-20 ## Presentation @@ -12,10 +12,10 @@ Ce dossier contient le Memory Bank du projet API Logistics. Il sert de source de |---------|-------------| | `projectbrief.md` | Vision, objectifs et perimetre du projet | | `productContext.md` | Contexte produit, problemes resolus, experience utilisateur | -| `techContext.md` | Stack technique, API Logistics, dependances | -| `systemPatterns.md` | Architecture, patterns, structure des repertoires | +| `techContext.md` | Stack technique, API Logistics, dependances, configuration | +| `systemPatterns.md` | Architecture, patterns, structure des repertoires, conventions | | `activeContext.md` | Travail en cours, decisions recentes, prochaines etapes | -| `progress.md` | Avancement, ce qui fonctionne, ce qui reste a faire | +| `progress.md` | Avancement, ce qui fonctionne, ce qui reste a faire, metriques | ## Utilisation diff --git a/memory-bank/activeContext.md b/memory-bank/activeContext.md index b99f84b..dc01c2f 100644 --- a/memory-bank/activeContext.md +++ b/memory-bank/activeContext.md @@ -1,10 +1,10 @@ # Active Context -Derniere mise a jour : 2026-02-19 +Derniere mise a jour : 2026-02-20 ## Travail en cours -Aucun travail en cours. Le setup initial du projet est termine. +Aucun travail en cours. Les ameliorations de robustesse du service API sont terminees. ## Decisions recentes @@ -14,20 +14,31 @@ Aucun travail en cours. Le setup initial du projet est termine. - **LogisticsService** : Toutes les interactions avec l'API sont centralisees dans un seul service pour faciliter la maintenance et le tracage. - **Heroicon enum** : Filament v5 impose l'utilisation de `BackedEnum` pour `$navigationIcon` au lieu de strings. - **$view non-static** : Filament v5 a change `$view` de static a instance property. +- **Timeout et retry configurables** (2026-02-20) : Les parametres `timeout`, `connect_timeout`, `retry.times` et `retry.sleep_ms` sont desormais dans `config/logistics.php` et pilotes par des variables `.env`. Le retry ne se declenche que sur les `ConnectionException`. +- **LogisticsApiException** (2026-02-20) : Exception dediee creee pour distinguer les erreurs de connexion (API injoignable) des erreurs de requete generiques. Messages en francais. +- **Logging des echecs** (2026-02-20) : Les requetes echouees sont desormais aussi enregistrees dans `api_request_logs` avec `response_status = 0`. +- **Contrainte reseau** (2026-02-20) : Le serveur API `tse-10-test.esiweb.pro` est sur un reseau prive, accessible uniquement via Bureau a distance (RDP). L'application locale ne peut pas joindre l'API sans tunnel ou deploiement sur le reseau distant. -## Changements importants +## Changements importants (2026-02-20) + +- `LogisticsService` mis a jour : timeout, connectTimeout, retry, gestion d'erreur avec `LogisticsApiException`. +- `LogisticsApiException` creee dans `app/Exceptions/`. +- `config/logistics.php` etendu avec timeout et retry. +- `.env` et `.env.example` completes avec les nouvelles variables. +- Les 5 pages Filament mises a jour pour attraper `LogisticsApiException`. +- Tests passes de 8 a 12 (4 nouveaux tests pour timeout, exception, logging, contexte). + +## Historique (2026-02-19) - Installation de Filament v5.0.0 (31 packages ajoutes). - 5 pages Filament creees : TablesExplorer, Articles, Documents, Journaux, Tiers. -- `LogisticsService` couvre 17 endpoints de l'API. -- Migration `api_request_logs` creee pour le tracage des requetes. -- 8 tests Pest ecrits et valides pour le service. +- `LogisticsService` cree avec 17 endpoints. +- Migration `api_request_logs` creee. +- 8 tests Pest ecrits et valides. ## Prochaines etapes -- L'utilisateur doit creer la base de donnees MySQL `api_logistics`. -- L'utilisateur doit executer `php artisan migrate`. -- L'utilisateur doit renseigner sa cle API dans `.env` (`LOGISTICS_API_KEY`). -- L'utilisateur doit executer `npm run build`. -- Tester le dashboard avec de vraies donnees API. +- Deployer l'application sur le reseau distant (serveur accessible via RDP) ou mettre en place un tunnel SSH/VPN pour que l'application puisse joindre l'API. +- Tester le dashboard avec de vraies donnees API une fois la connectivite reseau resolue. - Eventuellement : ajouter des pages pour les endpoints d'ecriture (document_add, document_mod). +- Eventuellement : ameliorer l'affichage des resultats (pagination, formatage). diff --git a/memory-bank/productContext.md b/memory-bank/productContext.md index 601798f..fc30190 100644 --- a/memory-bank/productContext.md +++ b/memory-bank/productContext.md @@ -1,6 +1,6 @@ # Product Context -Derniere mise a jour : 2026-02-19 +Derniere mise a jour : 2026-02-20 ## Pourquoi ce projet existe @@ -14,7 +14,8 @@ L'API Logistics (Flex/ESI Gescom) est un systeme de gestion commerciale accessib - **Exploration de l'API** : Le dashboard permet de tester chaque endpoint avec des parametres personnalisables, sans avoir besoin de Postman. - **Comprehension de la structure** : La page "Tables" permet de decouvrir les tables et colonnes disponibles dans l'API. -- **Tracabilite** : Chaque requete effectuee est enregistree dans `api_request_logs` pour pouvoir analyser les echanges. +- **Tracabilite** : Chaque requete effectuee (reussie ou echouee) est enregistree dans `api_request_logs` pour pouvoir analyser les echanges. +- **Resilience** : Les erreurs de connexion sont gerees avec retry automatique et messages explicites en francais. ## Experience utilisateur @@ -26,4 +27,4 @@ L'utilisateur accede au dashboard Filament sur `http://api-logistics.test/admin` 4. **Journaux** : Recherche par type de journal (TYPE). 5. **Tiers** : Recherche de tiers (search obligatoire) + historique des articles d'un tiers. -Chaque page affiche les resultats sous forme de tableau dynamique et les metadonnees de la reponse (nombre de resultats, succes). +Chaque page affiche les resultats sous forme de tableau dynamique et les metadonnees de la reponse (nombre de resultats, succes). En cas d'erreur de connexion a l'API, un message clair en francais est affiche a l'utilisateur. diff --git a/memory-bank/progress.md b/memory-bank/progress.md index 546a2be..3fb0665 100644 --- a/memory-bank/progress.md +++ b/memory-bank/progress.md @@ -1,6 +1,6 @@ # Progress -Derniere mise a jour : 2026-02-19 +Derniere mise a jour : 2026-02-20 ## Ce qui fonctionne @@ -9,33 +9,35 @@ Derniere mise a jour : 2026-02-19 - [x] Fortify installe (authentification existante, non utilisee par Filament) - [x] Documentation API redigee (`documentation/WEB-A-1 (3).md`, `documentation/result.pdf`) - [x] Memory bank cree et structure (`memory-bank/`, `.cursor/rules/memory-bank.mdc`) -- [x] Configuration API Logistics (`.env`, `config/logistics.php`) -- [x] `LogisticsService` cree (`app/Services/LogisticsService.php`) avec 17 methodes +- [x] Configuration API Logistics (`.env`, `config/logistics.php`) avec timeout et retry +- [x] `LogisticsService` cree (`app/Services/LogisticsService.php`) avec 17 methodes, timeout, retry, gestion d'erreur +- [x] `LogisticsApiException` creee (`app/Exceptions/LogisticsApiException.php`) avec messages francais - [x] Migration `api_request_logs` creee - [x] Filament v5.0.0 installe et configure sans authentification - [x] 5 pages Filament creees : TablesExplorer, Articles, Documents, Journaux, Tiers - [x] 5 vues Blade associees dans `resources/views/filament/pages/` -- [x] 8 tests Pest pour LogisticsService (tous passent) +- [x] Gestion d'erreur dans toutes les pages Filament (LogisticsApiException + Throwable) +- [x] Logging des requetes API reussies et echouees dans `api_request_logs` +- [x] 12 tests Pest pour LogisticsService (tous passent) - [x] `README.md` cree - [x] Formatage Pint valide +- [x] CI GitHub Actions (lint + tests) ## Ce qui reste a faire -- [ ] Creer la base de donnees MySQL `api_logistics` (a faire par l'utilisateur) -- [ ] Executer `php artisan migrate` (a faire par l'utilisateur) -- [ ] Renseigner la cle API dans `.env` (`LOGISTICS_API_KEY`) -- [ ] Executer `npm run build` +- [ ] Resoudre la connectivite reseau : deployer sur le reseau distant ou mettre en place un tunnel - [ ] Tester le dashboard avec de vraies donnees API - [ ] Eventuellement : pages d'ecriture (document_add, document_mod) - [ ] Eventuellement : ameliorer l'affichage des resultats (pagination, formatage) ## Problemes connus -- L'erreur `SQLSTATE[HY000] [1049] Unknown database 'api_logistics'` apparait lors de `composer update` car le script `boost:update` tente d'acceder a la base de donnees qui n'existe pas encore. Sans impact sur le fonctionnement une fois la base creee. +- **API injoignable depuis la machine locale** : Le serveur `tse-10-test.esiweb.pro` est sur un reseau prive accessible uniquement via Bureau a distance (RDP). L'application locale recoit `cURL error 28: Connection timed out`. Solution : deployer sur le reseau distant ou creer un tunnel SSH/VPN. +- L'erreur `SQLSTATE[HY000] [1049] Unknown database` peut apparaitre lors de `composer update` si la base n'est pas encore creee (script `boost:update`). Sans impact une fois la base creee. ## Metriques -- Tests : 8 (tous passent) +- Tests : 12 (tous passent, 18 assertions) - Pages Filament : 5 - Endpoints API couverts par LogisticsService : 17 - Migrations : 5 (users, cache, jobs, two_factor, api_request_logs) diff --git a/memory-bank/projectbrief.md b/memory-bank/projectbrief.md index 816594b..038f761 100644 --- a/memory-bank/projectbrief.md +++ b/memory-bank/projectbrief.md @@ -1,6 +1,6 @@ # Project Brief -Derniere mise a jour : 2026-02-19 +Derniere mise a jour : 2026-02-20 ## Vision @@ -19,11 +19,13 @@ Application Laravel de test dont l'objectif est de comprendre le fonctionnement - Formulaires de recherche parametrables pour chaque endpoint. - Affichage des resultats bruts retournes par l'API. - Tracage des requetes effectuees dans une table `api_request_logs`. +- Gestion robuste des erreurs API (timeout, retry, messages utilisateur en francais). ## Contraintes - Pas d'authentification sur le dashboard (projet de test interne). - L'API Logistics est hebergee sur le serveur TSE-10-TEST (`http://tse-10-test.esiweb.pro`). +- Le serveur API est accessible uniquement via le reseau interne (connexion Bureau a distance / RDP requise). L'application doit etre deployee sur ce reseau ou un tunnel doit etre mis en place. - Toutes les requetes API sont en POST et necessitent un header `X-API-KEY`. - Le nom du dossier dans les URLs de l'API doit etre en minuscules. diff --git a/memory-bank/systemPatterns.md b/memory-bank/systemPatterns.md index cdc0676..dfa459e 100644 --- a/memory-bank/systemPatterns.md +++ b/memory-bank/systemPatterns.md @@ -1,6 +1,6 @@ # System Patterns -Derniere mise a jour : 2026-02-19 +Derniere mise a jour : 2026-02-20 ## Architecture applicative @@ -14,8 +14,12 @@ Utilisateur --> Filament Dashboard (/admin) LogisticsService (app/Services/) | +---> API Logistics (HTTP POST + X-API-KEY) + | (retry automatique sur ConnectionException) | +---> api_request_logs (MySQL) + | (succes ET echecs) + | + +---> LogisticsApiException (en cas d'erreur) ``` ## Patterns utilises @@ -26,8 +30,19 @@ Utilisateur --> Filament Dashboard (/admin) - Construit l'URL a partir de `config('logistics.base_url')`, `config('logistics.folder')` et le nom de l'endpoint. - Ajoute automatiquement le header `X-API-KEY`. -- Enregistre chaque requete dans la table `api_request_logs`. +- Configure `timeout()` et `connectTimeout()` depuis la config. +- Effectue un retry automatique (nombre et delai configurables) uniquement sur les `ConnectionException`. +- Enregistre chaque requete dans la table `api_request_logs` (succes et echecs). - Retourne un tableau PHP avec les cles `data`, `metadata`, `error`. +- Lance une `LogisticsApiException` en cas d'erreur de connexion ou de requete. + +### Exception dediee + +`App\Exceptions\LogisticsApiException` (etend `RuntimeException`) fournit : + +- Des messages d'erreur en francais lisibles par l'utilisateur. +- Des proprietes `endpoint` et `params` pour le contexte de l'erreur. +- Deux methodes statiques : `connectionTimeout()` (API injoignable) et `requestFailed()` (erreur de requete). ### Pages Filament personnalisees (pas de Resources) @@ -36,33 +51,38 @@ L'application n'utilise pas de Resources Filament (pas de CRUD local). Chaque pa - Etend `Filament\Pages\Page`. - Utilise des proprietes Livewire publiques pour les champs de formulaire. - Appelle `LogisticsService` via `app(LogisticsService::class)` dans des methodes d'action (ex: `searchArticles()`). +- Attrape `LogisticsApiException` pour afficher un message clair, et `\Throwable` en fallback. - Affiche les resultats dans des tableaux HTML dynamiques generes dans les vues Blade. ### Configuration externalisee -Les parametres de connexion a l'API sont dans `config/logistics.php` et lus depuis `.env`. +Les parametres de connexion a l'API (URL, cle, dossier, timeout, retry) sont dans `config/logistics.php` et lus depuis `.env`. ## Structure des repertoires ``` app/ + Exceptions/ + LogisticsApiException.php # Exception dediee API Filament/ Pages/ - Articles.php # Recherche articles + stock - Documents.php # Recherche documents + detail - Journaux.php # Recherche journaux - TablesExplorer.php # Exploration tables + colonnes - Tiers.php # Recherche tiers + historique + Articles.php # Recherche articles + stock + Documents.php # Recherche documents + detail + Journaux.php # Recherche journaux + TablesExplorer.php # Exploration tables + colonnes + Tiers.php # Recherche tiers + historique Models/ - User.php # Modele utilisateur (Fortify) + User.php # Modele utilisateur (Fortify) Providers/ Filament/ - AdminPanelProvider.php # Configuration du panel (sans auth) + AdminPanelProvider.php # Configuration du panel (sans auth) + FortifyServiceProvider.php # Authentification Fortify + AppServiceProvider.php # Config globale (CarbonImmutable, DB safety) Services/ - LogisticsService.php # Service centralise API Logistics + LogisticsService.php # Service centralise API Logistics config/ - logistics.php # Configuration API Logistics + logistics.php # Configuration API Logistics (URL, cle, timeout, retry) database/ migrations/ @@ -81,7 +101,7 @@ resources/views/ tiers.blade.php tests/Feature/ - LogisticsServiceTest.php # 8 tests avec mocks HTTP + LogisticsServiceTest.php # 12 tests avec mocks HTTP ``` ## Conventions @@ -90,3 +110,5 @@ tests/Feature/ - Filament v5 : la propriete `$view` est non-static (`protected string $view`). - Les proprietes statiques (`$navigationIcon`, `$navigationLabel`, `$title`, `$navigationSort`) restent static. - Les appels API passent toujours par `LogisticsService`, jamais directement par `Http::`. +- Les pages Filament attrapent `LogisticsApiException` en premier, puis `\Throwable` en fallback. +- Les messages d'erreur affiches a l'utilisateur sont en francais. diff --git a/memory-bank/techContext.md b/memory-bank/techContext.md index 311ac50..ff49fa2 100644 --- a/memory-bank/techContext.md +++ b/memory-bank/techContext.md @@ -1,6 +1,6 @@ # Tech Context -Derniere mise a jour : 2026-02-19 +Derniere mise a jour : 2026-02-20 ## Stack technique @@ -33,21 +33,35 @@ Derniere mise a jour : 2026-02-19 LOGISTICS_API_BASE_URL=http://tse-10-test.esiweb.pro LOGISTICS_API_KEY= LOGISTICS_API_FOLDER=esigescom +LOGISTICS_API_TIMEOUT=30 +LOGISTICS_API_CONNECT_TIMEOUT=10 +LOGISTICS_API_RETRY_TIMES=3 +LOGISTICS_API_RETRY_SLEEP_MS=500 ``` Fichier de config : `config/logistics.php` +| Cle de config | Variable .env | Defaut | Description | +|---------------|---------------|--------|-------------| +| `logistics.base_url` | `LOGISTICS_API_BASE_URL` | - | URL de base de l'API | +| `logistics.api_key` | `LOGISTICS_API_KEY` | - | Cle d'authentification | +| `logistics.folder` | `LOGISTICS_API_FOLDER` | - | Dossier dans l'URL | +| `logistics.timeout` | `LOGISTICS_API_TIMEOUT` | 30 | Timeout total de la requete (secondes) | +| `logistics.connect_timeout` | `LOGISTICS_API_CONNECT_TIMEOUT` | 10 | Timeout de connexion (secondes) | +| `logistics.retry.times` | `LOGISTICS_API_RETRY_TIMES` | 3 | Nombre de tentatives en cas d'echec de connexion | +| `logistics.retry.sleep_ms` | `LOGISTICS_API_RETRY_SLEEP_MS` | 500 | Delai entre les tentatives (ms) | + ### Base de donnees - Connexion : MySQL -- Base : `api_logistics` +- Base : `logistics` - Configuration dans `.env` : `DB_CONNECTION=mysql` ## API Logistics ### Connexion -- Serveur : TSE-10-TEST +- Serveur : TSE-10-TEST (reseau prive, accessible uniquement via Bureau a distance / RDP) - Base URL : `http://tse-10-test.esiweb.pro` - Dossier : `esigescom` (minuscules obligatoires) - Authentification : Header `X-API-KEY` diff --git a/tests/Feature/LogisticsServiceTest.php b/tests/Feature/LogisticsServiceTest.php index c1bdea5..82f3313 100644 --- a/tests/Feature/LogisticsServiceTest.php +++ b/tests/Feature/LogisticsServiceTest.php @@ -1,6 +1,9 @@ 'http://test-server.local', 'logistics.api_key' => 'test-api-key', 'logistics.folder' => 'testfolder', + 'logistics.timeout' => 30, + 'logistics.connect_timeout' => 10, + 'logistics.retry.times' => 1, + 'logistics.retry.sleep_ms' => 0, ]); }); @@ -123,3 +130,57 @@ it('returns fallback data when API returns empty response', function () { expect($result)->toHaveKey('error') ->and($result['metadata']['issuccess'])->toBeFalse(); }); + +it('throws LogisticsApiException on connection timeout', function () { + Http::fake(fn () => throw new ConnectionException('Connection timed out')); + + $service = app(LogisticsService::class); + $service->tablesList(); +})->throws(LogisticsApiException::class, "L'API Logistics est injoignable"); + +it('throws LogisticsApiException on general request failure', function () { + Http::fake(fn () => throw new \RuntimeException('Server error')); + + $service = app(LogisticsService::class); + $service->tablesList(); +})->throws(LogisticsApiException::class, "La requete vers l'API Logistics a echoue"); + +it('logs failed requests to api_request_logs as valid JSON', function () { + Http::fake(fn () => throw new ConnectionException('Connection timed out')); + + $service = app(LogisticsService::class); + + try { + $service->tablesList(); + } catch (LogisticsApiException) { + // expected + } + + $this->assertDatabaseHas('api_request_logs', [ + 'endpoint' => 'tables_list', + 'response_status' => 0, + ]); + + $log = DB::table('api_request_logs')->where('endpoint', 'tables_list')->first(); + $decoded = json_decode($log->response_data, true); + + expect($decoded)->toBeArray() + ->and($decoded)->toHaveKey('error'); +}); + +it('includes endpoint info in LogisticsApiException', function () { + Http::fake(fn () => throw new ConnectionException('Connection timed out')); + + $service = app(LogisticsService::class); + + try { + $service->artList(['search' => 'test']); + } catch (LogisticsApiException $e) { + expect($e->endpoint)->toBe('art_list') + ->and($e->params)->toBe(['search' => 'test']); + + return; + } + + $this->fail('Expected LogisticsApiException was not thrown'); +});