feature a test
This commit is contained in:
parent
f31f1b6c69
commit
b3ed387197
@ -515,6 +515,449 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "API de calcul automatisé de devis transport maritime - génère 3 offres (Rapide, Standard, Économique) basées sur les grilles tarifaires LESCHACO"
|
"description": "API de calcul automatisé de devis transport maritime - génère 3 offres (Rapide, Standard, Économique) basées sur les grilles tarifaires LESCHACO"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "📦 SSC Export Folders",
|
||||||
|
"item": [
|
||||||
|
{
|
||||||
|
"name": "🔍 Rechercher Dossiers",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Content-Type",
|
||||||
|
"value": "application/json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Authorization",
|
||||||
|
"value": "Bearer {{jwt_token}}"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"url": {
|
||||||
|
"raw": "{{base_url}}/api/v1/export-folders?page=0&size=20&sortBy=dateCreation&sortDir=DESC&statut=CREE&numeroDossier=EXP-2024",
|
||||||
|
"host": ["{{base_url}}"],
|
||||||
|
"path": ["api", "v1", "export-folders"],
|
||||||
|
"query": [
|
||||||
|
{
|
||||||
|
"key": "page",
|
||||||
|
"value": "0",
|
||||||
|
"description": "Numéro de page (0-indexed)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "size",
|
||||||
|
"value": "20",
|
||||||
|
"description": "Taille de page"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "sortBy",
|
||||||
|
"value": "dateCreation",
|
||||||
|
"description": "Champ de tri"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "sortDir",
|
||||||
|
"value": "DESC",
|
||||||
|
"description": "Direction de tri"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "statut",
|
||||||
|
"value": "CREE",
|
||||||
|
"description": "Filtrer par statut",
|
||||||
|
"disabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "numeroDossier",
|
||||||
|
"value": "EXP-2024",
|
||||||
|
"description": "Recherche par numéro de dossier",
|
||||||
|
"disabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "companyId",
|
||||||
|
"value": "1",
|
||||||
|
"description": "Filtrer par entreprise (admin uniquement)",
|
||||||
|
"disabled": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": [],
|
||||||
|
"protocolProfileBehavior": {
|
||||||
|
"disableBodyPruning": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "📋 Obtenir Dossier par ID",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Content-Type",
|
||||||
|
"value": "application/json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Authorization",
|
||||||
|
"value": "Bearer {{jwt_token}}"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"url": {
|
||||||
|
"raw": "{{base_url}}/api/v1/export-folders/1",
|
||||||
|
"host": ["{{base_url}}"],
|
||||||
|
"path": ["api", "v1", "export-folders", "1"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "➕ Créer Dossier depuis Devis",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Content-Type",
|
||||||
|
"value": "application/json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Authorization",
|
||||||
|
"value": "Bearer {{jwt_token}}"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\n \"quoteId\": 1,\n \"commentairesClient\": \"Demande de transport urgent pour livraison avant fin mars. Marchandises fragiles - manipulation soignée requise.\"\n}"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{base_url}}/api/v1/export-folders",
|
||||||
|
"host": ["{{base_url}}"],
|
||||||
|
"path": ["api", "v1", "export-folders"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "🔄 Mettre à jour Statut",
|
||||||
|
"request": {
|
||||||
|
"method": "PUT",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Content-Type",
|
||||||
|
"value": "application/json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Authorization",
|
||||||
|
"value": "Bearer {{jwt_token}}"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\n \"newStatus\": \"DOCUMENTS_EN_ATTENTE\",\n \"comment\": \"Dossier validé, en attente des documents clients\"\n}"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{base_url}}/api/v1/export-folders/1/status",
|
||||||
|
"host": ["{{base_url}}"],
|
||||||
|
"path": ["api", "v1", "export-folders", "1", "status"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "📎 Uploader Document",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Authorization",
|
||||||
|
"value": "Bearer {{jwt_token}}"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"mode": "formdata",
|
||||||
|
"formdata": [
|
||||||
|
{
|
||||||
|
"key": "file",
|
||||||
|
"type": "file",
|
||||||
|
"src": "/path/to/your/document.pdf"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "typeDocumentId",
|
||||||
|
"value": "1",
|
||||||
|
"type": "text",
|
||||||
|
"description": "ID du type de document (1=Facture commerciale, 2=Liste de colisage, etc.)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "description",
|
||||||
|
"value": "Facture commerciale pour export maritime",
|
||||||
|
"type": "text",
|
||||||
|
"description": "Description optionnelle"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{base_url}}/api/v1/export-folders/1/documents",
|
||||||
|
"host": ["{{base_url}}"],
|
||||||
|
"path": ["api", "v1", "export-folders", "1", "documents"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "📑 Lister Documents",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Content-Type",
|
||||||
|
"value": "application/json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Authorization",
|
||||||
|
"value": "Bearer {{jwt_token}}"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"url": {
|
||||||
|
"raw": "{{base_url}}/api/v1/export-folders/1/documents",
|
||||||
|
"host": ["{{base_url}}"],
|
||||||
|
"path": ["api", "v1", "export-folders", "1", "documents"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "📜 Historique du Dossier",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Content-Type",
|
||||||
|
"value": "application/json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Authorization",
|
||||||
|
"value": "Bearer {{jwt_token}}"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"url": {
|
||||||
|
"raw": "{{base_url}}/api/v1/export-folders/1/history?limit=50",
|
||||||
|
"host": ["{{base_url}}"],
|
||||||
|
"path": ["api", "v1", "export-folders", "1", "history"],
|
||||||
|
"query": [
|
||||||
|
{
|
||||||
|
"key": "limit",
|
||||||
|
"value": "50",
|
||||||
|
"description": "Nombre maximum d'entrées à retourner"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "🔐 Actions Autorisées",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Content-Type",
|
||||||
|
"value": "application/json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Authorization",
|
||||||
|
"value": "Bearer {{jwt_token}}"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"url": {
|
||||||
|
"raw": "{{base_url}}/api/v1/export-folders/1/permissions",
|
||||||
|
"host": ["{{base_url}}"],
|
||||||
|
"path": ["api", "v1", "export-folders", "1", "permissions"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "🚢 Système SSC de Gestion des Dossiers d'Export - Workflow complet de création, suivi et gestion documentaire pour les expéditions maritimes",
|
||||||
|
"auth": {
|
||||||
|
"type": "bearer",
|
||||||
|
"bearer": [
|
||||||
|
{
|
||||||
|
"key": "token",
|
||||||
|
"value": "{{jwt_token}}",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "📊 Grilles Tarifaires",
|
||||||
|
"item": [
|
||||||
|
{
|
||||||
|
"name": "📋 Lister Grilles Tarifaires",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Content-Type",
|
||||||
|
"value": "application/json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"url": {
|
||||||
|
"raw": "{{base_url}}/api/v1/grilles-tarifaires?page=0&size=20",
|
||||||
|
"host": ["{{base_url}}"],
|
||||||
|
"path": ["api", "v1", "grilles-tarifaires"],
|
||||||
|
"query": [
|
||||||
|
{
|
||||||
|
"key": "page",
|
||||||
|
"value": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "size",
|
||||||
|
"value": "20"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "nom",
|
||||||
|
"value": "LESCHACO",
|
||||||
|
"disabled": true,
|
||||||
|
"description": "Filtrer par nom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "paysOrigine",
|
||||||
|
"value": "France",
|
||||||
|
"disabled": true,
|
||||||
|
"description": "Filtrer par pays d'origine"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "🔍 Obtenir Grille par ID",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Content-Type",
|
||||||
|
"value": "application/json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"url": {
|
||||||
|
"raw": "{{base_url}}/api/v1/grilles-tarifaires/1",
|
||||||
|
"host": ["{{base_url}}"],
|
||||||
|
"path": ["api", "v1", "grilles-tarifaires", "1"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "✅ Valider Grille Tarifaire",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Authorization",
|
||||||
|
"value": "Bearer {{jwt_token}}"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"mode": "formdata",
|
||||||
|
"formdata": [
|
||||||
|
{
|
||||||
|
"key": "file",
|
||||||
|
"type": "file",
|
||||||
|
"src": "/path/to/your/grille.xlsx",
|
||||||
|
"description": "Fichier Excel ou CSV contenant la grille tarifaire"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{base_url}}/api/v1/grilles-tarifaires/validate",
|
||||||
|
"host": ["{{base_url}}"],
|
||||||
|
"path": ["api", "v1", "grilles-tarifaires", "validate"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "📤 Import CSV",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Authorization",
|
||||||
|
"value": "Bearer {{jwt_token}}"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"mode": "formdata",
|
||||||
|
"formdata": [
|
||||||
|
{
|
||||||
|
"key": "file",
|
||||||
|
"type": "file",
|
||||||
|
"src": "/path/to/your/grille.csv"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{base_url}}/api/v1/grilles-tarifaires/import/csv",
|
||||||
|
"host": ["{{base_url}}"],
|
||||||
|
"path": ["api", "v1", "grilles-tarifaires", "import", "csv"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "📤 Import Excel",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Authorization",
|
||||||
|
"value": "Bearer {{jwt_token}}"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"mode": "formdata",
|
||||||
|
"formdata": [
|
||||||
|
{
|
||||||
|
"key": "file",
|
||||||
|
"type": "file",
|
||||||
|
"src": "/path/to/your/grille.xlsx"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{base_url}}/api/v1/grilles-tarifaires/import/excel",
|
||||||
|
"host": ["{{base_url}}"],
|
||||||
|
"path": ["api", "v1", "grilles-tarifaires", "import", "excel"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "📤 Import JSON",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Content-Type",
|
||||||
|
"value": "application/json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Authorization",
|
||||||
|
"value": "Bearer {{jwt_token}}"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\n \"nom\": \"Grille LESCHACO 2025\",\n \"paysOrigine\": \"France\",\n \"paysDestination\": \"Chine\",\n \"portOrigine\": \"FRBOL\",\n \"portDestination\": \"CNSHA\",\n \"dateValiditeDebut\": \"2025-01-01\",\n \"dateValiditeFin\": \"2025-12-31\",\n \"tarifsFret\": [\n {\n \"poidsMin\": 0.0,\n \"poidsMax\": 100.0,\n \"prixParKg\": 2.50,\n \"prixForfaitaire\": 150.00\n },\n {\n \"poidsMin\": 100.0,\n \"poidsMax\": 500.0,\n \"prixParKg\": 2.20,\n \"prixForfaitaire\": 200.00\n }\n ],\n \"fraisAdditionnels\": [\n {\n \"type\": \"MANUTENTION\",\n \"montant\": 45.00,\n \"description\": \"Frais de manutention portuaire\"\n },\n {\n \"type\": \"DOCUMENTATION\",\n \"montant\": 25.00,\n \"description\": \"Frais de documentation\"\n }\n ],\n \"surchargesDangereuses\": [\n {\n \"classe\": \"3\",\n \"pourcentage\": 15.0,\n \"montantFixe\": 100.0,\n \"description\": \"Surcharge marchandises inflammables\"\n }\n ]\n}"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{base_url}}/api/v1/grilles-tarifaires/import/json",
|
||||||
|
"host": ["{{base_url}}"],
|
||||||
|
"path": ["api", "v1", "grilles-tarifaires", "import", "json"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "🏷️ Gestion des grilles tarifaires LESCHACO - Import, validation et consultation des tarifs pour le calcul automatisé des devis"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"event": [
|
"event": [
|
||||||
|
|||||||
@ -0,0 +1,363 @@
|
|||||||
|
package com.dh7789dev.xpeditis.controller.api.v1;
|
||||||
|
|
||||||
|
import com.dh7789dev.xpeditis.dto.app.*;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
|
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/documents")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
|
@Tag(name = "Documents", description = "Gestion des documents SSC avec validation")
|
||||||
|
@SecurityRequirement(name = "bearerAuth")
|
||||||
|
public class DocumentRestController {
|
||||||
|
|
||||||
|
// TODO: Inject required services
|
||||||
|
// private final DocumentValidationService documentValidationService;
|
||||||
|
// private final DocumentStorageService documentStorageService;
|
||||||
|
// private final ExportFolderPermissionService permissionService;
|
||||||
|
// private final NotificationService notificationService;
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Télécharger un document",
|
||||||
|
description = "Télécharge le fichier document si autorisé",
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(responseCode = "200", description = "Fichier téléchargé"),
|
||||||
|
@ApiResponse(responseCode = "403", description = "Accès refusé"),
|
||||||
|
@ApiResponse(responseCode = "404", description = "Document non trouvé")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@GetMapping("/{id}/download")
|
||||||
|
@PreAuthorize("hasRole('COMPANY_USER') or hasRole('ADMIN')")
|
||||||
|
public ResponseEntity<Resource> downloadDocument(
|
||||||
|
@Parameter(description = "ID du document")
|
||||||
|
@PathVariable Long id
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
// TODO: Implement download with permission check
|
||||||
|
// UserEntity currentUser = getCurrentUser();
|
||||||
|
// DocumentDossierEntity document = documentService.findById(id);
|
||||||
|
//
|
||||||
|
// if (!permissionService.canPerformAction(currentUser, document.getDossier(), FolderAction.DOWNLOAD_DOCUMENTS)) {
|
||||||
|
// return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Resource resource = documentStorageService.loadAsResource(document);
|
||||||
|
|
||||||
|
log.info("Téléchargement document ID: {}", id);
|
||||||
|
|
||||||
|
// Placeholder response - would return actual file resource
|
||||||
|
return ResponseEntity.ok()
|
||||||
|
.contentType(MediaType.APPLICATION_OCTET_STREAM)
|
||||||
|
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"document.pdf\"")
|
||||||
|
.body(null); // Would return actual Resource
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Erreur lors du téléchargement du document {}", id, e);
|
||||||
|
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Valider un document",
|
||||||
|
description = "Approuve ou refuse un document (admin SSC uniquement)",
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(responseCode = "200", description = "Document validé"),
|
||||||
|
@ApiResponse(responseCode = "403", description = "Droits insuffisants"),
|
||||||
|
@ApiResponse(responseCode = "404", description = "Document non trouvé")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@PutMapping("/{id}/validate")
|
||||||
|
@PreAuthorize("hasRole('ADMIN_SSC') or hasRole('SUPER_ADMIN')")
|
||||||
|
public ResponseEntity<DocumentSummaryDto> validateDocument(
|
||||||
|
@Parameter(description = "ID du document")
|
||||||
|
@PathVariable Long id,
|
||||||
|
|
||||||
|
@Parameter(description = "Décision de validation")
|
||||||
|
@Valid @RequestBody ValidationRequest request
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
// TODO: Implement document validation
|
||||||
|
// UserEntity currentUser = getCurrentUser();
|
||||||
|
// DocumentSummaryDto validatedDocument = documentValidationService.processValidation(
|
||||||
|
// currentUser, id, request);
|
||||||
|
|
||||||
|
log.info("Validation document {} - décision: {}, commentaire: {}",
|
||||||
|
id, request.isApproved(), request.getComment());
|
||||||
|
|
||||||
|
// Placeholder response
|
||||||
|
return ResponseEntity.ok(DocumentSummaryDto.builder()
|
||||||
|
.id(id)
|
||||||
|
.statutVerification(request.isApproved() ?
|
||||||
|
StatutVerification.VALIDE : StatutVerification.REFUSE)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Erreur lors de la validation du document {}", id, e);
|
||||||
|
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Supprimer un document",
|
||||||
|
description = "Supprime un document si autorisé",
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(responseCode = "204", description = "Document supprimé"),
|
||||||
|
@ApiResponse(responseCode = "403", description = "Accès refusé"),
|
||||||
|
@ApiResponse(responseCode = "404", description = "Document non trouvé")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
@PreAuthorize("hasRole('COMPANY_USER') or hasRole('ADMIN')")
|
||||||
|
public ResponseEntity<Void> deleteDocument(
|
||||||
|
@Parameter(description = "ID du document")
|
||||||
|
@PathVariable Long id
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
// TODO: Implement deletion with permission check
|
||||||
|
// UserEntity currentUser = getCurrentUser();
|
||||||
|
// DocumentDossierEntity document = documentService.findById(id);
|
||||||
|
//
|
||||||
|
// if (!permissionService.canPerformAction(currentUser, document.getDossier(), FolderAction.DELETE_DOCUMENTS)) {
|
||||||
|
// return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// documentValidationService.deleteDocument(currentUser, id);
|
||||||
|
|
||||||
|
log.info("Suppression document ID: {}", id);
|
||||||
|
|
||||||
|
return ResponseEntity.noContent().build();
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Erreur lors de la suppression du document {}", id, e);
|
||||||
|
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Uploader une nouvelle version",
|
||||||
|
description = "Remplace un document par une nouvelle version",
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(responseCode = "201", description = "Nouvelle version uploadée"),
|
||||||
|
@ApiResponse(responseCode = "400", description = "Fichier invalide"),
|
||||||
|
@ApiResponse(responseCode = "403", description = "Accès refusé")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@PostMapping("/{id}/new-version")
|
||||||
|
@PreAuthorize("hasRole('COMPANY_USER') or hasRole('ADMIN')")
|
||||||
|
public ResponseEntity<DocumentSummaryDto> uploadNewVersion(
|
||||||
|
@Parameter(description = "ID du document original")
|
||||||
|
@PathVariable Long id,
|
||||||
|
|
||||||
|
@Parameter(description = "Nouveau fichier")
|
||||||
|
@RequestPart("file") MultipartFile file,
|
||||||
|
|
||||||
|
@Parameter(description = "Description des changements")
|
||||||
|
@RequestPart(value = "description", required = false) String description
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
// TODO: Implement version upload
|
||||||
|
// UserEntity currentUser = getCurrentUser();
|
||||||
|
// DocumentDossierEntity originalDocument = documentService.findById(id);
|
||||||
|
//
|
||||||
|
// if (!permissionService.canPerformAction(currentUser, originalDocument.getDossier(), FolderAction.UPLOAD_DOCUMENTS)) {
|
||||||
|
// return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// DocumentSummaryDto newVersion = documentValidationService.uploadNewVersion(
|
||||||
|
// currentUser, id, file, description);
|
||||||
|
|
||||||
|
log.info("Upload nouvelle version document {} - fichier: {}", id, file.getOriginalFilename());
|
||||||
|
|
||||||
|
// Placeholder response
|
||||||
|
return ResponseEntity.status(HttpStatus.CREATED)
|
||||||
|
.body(DocumentSummaryDto.builder()
|
||||||
|
.id(id + 1000L) // Simulated new version ID
|
||||||
|
.nomOriginal(file.getOriginalFilename())
|
||||||
|
.numeroVersion(2)
|
||||||
|
.statutVerification(StatutVerification.EN_ATTENTE)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Erreur lors de l'upload de nouvelle version pour document {}", id, e);
|
||||||
|
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Obtenir les détails d'un document",
|
||||||
|
description = "Récupère les informations complètes d'un document",
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(responseCode = "200", description = "Détails du document"),
|
||||||
|
@ApiResponse(responseCode = "403", description = "Accès refusé"),
|
||||||
|
@ApiResponse(responseCode = "404", description = "Document non trouvé")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
@PreAuthorize("hasRole('COMPANY_USER') or hasRole('ADMIN')")
|
||||||
|
public ResponseEntity<DocumentDetailDto> getDocumentDetails(
|
||||||
|
@Parameter(description = "ID du document")
|
||||||
|
@PathVariable Long id
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
// TODO: Implement details retrieval with permissions
|
||||||
|
// UserEntity currentUser = getCurrentUser();
|
||||||
|
// DocumentDetailDto details = documentService.getDetailsWithPermissions(currentUser, id);
|
||||||
|
|
||||||
|
log.info("Consultation détails document ID: {}", id);
|
||||||
|
|
||||||
|
// Placeholder response
|
||||||
|
return ResponseEntity.ok(DocumentDetailDto.builder()
|
||||||
|
.id(id)
|
||||||
|
.statutVerification(StatutVerification.EN_ATTENTE)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Erreur lors de la récupération des détails du document {}", id, e);
|
||||||
|
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Marquer un document comme expiré",
|
||||||
|
description = "Force l'expiration d'un document (admin uniquement)",
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(responseCode = "200", description = "Document marqué expiré"),
|
||||||
|
@ApiResponse(responseCode = "403", description = "Droits insuffisants"),
|
||||||
|
@ApiResponse(responseCode = "404", description = "Document non trouvé")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@PutMapping("/{id}/expire")
|
||||||
|
@PreAuthorize("hasRole('ADMIN_SSC') or hasRole('SUPER_ADMIN')")
|
||||||
|
public ResponseEntity<Void> expireDocument(
|
||||||
|
@Parameter(description = "ID du document")
|
||||||
|
@PathVariable Long id,
|
||||||
|
|
||||||
|
@Parameter(description = "Raison de l'expiration")
|
||||||
|
@RequestBody ExpireDocumentRequest request
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
// TODO: Implement document expiration
|
||||||
|
// UserEntity currentUser = getCurrentUser();
|
||||||
|
// documentValidationService.expireDocument(currentUser, id, request.getReason());
|
||||||
|
|
||||||
|
log.info("Expiration document {} - raison: {}", id, request.getReason());
|
||||||
|
|
||||||
|
return ResponseEntity.ok().build();
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Erreur lors de l'expiration du document {}", id, e);
|
||||||
|
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Obtenir l'historique de validation d'un document",
|
||||||
|
description = "Liste chronologique des validations d'un document",
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(responseCode = "200", description = "Historique de validation"),
|
||||||
|
@ApiResponse(responseCode = "403", description = "Accès refusé"),
|
||||||
|
@ApiResponse(responseCode = "404", description = "Document non trouvé")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@GetMapping("/{id}/validation-history")
|
||||||
|
@PreAuthorize("hasRole('COMPANY_USER') or hasRole('ADMIN')")
|
||||||
|
public ResponseEntity<java.util.List<ValidationHistoryDto>> getValidationHistory(
|
||||||
|
@Parameter(description = "ID du document")
|
||||||
|
@PathVariable Long id
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
// TODO: Implement validation history retrieval
|
||||||
|
// UserEntity currentUser = getCurrentUser();
|
||||||
|
// List<ValidationHistoryDto> history = documentService.getValidationHistory(currentUser, id);
|
||||||
|
|
||||||
|
log.info("Consultation historique validation document ID: {}", id);
|
||||||
|
|
||||||
|
// Placeholder response
|
||||||
|
return ResponseEntity.ok(java.util.List.of());
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Erreur lors de la récupération de l'historique de validation du document {}", id, e);
|
||||||
|
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Add getCurrentUser() method to get authenticated user
|
||||||
|
// private UserEntity getCurrentUser() {
|
||||||
|
// // Implementation to get current authenticated user
|
||||||
|
// return null;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== REQUEST DTOs ==========
|
||||||
|
|
||||||
|
@lombok.Data
|
||||||
|
class ValidationRequest {
|
||||||
|
private boolean approved;
|
||||||
|
private String comment;
|
||||||
|
private String correctionsDemandees;
|
||||||
|
}
|
||||||
|
|
||||||
|
@lombok.Data
|
||||||
|
class ExpireDocumentRequest {
|
||||||
|
private String reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== RESPONSE DTOs ==========
|
||||||
|
|
||||||
|
@lombok.Data
|
||||||
|
@lombok.Builder
|
||||||
|
@lombok.NoArgsConstructor
|
||||||
|
@lombok.AllArgsConstructor
|
||||||
|
class DocumentDetailDto {
|
||||||
|
private Long id;
|
||||||
|
private String nomOriginal;
|
||||||
|
private String typeDocumentNom;
|
||||||
|
private Long tailleOctets;
|
||||||
|
private String typeMime;
|
||||||
|
private Integer numeroVersion;
|
||||||
|
private StatutVerification statutVerification;
|
||||||
|
private String commentaireVerification;
|
||||||
|
private String correctionsDemandees;
|
||||||
|
private String description;
|
||||||
|
private java.time.LocalDate dateValidite;
|
||||||
|
private boolean isExpired;
|
||||||
|
private String uploadePar;
|
||||||
|
private java.time.LocalDateTime dateUpload;
|
||||||
|
private String verifiePar;
|
||||||
|
private java.time.LocalDateTime dateVerification;
|
||||||
|
private boolean canDownload;
|
||||||
|
private boolean canDelete;
|
||||||
|
private boolean canValidate;
|
||||||
|
private boolean canUploadNewVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
@lombok.Data
|
||||||
|
@lombok.Builder
|
||||||
|
@lombok.NoArgsConstructor
|
||||||
|
@lombok.AllArgsConstructor
|
||||||
|
class ValidationHistoryDto {
|
||||||
|
private Long id;
|
||||||
|
private StatutVerification ancienStatut;
|
||||||
|
private StatutVerification nouveauStatut;
|
||||||
|
private String commentaire;
|
||||||
|
private String effectuePar;
|
||||||
|
private java.time.LocalDateTime dateValidation;
|
||||||
|
private String reason;
|
||||||
|
}
|
||||||
@ -0,0 +1,274 @@
|
|||||||
|
package com.dh7789dev.xpeditis.controller.api.v1;
|
||||||
|
|
||||||
|
import com.dh7789dev.xpeditis.dto.app.*;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
|
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.PageRequest;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.data.domain.Sort;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/export-folders")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
|
@Tag(name = "Export Folders", description = "Gestion des dossiers d'export SSC")
|
||||||
|
@SecurityRequirement(name = "bearerAuth")
|
||||||
|
public class ExportFolderRestController {
|
||||||
|
|
||||||
|
// TODO: Inject permission service when modules are properly connected
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Rechercher des dossiers d'export",
|
||||||
|
description = "Recherche paginée avec filtres selon permissions utilisateur"
|
||||||
|
)
|
||||||
|
@GetMapping
|
||||||
|
@PreAuthorize("hasRole('COMPANY_USER') or hasRole('ADMIN')")
|
||||||
|
public ResponseEntity<Page<ExportFolderDto>> searchFolders(
|
||||||
|
@Parameter(description = "Critères de recherche")
|
||||||
|
@RequestParam Map<String, String> params,
|
||||||
|
|
||||||
|
@Parameter(description = "Numéro de page (0-indexed)")
|
||||||
|
@RequestParam(defaultValue = "0") int page,
|
||||||
|
|
||||||
|
@Parameter(description = "Taille de page")
|
||||||
|
@RequestParam(defaultValue = "20") int size,
|
||||||
|
|
||||||
|
@Parameter(description = "Champ de tri")
|
||||||
|
@RequestParam(defaultValue = "dateCreation") String sortBy,
|
||||||
|
|
||||||
|
@Parameter(description = "Direction de tri")
|
||||||
|
@RequestParam(defaultValue = "DESC") String sortDir
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
Pageable pageable = PageRequest.of(
|
||||||
|
page, size,
|
||||||
|
Sort.Direction.fromString(sortDir),
|
||||||
|
sortBy
|
||||||
|
);
|
||||||
|
|
||||||
|
log.info("Recherche dossiers - page: {}, size: {}, filtres: {}", page, size, params);
|
||||||
|
|
||||||
|
// Placeholder response
|
||||||
|
return ResponseEntity.ok(Page.empty());
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Erreur lors de la recherche de dossiers", e);
|
||||||
|
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Créer un dossier d'export depuis un devis",
|
||||||
|
description = "Crée un nouveau dossier d'export à partir d'un devis accepté"
|
||||||
|
)
|
||||||
|
@PostMapping
|
||||||
|
@PreAuthorize("hasRole('COMPANY_USER') or hasRole('ADMIN')")
|
||||||
|
public ResponseEntity<ExportFolderDto> createFromQuote(
|
||||||
|
@Parameter(description = "Données pour création du dossier")
|
||||||
|
@Valid @RequestBody CreateFolderRequest request
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
log.info("Création dossier depuis devis ID: {}", request.getQuoteId());
|
||||||
|
|
||||||
|
// Placeholder response
|
||||||
|
return ResponseEntity.status(HttpStatus.CREATED)
|
||||||
|
.body(ExportFolderDto.builder().build());
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Erreur lors de la création du dossier", e);
|
||||||
|
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Obtenir un dossier par ID",
|
||||||
|
description = "Récupère les détails complets d'un dossier selon permissions"
|
||||||
|
)
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
@PreAuthorize("hasRole('COMPANY_USER') or hasRole('ADMIN')")
|
||||||
|
public ResponseEntity<ExportFolderDto> getFolder(
|
||||||
|
@Parameter(description = "ID du dossier")
|
||||||
|
@PathVariable Long id
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
log.info("Consultation dossier ID: {}", id);
|
||||||
|
|
||||||
|
// Placeholder response
|
||||||
|
return ResponseEntity.ok(ExportFolderDto.builder().id(id).build());
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Erreur lors de la récupération du dossier {}", id, e);
|
||||||
|
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Mettre à jour le statut d'un dossier",
|
||||||
|
description = "Change le statut d'un dossier selon le workflow"
|
||||||
|
)
|
||||||
|
@PutMapping("/{id}/status")
|
||||||
|
@PreAuthorize("hasRole('ADMIN_SSC') or hasRole('SUPER_ADMIN')")
|
||||||
|
public ResponseEntity<Void> updateStatus(
|
||||||
|
@Parameter(description = "ID du dossier")
|
||||||
|
@PathVariable Long id,
|
||||||
|
|
||||||
|
@Parameter(description = "Nouveau statut")
|
||||||
|
@Valid @RequestBody StatusUpdateRequest request
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
log.info("Mise à jour statut dossier {} vers {}", id, request.getNewStatus());
|
||||||
|
|
||||||
|
return ResponseEntity.ok().build();
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Erreur lors de la mise à jour du statut du dossier {}", id, e);
|
||||||
|
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Uploader un document",
|
||||||
|
description = "Ajoute un nouveau document au dossier"
|
||||||
|
)
|
||||||
|
@PostMapping("/{id}/documents")
|
||||||
|
@PreAuthorize("hasRole('COMPANY_USER') or hasRole('ADMIN')")
|
||||||
|
public ResponseEntity<DocumentSummaryDto> uploadDocument(
|
||||||
|
@Parameter(description = "ID du dossier")
|
||||||
|
@PathVariable Long id,
|
||||||
|
|
||||||
|
@Parameter(description = "Fichier à uploader")
|
||||||
|
@RequestPart("file") MultipartFile file,
|
||||||
|
|
||||||
|
@Parameter(description = "ID du type de document")
|
||||||
|
@RequestPart("typeDocumentId") Long typeDocumentId,
|
||||||
|
|
||||||
|
@Parameter(description = "Description optionnelle")
|
||||||
|
@RequestPart(value = "description", required = false) String description
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
log.info("Upload document pour dossier {} - fichier: {}, type: {}",
|
||||||
|
id, file.getOriginalFilename(), typeDocumentId);
|
||||||
|
|
||||||
|
// Placeholder response
|
||||||
|
return ResponseEntity.status(HttpStatus.CREATED)
|
||||||
|
.body(DocumentSummaryDto.builder().build());
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Erreur lors de l'upload de document pour dossier {}", id, e);
|
||||||
|
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Obtenir les documents d'un dossier",
|
||||||
|
description = "Liste tous les documents avec statuts de validation"
|
||||||
|
)
|
||||||
|
@GetMapping("/{id}/documents")
|
||||||
|
@PreAuthorize("hasRole('COMPANY_USER') or hasRole('ADMIN')")
|
||||||
|
public ResponseEntity<List<DocumentSummaryDto>> getDocuments(
|
||||||
|
@Parameter(description = "ID du dossier")
|
||||||
|
@PathVariable Long id
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
log.info("Consultation documents dossier ID: {}", id);
|
||||||
|
|
||||||
|
// Placeholder response
|
||||||
|
return ResponseEntity.ok(List.of());
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Erreur lors de la récupération des documents du dossier {}", id, e);
|
||||||
|
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Obtenir l'historique d'un dossier",
|
||||||
|
description = "Liste chronologique des actions sur le dossier"
|
||||||
|
)
|
||||||
|
@GetMapping("/{id}/history")
|
||||||
|
@PreAuthorize("hasRole('COMPANY_USER') or hasRole('ADMIN')")
|
||||||
|
public ResponseEntity<List<HistoryEntryDto>> getHistory(
|
||||||
|
@Parameter(description = "ID du dossier")
|
||||||
|
@PathVariable Long id,
|
||||||
|
|
||||||
|
@Parameter(description = "Nombre d'entrées max")
|
||||||
|
@RequestParam(defaultValue = "50") int limit
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
log.info("Consultation historique dossier ID: {}, limit: {}", id, limit);
|
||||||
|
|
||||||
|
// Placeholder response
|
||||||
|
return ResponseEntity.ok(List.of());
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Erreur lors de la récupération de l'historique du dossier {}", id, e);
|
||||||
|
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Obtenir les actions autorisées",
|
||||||
|
description = "Liste les actions que l'utilisateur peut effectuer sur ce dossier"
|
||||||
|
)
|
||||||
|
@GetMapping("/{id}/permissions")
|
||||||
|
@PreAuthorize("hasRole('COMPANY_USER') or hasRole('ADMIN')")
|
||||||
|
public ResponseEntity<Set<FolderAction>> getAuthorizedActions(
|
||||||
|
@Parameter(description = "ID du dossier")
|
||||||
|
@PathVariable Long id
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
log.info("Consultation permissions dossier ID: {}", id);
|
||||||
|
|
||||||
|
// Placeholder response
|
||||||
|
return ResponseEntity.ok(Set.of(FolderAction.VIEW_BASIC_INFO));
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Erreur lors de la récupération des permissions du dossier {}", id, e);
|
||||||
|
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== REQUEST DTOs ==========
|
||||||
|
|
||||||
|
@lombok.Data
|
||||||
|
class CreateFolderRequest {
|
||||||
|
private Long quoteId;
|
||||||
|
private String commentairesClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
@lombok.Data
|
||||||
|
class StatusUpdateRequest {
|
||||||
|
private DossierStatus newStatus;
|
||||||
|
private String comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
@lombok.Data
|
||||||
|
class AssignFolderRequest {
|
||||||
|
private Long adminId;
|
||||||
|
private String comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
@lombok.Data
|
||||||
|
class UpdateTransportRequest {
|
||||||
|
private String referenceBooking;
|
||||||
|
private String numeroBl;
|
||||||
|
private String numeroConteneur;
|
||||||
|
private String notesInternes;
|
||||||
|
}
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
package com.dh7789dev.xpeditis.dto.app;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class DocumentSummaryDto {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
// ========== DOCUMENT TYPE ==========
|
||||||
|
private Long typeDocumentId;
|
||||||
|
private String typeDocumentNom;
|
||||||
|
private boolean isObligatoire;
|
||||||
|
|
||||||
|
// ========== FILE INFO ==========
|
||||||
|
private String nomOriginal;
|
||||||
|
private Long tailleOctets;
|
||||||
|
private String typeMime;
|
||||||
|
private Integer numeroVersion;
|
||||||
|
|
||||||
|
// ========== VALIDATION ==========
|
||||||
|
private StatutVerification statutVerification;
|
||||||
|
private String displayStatutVerification;
|
||||||
|
private String commentaireVerification;
|
||||||
|
private String correctionsDemandees;
|
||||||
|
|
||||||
|
// ========== METADATA ==========
|
||||||
|
private String description;
|
||||||
|
private LocalDate dateValidite;
|
||||||
|
private boolean isExpired;
|
||||||
|
|
||||||
|
// ========== USER INFO ==========
|
||||||
|
private String uploadePar;
|
||||||
|
private LocalDateTime dateUpload;
|
||||||
|
private String verifiePar;
|
||||||
|
private LocalDateTime dateVerification;
|
||||||
|
|
||||||
|
// ========== ACTIONS ==========
|
||||||
|
private boolean canDownload;
|
||||||
|
private boolean canDelete;
|
||||||
|
private boolean canValidate;
|
||||||
|
private boolean requiresAdminAction;
|
||||||
|
}
|
||||||
@ -0,0 +1,167 @@
|
|||||||
|
package com.dh7789dev.xpeditis.dto.app;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Énumération des statuts d'un dossier d'export
|
||||||
|
* Définit le workflow complet de traitement des dossiers
|
||||||
|
*/
|
||||||
|
public enum DossierStatus {
|
||||||
|
// ========== PHASE CRÉATION ==========
|
||||||
|
CREE("Créé", "Dossier créé depuis devis accepté", false, false),
|
||||||
|
|
||||||
|
// ========== PHASE DOCUMENTS ==========
|
||||||
|
DOCUMENTS_EN_ATTENTE("Documents en attente", "En attente d'upload des documents obligatoires", true, false),
|
||||||
|
DOCUMENTS_UPLOADES("Documents uploadés", "Documents uploadés, en attente de vérification", true, false),
|
||||||
|
DOCUMENTS_EN_VERIFICATION("Documents en vérification", "Vérification des documents en cours par admin", false, false),
|
||||||
|
DOCUMENTS_REFUSES("Documents refusés", "Corrections demandées sur un ou plusieurs documents", true, false),
|
||||||
|
DOCUMENTS_VALIDES("Documents validés", "Tous documents validés, prêt pour booking", false, false),
|
||||||
|
|
||||||
|
// ========== PHASE TRANSPORT ==========
|
||||||
|
BOOKING_EN_COURS("Booking en cours", "Réservation transport en cours", false, false),
|
||||||
|
BOOKING_CONFIRME("Booking confirmé", "Transport confirmé", false, false),
|
||||||
|
ENLEVE("Enlevé", "Marchandise enlevée", false, false),
|
||||||
|
EN_TRANSIT("En transit", "En cours de transport", false, false),
|
||||||
|
ARRIVE("Arrivé", "Arrivé à destination", false, false),
|
||||||
|
|
||||||
|
// ========== PHASE FINALISATION ==========
|
||||||
|
LIVRE("Livré", "Livré au destinataire", false, true),
|
||||||
|
CLOTURE("Clôturé", "Dossier clôturé", false, true),
|
||||||
|
|
||||||
|
// ========== STATUT SPÉCIAL ==========
|
||||||
|
ANNULE("Annulé", "Dossier annulé", false, true);
|
||||||
|
|
||||||
|
private final String displayName;
|
||||||
|
private final String description;
|
||||||
|
private final boolean allowsDocumentUpload;
|
||||||
|
private final boolean isFinalized;
|
||||||
|
|
||||||
|
DossierStatus(String displayName, String description, boolean allowsDocumentUpload, boolean isFinalized) {
|
||||||
|
this.displayName = displayName;
|
||||||
|
this.description = description;
|
||||||
|
this.allowsDocumentUpload = allowsDocumentUpload;
|
||||||
|
this.isFinalized = isFinalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDisplayName() {
|
||||||
|
return displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean allowsDocumentUpload() {
|
||||||
|
return allowsDocumentUpload;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFinalized() {
|
||||||
|
return isFinalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retourne les statuts qui permettent l'upload de documents
|
||||||
|
*/
|
||||||
|
public static Set<DossierStatus> getUploadableStatuses() {
|
||||||
|
return Set.of(
|
||||||
|
DOCUMENTS_EN_ATTENTE,
|
||||||
|
DOCUMENTS_UPLOADES,
|
||||||
|
DOCUMENTS_REFUSES
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retourne les statuts considérés comme actifs (non terminés)
|
||||||
|
*/
|
||||||
|
public static Set<DossierStatus> getActiveStatuses() {
|
||||||
|
return Set.of(
|
||||||
|
CREE,
|
||||||
|
DOCUMENTS_EN_ATTENTE,
|
||||||
|
DOCUMENTS_UPLOADES,
|
||||||
|
DOCUMENTS_EN_VERIFICATION,
|
||||||
|
DOCUMENTS_REFUSES,
|
||||||
|
DOCUMENTS_VALIDES,
|
||||||
|
BOOKING_EN_COURS,
|
||||||
|
BOOKING_CONFIRME,
|
||||||
|
ENLEVE,
|
||||||
|
EN_TRANSIT,
|
||||||
|
ARRIVE
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vérifie si une transition de statut est valide
|
||||||
|
*/
|
||||||
|
public boolean canTransitionTo(DossierStatus newStatus) {
|
||||||
|
if (this.isFinalized) {
|
||||||
|
return false; // Aucune transition possible depuis un statut finalisé
|
||||||
|
}
|
||||||
|
|
||||||
|
// Définition des transitions valides
|
||||||
|
switch (this) {
|
||||||
|
case CREE:
|
||||||
|
return newStatus == DOCUMENTS_EN_ATTENTE || newStatus == ANNULE;
|
||||||
|
|
||||||
|
case DOCUMENTS_EN_ATTENTE:
|
||||||
|
return newStatus == DOCUMENTS_UPLOADES || newStatus == ANNULE;
|
||||||
|
|
||||||
|
case DOCUMENTS_UPLOADES:
|
||||||
|
return newStatus == DOCUMENTS_EN_VERIFICATION ||
|
||||||
|
newStatus == DOCUMENTS_EN_ATTENTE || newStatus == ANNULE;
|
||||||
|
|
||||||
|
case DOCUMENTS_EN_VERIFICATION:
|
||||||
|
return newStatus == DOCUMENTS_VALIDES ||
|
||||||
|
newStatus == DOCUMENTS_REFUSES || newStatus == ANNULE;
|
||||||
|
|
||||||
|
case DOCUMENTS_REFUSES:
|
||||||
|
return newStatus == DOCUMENTS_UPLOADES ||
|
||||||
|
newStatus == DOCUMENTS_EN_VERIFICATION || newStatus == ANNULE;
|
||||||
|
|
||||||
|
case DOCUMENTS_VALIDES:
|
||||||
|
return newStatus == BOOKING_EN_COURS || newStatus == ANNULE;
|
||||||
|
|
||||||
|
case BOOKING_EN_COURS:
|
||||||
|
return newStatus == BOOKING_CONFIRME ||
|
||||||
|
newStatus == DOCUMENTS_VALIDES || newStatus == ANNULE;
|
||||||
|
|
||||||
|
case BOOKING_CONFIRME:
|
||||||
|
return newStatus == ENLEVE || newStatus == ANNULE;
|
||||||
|
|
||||||
|
case ENLEVE:
|
||||||
|
return newStatus == EN_TRANSIT;
|
||||||
|
|
||||||
|
case EN_TRANSIT:
|
||||||
|
return newStatus == ARRIVE;
|
||||||
|
|
||||||
|
case ARRIVE:
|
||||||
|
return newStatus == LIVRE;
|
||||||
|
|
||||||
|
case LIVRE:
|
||||||
|
return newStatus == CLOTURE;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retourne le prochain statut logique dans le workflow standard
|
||||||
|
*/
|
||||||
|
public DossierStatus getNextLogicalStatus() {
|
||||||
|
switch (this) {
|
||||||
|
case CREE: return DOCUMENTS_EN_ATTENTE;
|
||||||
|
case DOCUMENTS_EN_ATTENTE: return DOCUMENTS_UPLOADES;
|
||||||
|
case DOCUMENTS_UPLOADES: return DOCUMENTS_EN_VERIFICATION;
|
||||||
|
case DOCUMENTS_EN_VERIFICATION: return DOCUMENTS_VALIDES;
|
||||||
|
case DOCUMENTS_REFUSES: return DOCUMENTS_UPLOADES;
|
||||||
|
case DOCUMENTS_VALIDES: return BOOKING_EN_COURS;
|
||||||
|
case BOOKING_EN_COURS: return BOOKING_CONFIRME;
|
||||||
|
case BOOKING_CONFIRME: return ENLEVE;
|
||||||
|
case ENLEVE: return EN_TRANSIT;
|
||||||
|
case EN_TRANSIT: return ARRIVE;
|
||||||
|
case ARRIVE: return LIVRE;
|
||||||
|
case LIVRE: return CLOTURE;
|
||||||
|
default: return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,79 @@
|
|||||||
|
package com.dh7789dev.xpeditis.dto.app;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ExportFolderDto {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
// ========== BASIC INFO ==========
|
||||||
|
private String reference;
|
||||||
|
private String numeroDossier;
|
||||||
|
private DossierStatus statut;
|
||||||
|
private String displayStatut;
|
||||||
|
|
||||||
|
// ========== COMPANY & QUOTE ==========
|
||||||
|
private Long companyId;
|
||||||
|
private String companyName;
|
||||||
|
private Long quoteId;
|
||||||
|
private String quoteReference;
|
||||||
|
|
||||||
|
// ========== WORKFLOW DATES ==========
|
||||||
|
private LocalDateTime dateCreation;
|
||||||
|
private LocalDateTime dateDocumentsComplets;
|
||||||
|
private LocalDateTime dateValidationDocuments;
|
||||||
|
private LocalDateTime dateBooking;
|
||||||
|
private LocalDateTime dateEnlevement;
|
||||||
|
private LocalDateTime dateLivraison;
|
||||||
|
private LocalDateTime dateCloture;
|
||||||
|
|
||||||
|
// ========== TRANSPORT REFERENCES ==========
|
||||||
|
private String referenceBooking;
|
||||||
|
private String numeroBl;
|
||||||
|
private String numeroConteneur;
|
||||||
|
|
||||||
|
// ========== METADATA ==========
|
||||||
|
private String commentairesClient;
|
||||||
|
private String notesInternes;
|
||||||
|
|
||||||
|
// ========== USER ASSIGNMENTS ==========
|
||||||
|
private Long createdById;
|
||||||
|
private String createdByName;
|
||||||
|
private Long assignedToAdminId;
|
||||||
|
private String assignedToAdminName;
|
||||||
|
|
||||||
|
// ========== WORKFLOW INFO ==========
|
||||||
|
private boolean allowsDocumentUpload;
|
||||||
|
private boolean isFinalized;
|
||||||
|
private DossierStatus nextLogicalStatus;
|
||||||
|
private boolean canTransitionToNext;
|
||||||
|
|
||||||
|
// ========== DOCUMENTS SUMMARY ==========
|
||||||
|
private int totalDocuments;
|
||||||
|
private int validatedDocuments;
|
||||||
|
private int pendingDocuments;
|
||||||
|
private int rejectedDocuments;
|
||||||
|
private boolean allMandatoryDocumentsProvided;
|
||||||
|
|
||||||
|
// ========== PERMISSIONS ==========
|
||||||
|
private Set<FolderAction> authorizedActions;
|
||||||
|
|
||||||
|
// ========== RELATED DATA ==========
|
||||||
|
private List<DocumentSummaryDto> documents;
|
||||||
|
private List<HistoryEntryDto> recentHistory;
|
||||||
|
|
||||||
|
// ========== TIMESTAMPS ==========
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
private LocalDateTime modifiedAt;
|
||||||
|
}
|
||||||
@ -0,0 +1,90 @@
|
|||||||
|
package com.dh7789dev.xpeditis.dto.app;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actions possibles sur les dossiers d'export
|
||||||
|
* Utilisé pour le système de permissions granulaires
|
||||||
|
*/
|
||||||
|
public enum FolderAction {
|
||||||
|
|
||||||
|
// ========== CONSULTATION ==========
|
||||||
|
VIEW_BASIC_INFO("Consulter informations de base", "Voir référence, statut, dates"),
|
||||||
|
VIEW_FULL_DETAILS("Consulter détails complets", "Voir toutes les informations du dossier"),
|
||||||
|
VIEW_DOCUMENTS("Consulter documents", "Voir la liste des documents uploadés"),
|
||||||
|
VIEW_HISTORY("Consulter historique", "Voir l'historique des actions"),
|
||||||
|
DOWNLOAD_DOCUMENTS("Télécharger documents", "Télécharger les fichiers"),
|
||||||
|
|
||||||
|
// ========== MODIFICATION ==========
|
||||||
|
UPDATE_BASIC_INFO("Modifier informations de base", "Modifier commentaires client, références"),
|
||||||
|
UPDATE_STATUS("Modifier statut", "Changer le statut du dossier"),
|
||||||
|
ADD_INTERNAL_NOTES("Ajouter notes internes", "Ajouter des commentaires internes"),
|
||||||
|
|
||||||
|
// ========== GESTION DOCUMENTS ==========
|
||||||
|
UPLOAD_DOCUMENTS("Uploader documents", "Ajouter de nouveaux documents"),
|
||||||
|
DELETE_DOCUMENTS("Supprimer documents", "Supprimer des documents"),
|
||||||
|
VALIDATE_DOCUMENTS("Valider documents", "Approuver ou refuser des documents"),
|
||||||
|
REQUEST_CORRECTIONS("Demander corrections", "Demander des corrections sur documents"),
|
||||||
|
|
||||||
|
// ========== WORKFLOW ==========
|
||||||
|
TRANSITION_STATUS("Changer statut workflow", "Faire progresser le dossier dans le workflow"),
|
||||||
|
FORCE_STATUS_CHANGE("Forcer changement statut", "Changer le statut sans validation workflow"),
|
||||||
|
ASSIGN_TO_ADMIN("Assigner à administrateur", "Assigner le dossier à un admin"),
|
||||||
|
MARK_DOCUMENTS_COMPLETE("Marquer documents complets", "Valider que tous documents sont fournis"),
|
||||||
|
|
||||||
|
// ========== ADMINISTRATION ==========
|
||||||
|
DELETE_FOLDER("Supprimer dossier", "Supprimer complètement le dossier"),
|
||||||
|
EXPORT_DATA("Exporter données", "Exporter les données du dossier"),
|
||||||
|
MANAGE_PERMISSIONS("Gérer permissions", "Modifier les permissions sur le dossier"),
|
||||||
|
ACCESS_SYSTEM_DATA("Accès données système", "Voir données techniques internes"),
|
||||||
|
|
||||||
|
// ========== TRANSPORT ==========
|
||||||
|
UPDATE_TRANSPORT_INFO("Modifier infos transport", "Mettre à jour booking, BL, conteneur"),
|
||||||
|
TRACK_SHIPMENT("Suivre expédition", "Voir le suivi de l'expédition"),
|
||||||
|
UPDATE_TRACKING("Mettre à jour suivi", "Ajouter des mises à jour de suivi"),
|
||||||
|
|
||||||
|
// ========== NOTIFICATIONS ==========
|
||||||
|
RECEIVE_NOTIFICATIONS("Recevoir notifications", "Être notifié des changements"),
|
||||||
|
SEND_NOTIFICATIONS("Envoyer notifications", "Notifier d'autres utilisateurs");
|
||||||
|
|
||||||
|
private final String displayName;
|
||||||
|
private final String description;
|
||||||
|
|
||||||
|
FolderAction(String displayName, String description) {
|
||||||
|
this.displayName = displayName;
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDisplayName() {
|
||||||
|
return displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actions de consultation seulement
|
||||||
|
*/
|
||||||
|
public boolean isReadOnlyAction() {
|
||||||
|
return this == VIEW_BASIC_INFO || this == VIEW_FULL_DETAILS ||
|
||||||
|
this == VIEW_DOCUMENTS || this == VIEW_HISTORY ||
|
||||||
|
this == DOWNLOAD_DOCUMENTS || this == TRACK_SHIPMENT ||
|
||||||
|
this == RECEIVE_NOTIFICATIONS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actions nécessitant des privilèges administratifs
|
||||||
|
*/
|
||||||
|
public boolean requiresAdminPrivileges() {
|
||||||
|
return this == VALIDATE_DOCUMENTS || this == FORCE_STATUS_CHANGE ||
|
||||||
|
this == DELETE_FOLDER || this == MANAGE_PERMISSIONS ||
|
||||||
|
this == ACCESS_SYSTEM_DATA || this == ASSIGN_TO_ADMIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actions liées au workflow
|
||||||
|
*/
|
||||||
|
public boolean isWorkflowAction() {
|
||||||
|
return this == UPDATE_STATUS || this == TRANSITION_STATUS ||
|
||||||
|
this == FORCE_STATUS_CHANGE || this == MARK_DOCUMENTS_COMPLETE;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
package com.dh7789dev.xpeditis.dto.app;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class HistoryEntryDto {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
// ========== ACTION INFO ==========
|
||||||
|
private String action;
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
// ========== STATUS CHANGES ==========
|
||||||
|
private String ancienStatut;
|
||||||
|
private String nouveauStatut;
|
||||||
|
|
||||||
|
// ========== ACTOR INFO ==========
|
||||||
|
private String effectueParType; // COMPANY_USER, ADMIN, SYSTEM
|
||||||
|
private Long effectueParId;
|
||||||
|
private String effectueParNom;
|
||||||
|
|
||||||
|
// ========== TIMING ==========
|
||||||
|
private LocalDateTime dateAction;
|
||||||
|
private String relativeTime; // "Il y a 2 heures", "Hier", etc.
|
||||||
|
|
||||||
|
// ========== ADDITIONAL DATA ==========
|
||||||
|
private Map<String, Object> donneesSupplementaires;
|
||||||
|
|
||||||
|
// ========== UI HELPERS ==========
|
||||||
|
private String actionIcon; // Icon CSS class for UI
|
||||||
|
private String actionColor; // Color for UI display
|
||||||
|
private String displayText; // Human-readable display text
|
||||||
|
}
|
||||||
@ -1,8 +1,73 @@
|
|||||||
package com.dh7789dev.xpeditis.dto.app;
|
package com.dh7789dev.xpeditis.dto.app;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Système de rôles étendu pour SSC Export System
|
||||||
|
* Intègre les rôles existants et les nouveaux besoins SSC
|
||||||
|
*/
|
||||||
public enum Role {
|
public enum Role {
|
||||||
USER,
|
// ========== RÔLES EXISTANTS (Conservés) ==========
|
||||||
MANAGER,
|
USER("Utilisateur Standard", 1),
|
||||||
ADMIN,
|
MANAGER("Gestionnaire", 2),
|
||||||
ADMIN_PLATFORM
|
ADMIN("Administrateur", 3),
|
||||||
|
ADMIN_PLATFORM("Administrateur Plateforme", 4),
|
||||||
|
|
||||||
|
// ========== NOUVEAUX RÔLES SSC ==========
|
||||||
|
SUPER_ADMIN("Super Administrateur", 10), // Accès total système
|
||||||
|
ADMIN_SSC("Administrateur SSC", 9), // Validation documents, gestion dossiers
|
||||||
|
COMPANY_ADMIN("Administrateur Entreprise", 6), // Gestion équipe entreprise cliente
|
||||||
|
COMPANY_USER("Utilisateur Entreprise", 5), // Consultation dossiers de son entreprise
|
||||||
|
COMPANY_GUEST("Invité Entreprise", 3); // Lecture seule sur dossiers spécifiques
|
||||||
|
|
||||||
|
private final String displayName;
|
||||||
|
private final int level;
|
||||||
|
|
||||||
|
Role(String displayName, int level) {
|
||||||
|
this.displayName = displayName;
|
||||||
|
this.level = level;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDisplayName() {
|
||||||
|
return displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLevel() {
|
||||||
|
return level;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vérifie si ce rôle a un niveau supérieur ou égal à un autre
|
||||||
|
*/
|
||||||
|
public boolean hasLevelGreaterOrEqual(Role other) {
|
||||||
|
return this.level >= other.level;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vérifie si ce rôle est un rôle SSC (gestion dossiers export)
|
||||||
|
*/
|
||||||
|
public boolean isSscRole() {
|
||||||
|
return this == SUPER_ADMIN || this == ADMIN_SSC ||
|
||||||
|
this == COMPANY_ADMIN || this == COMPANY_USER || this == COMPANY_GUEST;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vérifie si ce rôle peut gérer des utilisateurs d'entreprise
|
||||||
|
*/
|
||||||
|
public boolean canManageCompanyUsers() {
|
||||||
|
return this == SUPER_ADMIN || this == ADMIN_SSC || this == COMPANY_ADMIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vérifie si ce rôle peut valider des documents
|
||||||
|
*/
|
||||||
|
public boolean canValidateDocuments() {
|
||||||
|
return this == SUPER_ADMIN || this == ADMIN_SSC;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vérifie si ce rôle est un administrateur
|
||||||
|
*/
|
||||||
|
public boolean isAdmin() {
|
||||||
|
return this == SUPER_ADMIN || this == ADMIN_SSC ||
|
||||||
|
this == ADMIN || this == ADMIN_PLATFORM;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -0,0 +1,51 @@
|
|||||||
|
package com.dh7789dev.xpeditis.dto.app;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Statuts de vérification des documents uploadés
|
||||||
|
* Cycle de validation par les administrateurs SSC
|
||||||
|
*/
|
||||||
|
public enum StatutVerification {
|
||||||
|
|
||||||
|
EN_ATTENTE("En attente", "Document uploadé, en attente de vérification"),
|
||||||
|
EN_COURS_VERIFICATION("En cours de vérification", "Document en cours d'analyse par un administrateur"),
|
||||||
|
VALIDE("Validé", "Document vérifié et approuvé"),
|
||||||
|
REFUSE("Refusé", "Document refusé, corrections nécessaires"),
|
||||||
|
EXPIRE("Expiré", "Document expiré, renouvellement requis");
|
||||||
|
|
||||||
|
private final String displayName;
|
||||||
|
private final String description;
|
||||||
|
|
||||||
|
StatutVerification(String displayName, String description) {
|
||||||
|
this.displayName = displayName;
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDisplayName() {
|
||||||
|
return displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vérifie si ce statut permet une nouvelle vérification
|
||||||
|
*/
|
||||||
|
public boolean allowsReVerification() {
|
||||||
|
return this == REFUSE || this == EXPIRE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vérifie si ce statut est considéré comme final
|
||||||
|
*/
|
||||||
|
public boolean isFinal() {
|
||||||
|
return this == VALIDE || this == REFUSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vérifie si ce statut nécessite une action admin
|
||||||
|
*/
|
||||||
|
public boolean requiresAdminAction() {
|
||||||
|
return this == EN_ATTENTE || this == EN_COURS_VERIFICATION;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
package com.dh7789dev.xpeditis.dto.request;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import jakarta.validation.constraints.Positive;
|
||||||
|
import jakarta.validation.constraints.Size;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.experimental.FieldDefaults;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder(toBuilder = true)
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@FieldDefaults(level = AccessLevel.PRIVATE)
|
||||||
|
public class CreateFolderRequest {
|
||||||
|
|
||||||
|
@NotNull(message = "Quote ID is required")
|
||||||
|
@Positive(message = "Quote ID must be positive")
|
||||||
|
Long quoteId;
|
||||||
|
|
||||||
|
@Size(max = 1000, message = "Comments must not exceed 1000 characters")
|
||||||
|
String commentairesClient;
|
||||||
|
}
|
||||||
@ -0,0 +1,148 @@
|
|||||||
|
package com.dh7789dev.xpeditis.entity;
|
||||||
|
|
||||||
|
import com.dh7789dev.xpeditis.dto.app.StatutVerification;
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.experimental.FieldDefaults;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "documents_dossier")
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
@FieldDefaults(level = AccessLevel.PRIVATE)
|
||||||
|
public class DocumentDossierEntity {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
Long id;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "dossier_export_id", nullable = false)
|
||||||
|
ExportFolderEntity dossier;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "type_document_id", nullable = false)
|
||||||
|
DocumentTypeEntity typeDocument;
|
||||||
|
|
||||||
|
// ========== FILE INFORMATION ==========
|
||||||
|
@Column(name = "nom_original", nullable = false, length = 255)
|
||||||
|
String nomOriginal;
|
||||||
|
|
||||||
|
@Column(name = "nom_stockage", nullable = false, length = 100)
|
||||||
|
String nomStockage; // UUID + extension
|
||||||
|
|
||||||
|
@Column(name = "chemin_stockage", nullable = false, length = 500)
|
||||||
|
String cheminStockage; // Path on filesystem
|
||||||
|
|
||||||
|
@Column(name = "taille_octets", nullable = false)
|
||||||
|
Long tailleOctets;
|
||||||
|
|
||||||
|
@Column(name = "type_mime", nullable = false, length = 100)
|
||||||
|
String typeMime;
|
||||||
|
|
||||||
|
@Column(name = "hash_fichier", length = 64)
|
||||||
|
String hashFichier; // SHA-256
|
||||||
|
|
||||||
|
// ========== VERSIONING ==========
|
||||||
|
@Column(name = "numero_version", nullable = false)
|
||||||
|
Integer numeroVersion = 1;
|
||||||
|
|
||||||
|
@Column(name = "description", length = 500)
|
||||||
|
String description;
|
||||||
|
|
||||||
|
@Column(name = "date_validite")
|
||||||
|
LocalDate dateValidite;
|
||||||
|
|
||||||
|
// ========== VERIFICATION STATUS ==========
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@Column(name = "statut_verification", nullable = false, length = 30)
|
||||||
|
StatutVerification statutVerification = StatutVerification.EN_ATTENTE;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "verifie_par")
|
||||||
|
UserEntity verifiePar;
|
||||||
|
|
||||||
|
@Column(name = "date_verification")
|
||||||
|
LocalDateTime dateVerification;
|
||||||
|
|
||||||
|
@Lob
|
||||||
|
@Column(name = "commentaire_verification")
|
||||||
|
String commentaireVerification;
|
||||||
|
|
||||||
|
@Lob
|
||||||
|
@Column(name = "corrections_demandees")
|
||||||
|
String correctionsDemandees;
|
||||||
|
|
||||||
|
// ========== METADATA ==========
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "uploade_par", nullable = false)
|
||||||
|
UserEntity uploadePar;
|
||||||
|
|
||||||
|
@Column(name = "date_upload", nullable = false)
|
||||||
|
LocalDateTime dateUpload;
|
||||||
|
|
||||||
|
@Column(name = "updated_at")
|
||||||
|
LocalDateTime updatedAt;
|
||||||
|
|
||||||
|
@PrePersist
|
||||||
|
protected void onCreate() {
|
||||||
|
if (dateUpload == null) {
|
||||||
|
dateUpload = LocalDateTime.now();
|
||||||
|
}
|
||||||
|
updatedAt = LocalDateTime.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreUpdate
|
||||||
|
protected void onUpdate() {
|
||||||
|
updatedAt = LocalDateTime.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== BUSINESS METHODS ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vérifie si le document est valide (approuvé)
|
||||||
|
*/
|
||||||
|
public boolean isValid() {
|
||||||
|
return statutVerification == StatutVerification.VALIDE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vérifie si le document nécessite une action admin
|
||||||
|
*/
|
||||||
|
public boolean requiresAdminAction() {
|
||||||
|
return statutVerification.requiresAdminAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vérifie si le document peut être re-vérifié
|
||||||
|
*/
|
||||||
|
public boolean canBeReVerified() {
|
||||||
|
return statutVerification.allowsReVerification();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vérifie si le document a expiré
|
||||||
|
*/
|
||||||
|
public boolean isExpired() {
|
||||||
|
return dateValidite != null && dateValidite.isBefore(LocalDate.now());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Génère un nom de fichier de stockage unique
|
||||||
|
*/
|
||||||
|
public static String generateStorageName(String originalName) {
|
||||||
|
String extension = "";
|
||||||
|
int i = originalName.lastIndexOf('.');
|
||||||
|
if (i > 0) {
|
||||||
|
extension = originalName.substring(i);
|
||||||
|
}
|
||||||
|
return java.util.UUID.randomUUID().toString() + extension;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,51 @@
|
|||||||
|
package com.dh7789dev.xpeditis.entity;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.experimental.FieldDefaults;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "document_types")
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
@FieldDefaults(level = AccessLevel.PRIVATE)
|
||||||
|
public class DocumentTypeEntity extends BaseEntity {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
Long id;
|
||||||
|
|
||||||
|
@Column(name = "nom", nullable = false, length = 100)
|
||||||
|
String nom;
|
||||||
|
|
||||||
|
@Column(name = "description", length = 500)
|
||||||
|
String description;
|
||||||
|
|
||||||
|
@Column(name = "obligatoire", nullable = false)
|
||||||
|
Boolean obligatoire = false;
|
||||||
|
|
||||||
|
@Column(name = "pour_import", nullable = false)
|
||||||
|
Boolean pourImport = false;
|
||||||
|
|
||||||
|
@Column(name = "pour_export", nullable = false)
|
||||||
|
Boolean pourExport = false;
|
||||||
|
|
||||||
|
@Column(name = "pour_marchandise_dangereuse", nullable = false)
|
||||||
|
Boolean pourMarchandiseDangereuse = false;
|
||||||
|
|
||||||
|
@Column(name = "extensions_acceptees", length = 200)
|
||||||
|
String extensionsAcceptees;
|
||||||
|
|
||||||
|
@Column(name = "taille_max_mo")
|
||||||
|
Integer tailleMaxMo = 10;
|
||||||
|
|
||||||
|
@Column(name = "ordre_affichage")
|
||||||
|
Integer ordreAffichage = 0;
|
||||||
|
|
||||||
|
@Column(name = "actif", nullable = false)
|
||||||
|
Boolean actif = true;
|
||||||
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
package com.dh7789dev.xpeditis.entity;
|
package com.dh7789dev.xpeditis.entity;
|
||||||
|
|
||||||
|
import com.dh7789dev.xpeditis.dto.app.DossierStatus;
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
@ -22,36 +23,107 @@ import java.util.List;
|
|||||||
public class ExportFolderEntity extends BaseEntity {
|
public class ExportFolderEntity extends BaseEntity {
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
private Long id;
|
Long id;
|
||||||
|
|
||||||
|
// ========== EXISTING FIELDS (Preserved) ==========
|
||||||
@Column(unique = true)
|
@Column(unique = true)
|
||||||
private String reference;
|
String reference;
|
||||||
|
|
||||||
@Column(name = "validationDate")
|
@Column(name = "validationDate")
|
||||||
private LocalDateTime validationDate;
|
LocalDateTime validationDate;
|
||||||
|
|
||||||
@OneToMany(mappedBy = "exportFolder", cascade = CascadeType.ALL, orphanRemoval = true)
|
@OneToMany(mappedBy = "exportFolder", cascade = CascadeType.ALL, orphanRemoval = true)
|
||||||
private List<DocumentEntity> documents;
|
List<DocumentEntity> documents;
|
||||||
|
|
||||||
@OneToMany(mappedBy = "exportFolder", cascade = CascadeType.ALL, orphanRemoval = true)
|
@OneToMany(mappedBy = "exportFolder", cascade = CascadeType.ALL, orphanRemoval = true)
|
||||||
private List<ShipmentTrackingEntity> trackingUpdates = new ArrayList<>();
|
List<ShipmentTrackingEntity> trackingUpdates = new ArrayList<>();
|
||||||
|
|
||||||
@ManyToOne
|
@ManyToOne
|
||||||
private CompanyEntity company;
|
CompanyEntity company;
|
||||||
|
|
||||||
@OneToOne
|
@OneToOne
|
||||||
@JoinColumn(name = "quote_id", unique = true)
|
@JoinColumn(name = "quote_id", unique = true)
|
||||||
private QuoteEntity quote;
|
QuoteEntity quote;
|
||||||
|
|
||||||
@Column(name = "created_at", updatable = false)
|
@Column(name = "created_at", updatable = false)
|
||||||
private LocalDateTime createdAt;
|
LocalDateTime createdAt;
|
||||||
|
|
||||||
@Column(name = "modified_at")
|
@Column(name = "modified_at")
|
||||||
private LocalDateTime modifiedAt;
|
LocalDateTime modifiedAt;
|
||||||
|
|
||||||
|
// ========== NEW WORKFLOW FIELDS ==========
|
||||||
|
@Column(name = "numero_dossier", unique = true, length = 20)
|
||||||
|
String numeroDossier; // EXP-2024-001234
|
||||||
|
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@Column(name = "statut", nullable = false, length = 50)
|
||||||
|
DossierStatus statut = DossierStatus.CREE;
|
||||||
|
|
||||||
|
// ========== TRACKING DATES ==========
|
||||||
|
@Column(name = "date_creation")
|
||||||
|
LocalDateTime dateCreation;
|
||||||
|
|
||||||
|
@Column(name = "date_documents_complets")
|
||||||
|
LocalDateTime dateDocumentsComplets;
|
||||||
|
|
||||||
|
@Column(name = "date_validation_documents")
|
||||||
|
LocalDateTime dateValidationDocuments;
|
||||||
|
|
||||||
|
@Column(name = "date_booking")
|
||||||
|
LocalDateTime dateBooking;
|
||||||
|
|
||||||
|
@Column(name = "date_enlevement")
|
||||||
|
LocalDateTime dateEnlevement;
|
||||||
|
|
||||||
|
@Column(name = "date_livraison")
|
||||||
|
LocalDateTime dateLivraison;
|
||||||
|
|
||||||
|
@Column(name = "date_cloture")
|
||||||
|
LocalDateTime dateCloture;
|
||||||
|
|
||||||
|
// ========== TRANSPORT REFERENCES ==========
|
||||||
|
@Column(name = "reference_booking", length = 50)
|
||||||
|
String referenceBooking;
|
||||||
|
|
||||||
|
@Column(name = "numero_bl", length = 50)
|
||||||
|
String numeroBl;
|
||||||
|
|
||||||
|
@Column(name = "numero_conteneur", length = 50)
|
||||||
|
String numeroConteneur;
|
||||||
|
|
||||||
|
// ========== METADATA ==========
|
||||||
|
@Lob
|
||||||
|
@Column(name = "commentaires_client")
|
||||||
|
String commentairesClient;
|
||||||
|
|
||||||
|
@Lob
|
||||||
|
@Column(name = "notes_internes")
|
||||||
|
String notesInternes;
|
||||||
|
|
||||||
|
// ========== USER RELATIONSHIPS ==========
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "created_by_user_id")
|
||||||
|
UserEntity createdByUser;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "assigned_to_admin")
|
||||||
|
UserEntity assignedToAdmin;
|
||||||
|
|
||||||
|
// ========== NEW RELATIONSHIPS ==========
|
||||||
|
@OneToMany(mappedBy = "dossier", cascade = CascadeType.ALL)
|
||||||
|
List<DocumentDossierEntity> documentsValidation = new ArrayList<>();
|
||||||
|
|
||||||
|
@OneToMany(mappedBy = "dossierId", cascade = CascadeType.ALL)
|
||||||
|
List<HistoriqueDossierEntity> historique = new ArrayList<>();
|
||||||
|
|
||||||
@PrePersist
|
@PrePersist
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
createdAt = LocalDateTime.now();
|
if (createdAt == null) {
|
||||||
|
createdAt = LocalDateTime.now();
|
||||||
|
}
|
||||||
|
if (dateCreation == null) {
|
||||||
|
dateCreation = LocalDateTime.now();
|
||||||
|
}
|
||||||
modifiedAt = LocalDateTime.now();
|
modifiedAt = LocalDateTime.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,5 +131,35 @@ public class ExportFolderEntity extends BaseEntity {
|
|||||||
public void onUpdate() {
|
public void onUpdate() {
|
||||||
modifiedAt = LocalDateTime.now();
|
modifiedAt = LocalDateTime.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========== BUSINESS METHODS ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vérifie si le dossier est dans un état permettant l'upload de documents
|
||||||
|
*/
|
||||||
|
public boolean allowsDocumentUpload() {
|
||||||
|
return statut != null && statut.allowsDocumentUpload();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vérifie si le dossier est finalisé
|
||||||
|
*/
|
||||||
|
public boolean isFinalized() {
|
||||||
|
return statut != null && statut.isFinalized();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vérifie si une transition de statut est possible
|
||||||
|
*/
|
||||||
|
public boolean canTransitionTo(DossierStatus newStatus) {
|
||||||
|
return statut != null && statut.canTransitionTo(newStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retourne le prochain statut logique
|
||||||
|
*/
|
||||||
|
public DossierStatus getNextLogicalStatus() {
|
||||||
|
return statut != null ? statut.getNextLogicalStatus() : null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,65 @@
|
|||||||
|
package com.dh7789dev.xpeditis.entity;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.experimental.FieldDefaults;
|
||||||
|
import org.hibernate.annotations.JdbcTypeCode;
|
||||||
|
import org.hibernate.type.SqlTypes;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "dossier_history")
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
@FieldDefaults(level = AccessLevel.PRIVATE)
|
||||||
|
public class HistoriqueDossierEntity {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
Long id;
|
||||||
|
|
||||||
|
@Column(name = "dossier_id", nullable = false)
|
||||||
|
Long dossierId;
|
||||||
|
|
||||||
|
@Column(name = "action", nullable = false, length = 100)
|
||||||
|
String action;
|
||||||
|
|
||||||
|
@Column(name = "ancien_statut", length = 50)
|
||||||
|
String ancienStatut;
|
||||||
|
|
||||||
|
@Column(name = "nouveau_statut", length = 50)
|
||||||
|
String nouveauStatut;
|
||||||
|
|
||||||
|
@Lob
|
||||||
|
@Column(name = "description")
|
||||||
|
String description;
|
||||||
|
|
||||||
|
@Column(name = "effectue_par_type", nullable = false, length = 20)
|
||||||
|
String effectueParType; // COMPANY_USER, ADMIN, SYSTEM
|
||||||
|
|
||||||
|
@Column(name = "effectue_par_id")
|
||||||
|
Long effectueParId;
|
||||||
|
|
||||||
|
@Column(name = "effectue_par_nom", nullable = false, length = 100)
|
||||||
|
String effectueParNom;
|
||||||
|
|
||||||
|
@Column(name = "date_action", nullable = false)
|
||||||
|
LocalDateTime dateAction;
|
||||||
|
|
||||||
|
@JdbcTypeCode(SqlTypes.JSON)
|
||||||
|
@Column(name = "donnees_supplementaires", columnDefinition = "json")
|
||||||
|
Map<String, Object> donneesSupplementaires;
|
||||||
|
|
||||||
|
@PrePersist
|
||||||
|
protected void onCreate() {
|
||||||
|
if (dateAction == null) {
|
||||||
|
dateAction = LocalDateTime.now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,288 @@
|
|||||||
|
-- Migration V4: SSC Export System - Complete Implementation
|
||||||
|
-- Creates tables and modifies existing structure for comprehensive export folder management
|
||||||
|
|
||||||
|
-- ========== CREATE NEW TABLES ==========
|
||||||
|
|
||||||
|
-- Table des types de documents
|
||||||
|
CREATE TABLE document_types (
|
||||||
|
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||||
|
nom VARCHAR(100) NOT NULL,
|
||||||
|
description VARCHAR(500),
|
||||||
|
obligatoire BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
pour_import BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
pour_export BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
pour_marchandise_dangereuse BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
extensions_acceptees VARCHAR(200),
|
||||||
|
taille_max_mo INTEGER DEFAULT 10,
|
||||||
|
ordre_affichage INTEGER DEFAULT 0,
|
||||||
|
actif BOOLEAN NOT NULL DEFAULT TRUE,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Table de l'historique des dossiers
|
||||||
|
CREATE TABLE dossier_history (
|
||||||
|
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||||
|
dossier_id BIGINT NOT NULL,
|
||||||
|
action VARCHAR(100) NOT NULL,
|
||||||
|
ancien_statut VARCHAR(50),
|
||||||
|
nouveau_statut VARCHAR(50),
|
||||||
|
description TEXT,
|
||||||
|
effectue_par_type VARCHAR(20) NOT NULL, -- COMPANY_USER, ADMIN, SYSTEM
|
||||||
|
effectue_par_id BIGINT,
|
||||||
|
effectue_par_nom VARCHAR(100) NOT NULL,
|
||||||
|
date_action TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
donnees_supplementaires JSON,
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
INDEX idx_dossier_history_dossier_id (dossier_id),
|
||||||
|
INDEX idx_dossier_history_date_action (date_action),
|
||||||
|
INDEX idx_dossier_history_effectue_par (effectue_par_type, effectue_par_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Table des documents de dossier (nouvelle version avec validation)
|
||||||
|
CREATE TABLE documents_dossier (
|
||||||
|
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||||
|
dossier_export_id BIGINT NOT NULL,
|
||||||
|
type_document_id BIGINT NOT NULL,
|
||||||
|
|
||||||
|
-- Informations fichier
|
||||||
|
nom_original VARCHAR(255) NOT NULL,
|
||||||
|
nom_stockage VARCHAR(100) NOT NULL,
|
||||||
|
chemin_stockage VARCHAR(500) NOT NULL,
|
||||||
|
taille_octets BIGINT NOT NULL,
|
||||||
|
type_mime VARCHAR(100) NOT NULL,
|
||||||
|
hash_fichier VARCHAR(64), -- SHA-256
|
||||||
|
|
||||||
|
-- Versioning
|
||||||
|
numero_version INTEGER NOT NULL DEFAULT 1,
|
||||||
|
description VARCHAR(500),
|
||||||
|
date_validite DATE,
|
||||||
|
|
||||||
|
-- Statut de vérification
|
||||||
|
statut_verification VARCHAR(30) NOT NULL DEFAULT 'EN_ATTENTE',
|
||||||
|
verifie_par BIGINT,
|
||||||
|
date_verification TIMESTAMP,
|
||||||
|
commentaire_verification TEXT,
|
||||||
|
corrections_demandees TEXT,
|
||||||
|
|
||||||
|
-- Métadonnées
|
||||||
|
uploade_par BIGINT NOT NULL,
|
||||||
|
date_upload TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
FOREIGN KEY (dossier_export_id) REFERENCES exportfolder(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (type_document_id) REFERENCES document_types(id),
|
||||||
|
FOREIGN KEY (uploade_par) REFERENCES user(id),
|
||||||
|
FOREIGN KEY (verifie_par) REFERENCES user(id),
|
||||||
|
|
||||||
|
INDEX idx_document_dossier_export_id (dossier_export_id),
|
||||||
|
INDEX idx_document_dossier_type (type_document_id),
|
||||||
|
INDEX idx_document_dossier_verification (statut_verification),
|
||||||
|
INDEX idx_document_dossier_upload_date (date_upload),
|
||||||
|
INDEX idx_document_dossier_version (numero_version),
|
||||||
|
UNIQUE KEY uk_document_stockage (nom_stockage)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ========== MODIFY EXISTING EXPORTFOLDER TABLE ==========
|
||||||
|
|
||||||
|
-- Add workflow fields
|
||||||
|
ALTER TABLE exportfolder
|
||||||
|
ADD COLUMN numero_dossier VARCHAR(20) UNIQUE,
|
||||||
|
ADD COLUMN statut VARCHAR(50) NOT NULL DEFAULT 'CREE',
|
||||||
|
|
||||||
|
-- Add tracking dates
|
||||||
|
ADD COLUMN date_creation TIMESTAMP,
|
||||||
|
ADD COLUMN date_documents_complets TIMESTAMP,
|
||||||
|
ADD COLUMN date_validation_documents TIMESTAMP,
|
||||||
|
ADD COLUMN date_booking TIMESTAMP,
|
||||||
|
ADD COLUMN date_enlevement TIMESTAMP,
|
||||||
|
ADD COLUMN date_livraison TIMESTAMP,
|
||||||
|
ADD COLUMN date_cloture TIMESTAMP,
|
||||||
|
|
||||||
|
-- Add transport references
|
||||||
|
ADD COLUMN reference_booking VARCHAR(50),
|
||||||
|
ADD COLUMN numero_bl VARCHAR(50),
|
||||||
|
ADD COLUMN numero_conteneur VARCHAR(50),
|
||||||
|
|
||||||
|
-- Add metadata
|
||||||
|
ADD COLUMN commentaires_client TEXT,
|
||||||
|
ADD COLUMN notes_internes TEXT,
|
||||||
|
|
||||||
|
-- Add user relationships
|
||||||
|
ADD COLUMN created_by BIGINT,
|
||||||
|
ADD COLUMN assigned_to_admin BIGINT;
|
||||||
|
|
||||||
|
-- Add foreign key constraints
|
||||||
|
ALTER TABLE exportfolder
|
||||||
|
ADD CONSTRAINT fk_exportfolder_created_by
|
||||||
|
FOREIGN KEY (created_by) REFERENCES user(id),
|
||||||
|
ADD CONSTRAINT fk_exportfolder_assigned_to
|
||||||
|
FOREIGN KEY (assigned_to_admin) REFERENCES user(id);
|
||||||
|
|
||||||
|
-- ========== CREATE INDEXES FOR PERFORMANCE ==========
|
||||||
|
|
||||||
|
-- ExportFolder indexes
|
||||||
|
CREATE INDEX idx_exportfolder_statut ON exportfolder(statut);
|
||||||
|
CREATE INDEX idx_exportfolder_company_statut ON exportfolder(company_id, statut);
|
||||||
|
CREATE INDEX idx_exportfolder_created_by ON exportfolder(created_by);
|
||||||
|
CREATE INDEX idx_exportfolder_assigned_to ON exportfolder(assigned_to_admin);
|
||||||
|
CREATE INDEX idx_exportfolder_date_creation ON exportfolder(date_creation);
|
||||||
|
CREATE INDEX idx_exportfolder_numero_dossier ON exportfolder(numero_dossier);
|
||||||
|
CREATE INDEX idx_exportfolder_date_modified ON exportfolder(modified_at);
|
||||||
|
|
||||||
|
-- Composite indexes for common queries
|
||||||
|
CREATE INDEX idx_exportfolder_company_date ON exportfolder(company_id, date_creation DESC);
|
||||||
|
CREATE INDEX idx_exportfolder_statut_date ON exportfolder(statut, date_creation DESC);
|
||||||
|
|
||||||
|
-- ========== DATA INITIALIZATION ==========
|
||||||
|
|
||||||
|
-- Insert default document types
|
||||||
|
INSERT INTO document_types (nom, description, obligatoire, pour_export, extensions_acceptees, taille_max_mo, ordre_affichage, actif) VALUES
|
||||||
|
('Facture commerciale', 'Facture détaillant les biens exportés', TRUE, TRUE, '.pdf,.jpg,.png', 5, 1, TRUE),
|
||||||
|
('Liste de colisage', 'Détail du contenu de chaque colis', TRUE, TRUE, '.pdf,.xls,.xlsx', 5, 2, TRUE),
|
||||||
|
('Certificat d''origine', 'Document attestant l''origine des marchandises', FALSE, TRUE, '.pdf,.jpg,.png', 3, 3, TRUE),
|
||||||
|
('Déclaration en douane', 'Documents douaniers requis', TRUE, TRUE, '.pdf', 10, 4, TRUE),
|
||||||
|
('Assurance transport', 'Police d''assurance pour le transport', FALSE, TRUE, '.pdf', 2, 5, TRUE),
|
||||||
|
('Autorisation d''exportation', 'Autorisation spéciale si requise', FALSE, TRUE, '.pdf,.jpg,.png', 5, 6, TRUE),
|
||||||
|
('Certificat sanitaire', 'Pour marchandises alimentaires/agricoles', FALSE, TRUE, '.pdf', 3, 7, TRUE),
|
||||||
|
('Documents marchandises dangereuses', 'Classification et déclaration MD', FALSE, TRUE, '.pdf', 5, 8, TRUE);
|
||||||
|
|
||||||
|
-- Update document types for dangerous goods
|
||||||
|
UPDATE document_types
|
||||||
|
SET pour_marchandise_dangereuse = TRUE
|
||||||
|
WHERE nom IN ('Documents marchandises dangereuses', 'Déclaration en douane', 'Certificat d''origine');
|
||||||
|
|
||||||
|
-- ========== UPDATE EXISTING DATA ==========
|
||||||
|
|
||||||
|
-- Initialize date_creation for existing folders
|
||||||
|
UPDATE exportfolder
|
||||||
|
SET date_creation = created_at
|
||||||
|
WHERE date_creation IS NULL AND created_at IS NOT NULL;
|
||||||
|
|
||||||
|
-- Initialize numero_dossier for existing folders (if any exist)
|
||||||
|
-- Format: EXP-YYYY-NNNNNN
|
||||||
|
UPDATE exportfolder
|
||||||
|
SET numero_dossier = CONCAT('EXP-', YEAR(COALESCE(created_at, NOW())), '-', LPAD(id, 6, '0'))
|
||||||
|
WHERE numero_dossier IS NULL;
|
||||||
|
|
||||||
|
-- Initialize created_by from user relationship if quote exists
|
||||||
|
UPDATE exportfolder ef
|
||||||
|
INNER JOIN Quote q ON ef.quote_id = q.id
|
||||||
|
SET ef.created_by = q.user_id
|
||||||
|
WHERE ef.created_by IS NULL AND q.user_id IS NOT NULL;
|
||||||
|
|
||||||
|
-- ========== ADD CONSTRAINTS AND VALIDATIONS ==========
|
||||||
|
|
||||||
|
-- Add check constraints for statut values
|
||||||
|
ALTER TABLE exportfolder
|
||||||
|
ADD CONSTRAINT chk_exportfolder_statut
|
||||||
|
CHECK (statut IN (
|
||||||
|
'CREE', 'DOCUMENTS_EN_ATTENTE', 'DOCUMENTS_UPLOADES',
|
||||||
|
'DOCUMENTS_EN_VERIFICATION', 'DOCUMENTS_REFUSES', 'DOCUMENTS_VALIDES',
|
||||||
|
'BOOKING_EN_COURS', 'BOOKING_CONFIRME', 'ENLEVE', 'EN_TRANSIT',
|
||||||
|
'ARRIVE', 'LIVRE', 'CLOTURE', 'ANNULE'
|
||||||
|
));
|
||||||
|
|
||||||
|
-- Add check constraints for document verification status
|
||||||
|
ALTER TABLE documents_dossier
|
||||||
|
ADD CONSTRAINT chk_documents_dossier_statut_verification
|
||||||
|
CHECK (statut_verification IN (
|
||||||
|
'EN_ATTENTE', 'EN_COURS_VERIFICATION', 'VALIDE', 'REFUSE', 'EXPIRE'
|
||||||
|
));
|
||||||
|
|
||||||
|
-- Ensure version numbers are positive
|
||||||
|
ALTER TABLE documents_dossier
|
||||||
|
ADD CONSTRAINT chk_documents_dossier_version
|
||||||
|
CHECK (numero_version > 0);
|
||||||
|
|
||||||
|
-- Ensure file size is positive
|
||||||
|
ALTER TABLE documents_dossier
|
||||||
|
ADD CONSTRAINT chk_documents_dossier_taille
|
||||||
|
CHECK (taille_octets > 0);
|
||||||
|
|
||||||
|
-- ========== TRIGGERS FOR AUDIT AND AUTOMATION ==========
|
||||||
|
|
||||||
|
-- Trigger to automatically create history entry on folder status change
|
||||||
|
DELIMITER //
|
||||||
|
CREATE TRIGGER tr_exportfolder_status_history
|
||||||
|
AFTER UPDATE ON exportfolder
|
||||||
|
FOR EACH ROW
|
||||||
|
BEGIN
|
||||||
|
IF OLD.statut != NEW.statut THEN
|
||||||
|
INSERT INTO dossier_history (
|
||||||
|
dossier_id, action, ancien_statut, nouveau_statut,
|
||||||
|
description, effectue_par_type, effectue_par_nom, date_action
|
||||||
|
) VALUES (
|
||||||
|
NEW.id, 'STATUS_CHANGE', OLD.statut, NEW.statut,
|
||||||
|
CONCAT('Changement de statut: ', OLD.statut, ' → ', NEW.statut),
|
||||||
|
'SYSTEM', 'System Auto', NOW()
|
||||||
|
);
|
||||||
|
END IF;
|
||||||
|
END//
|
||||||
|
DELIMITER ;
|
||||||
|
|
||||||
|
-- Trigger to update document modification date
|
||||||
|
DELIMITER //
|
||||||
|
CREATE TRIGGER tr_documents_dossier_update_date
|
||||||
|
BEFORE UPDATE ON documents_dossier
|
||||||
|
FOR EACH ROW
|
||||||
|
BEGIN
|
||||||
|
SET NEW.updated_at = NOW();
|
||||||
|
END//
|
||||||
|
DELIMITER ;
|
||||||
|
|
||||||
|
-- ========== VIEWS FOR REPORTING ==========
|
||||||
|
|
||||||
|
-- Vue des dossiers avec statistiques de documents
|
||||||
|
CREATE VIEW v_exportfolder_stats AS
|
||||||
|
SELECT
|
||||||
|
ef.id,
|
||||||
|
ef.numero_dossier,
|
||||||
|
ef.statut,
|
||||||
|
ef.date_creation,
|
||||||
|
ef.company_id,
|
||||||
|
c.name as company_name,
|
||||||
|
COUNT(dd.id) as total_documents,
|
||||||
|
COUNT(CASE WHEN dd.statut_verification = 'VALIDE' THEN 1 END) as documents_valides,
|
||||||
|
COUNT(CASE WHEN dd.statut_verification = 'EN_ATTENTE' THEN 1 END) as documents_en_attente,
|
||||||
|
COUNT(CASE WHEN dd.statut_verification = 'REFUSE' THEN 1 END) as documents_refuses,
|
||||||
|
COUNT(dt.id) as types_obligatoires,
|
||||||
|
COUNT(CASE WHEN dt.obligatoire = TRUE AND dd.id IS NOT NULL THEN 1 END) as types_obligatoires_fournis
|
||||||
|
FROM exportfolder ef
|
||||||
|
LEFT JOIN company c ON ef.company_id = c.id
|
||||||
|
LEFT JOIN documents_dossier dd ON ef.id = dd.dossier_export_id
|
||||||
|
LEFT JOIN document_types dt ON dt.obligatoire = TRUE AND dt.actif = TRUE
|
||||||
|
GROUP BY ef.id, ef.numero_dossier, ef.statut, ef.date_creation, ef.company_id, c.name;
|
||||||
|
|
||||||
|
-- Vue de l'activité récente
|
||||||
|
CREATE VIEW v_recent_activity AS
|
||||||
|
SELECT
|
||||||
|
dh.id,
|
||||||
|
dh.dossier_id,
|
||||||
|
ef.numero_dossier,
|
||||||
|
dh.action,
|
||||||
|
dh.description,
|
||||||
|
dh.effectue_par_nom,
|
||||||
|
dh.date_action,
|
||||||
|
ef.company_id,
|
||||||
|
c.name as company_name
|
||||||
|
FROM dossier_history dh
|
||||||
|
INNER JOIN exportfolder ef ON dh.dossier_id = ef.id
|
||||||
|
LEFT JOIN company c ON ef.company_id = c.id
|
||||||
|
ORDER BY dh.date_action DESC;
|
||||||
|
|
||||||
|
-- ========== FINAL OPTIMIZATIONS ==========
|
||||||
|
|
||||||
|
-- Analyze tables for optimal query planning
|
||||||
|
ANALYZE TABLE exportfolder;
|
||||||
|
ANALYZE TABLE documents_dossier;
|
||||||
|
ANALYZE TABLE dossier_history;
|
||||||
|
ANALYZE TABLE document_types;
|
||||||
|
|
||||||
|
-- Create full-text search indexes if needed (MySQL 5.7+)
|
||||||
|
-- ALTER TABLE exportfolder ADD FULLTEXT(commentaires_client, notes_internes);
|
||||||
|
-- ALTER TABLE documents_dossier ADD FULLTEXT(description, commentaire_verification);
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
Loading…
Reference in New Issue
Block a user