Implement robust error handling and configuration for Logistics API interactions

- Introduced `LogisticsApiException` to handle connection and request errors with user-friendly messages in French.
- Updated `LogisticsService` to include configurable timeout, connection timeout, retry attempts, and sleep duration for retries.
- Enhanced error handling in Filament pages to catch `LogisticsApiException` and provide clear feedback to users.
- Updated `.env` and `config/logistics.php` to support new configuration options.
- Added logging for failed API requests in `api_request_logs`.
- Created comprehensive API documentation for Logistics endpoints.
This commit is contained in:
2026-02-20 10:06:04 +01:00
parent 07a3b3a874
commit 4aef33f270
18 changed files with 820 additions and 64 deletions

View File

@@ -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

View File

@@ -0,0 +1,38 @@
<?php
namespace App\Exceptions;
use RuntimeException;
class LogisticsApiException extends RuntimeException
{
public function __construct(
string $message,
public readonly string $endpoint = '',
public readonly array $params = [],
int $code = 0,
?\Throwable $previous = null,
) {
parent::__construct($message, $code, $previous);
}
public static function connectionTimeout(string $endpoint, array $params, \Throwable $previous): self
{
return new self(
message: "L'API Logistics est injoignable. Verifiez que le serveur est accessible et que votre connexion reseau fonctionne.",
endpoint: $endpoint,
params: $params,
previous: $previous,
);
}
public static function requestFailed(string $endpoint, array $params, \Throwable $previous): self
{
return new self(
message: "La requete vers l'API Logistics a echoue : {$previous->getMessage()}",
endpoint: $endpoint,
params: $params,
previous: $previous,
);
}
}

View File

@@ -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 = [];
}
}
}

View File

@@ -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 = [];
}
}
}

View File

@@ -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 = [];
}
}
}

View File

@@ -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()}";
}
}
}

View File

@@ -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 = [];
}
}
}

View File

@@ -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,14 +155,21 @@ 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}";
try {
$response = Http::withHeaders([
'X-API-KEY' => $this->apiKey,
])->post($url, $params);
])
->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);
@@ -157,15 +178,39 @@ class LogisticsService
'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(),
]);

View File

@@ -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),
],
];

View File

@@ -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://<hote>:<port>/<dossier>/<endpoint>
```
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` | `<votre cle API>` |
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": "<resultat de la requete>",
"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 /<dossier>/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 /<dossier>/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 /<dossier>/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 /<dossier>/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 /<dossier>/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 /<dossier>/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 /<dossier>/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 /<dossier>/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 /<dossier>/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 /<dossier>/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 /<dossier>/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 /<dossier>/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 /<dossier>/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 /<dossier>/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 /<dossier>/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 /<dossier>/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 /<dossier>/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 /<dossier>/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 /<dossier>/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)

View File

@@ -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

View File

@@ -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).

View File

@@ -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.

View File

@@ -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)

View File

@@ -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.

View File

@@ -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,16 +51,19 @@ 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
@@ -58,11 +76,13 @@ app/
Providers/
Filament/
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
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.

View File

@@ -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=<cle API>
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`

View File

@@ -1,6 +1,9 @@
<?php
use App\Exceptions\LogisticsApiException;
use App\Services\LogisticsService;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;
beforeEach(function () {
@@ -8,6 +11,10 @@ beforeEach(function () {
'logistics.base_url' => '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');
});