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:
@@ -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
|
||||
|
||||
38
app/Exceptions/LogisticsApiException.php
Normal file
38
app/Exceptions/LogisticsApiException.php
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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 = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
]);
|
||||
|
||||
@@ -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),
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
517
documentation/documentation_api_logistics.md
Normal file
517
documentation/documentation_api_logistics.md
Normal 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)
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user