Sync: code local unifié
Synchronisation du code source local (fait foi). Signed-off-by: lions dev Team
This commit is contained in:
870
BACKEND_FINANCE_WORKFLOW_IMPLEMENTATION.md
Normal file
870
BACKEND_FINANCE_WORKFLOW_IMPLEMENTATION.md
Normal file
@@ -0,0 +1,870 @@
|
||||
# Backend Finance Workflow - Implémentation Complète
|
||||
|
||||
**Date:** 2026-03-14
|
||||
**Module:** unionflow-server-impl-quarkus
|
||||
**Version:** 1.0.0
|
||||
**Status:** ✅ COMPLET - Compilation réussie
|
||||
|
||||
## Vue d'ensemble
|
||||
|
||||
Implémentation complète du système de workflow financier (approbations multi-niveaux et budgets) pour UnionFlow. Cette implémentation backend complète la feature mobile Finance Workflow et débloque la production.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Pattern d'architecture
|
||||
- **Multi-module Maven:** Séparation API (DTOs) / Implementation (Quarkus)
|
||||
- **Clean Architecture:** Entities → Repositories → Services → Resources
|
||||
- **DDD:** Logique métier dans les entités et services
|
||||
- **Panache Repository:** BaseRepository<T> pattern pour les repositories
|
||||
|
||||
### Stack technique
|
||||
- Quarkus 3.15.1
|
||||
- Java 17
|
||||
- Hibernate Panache
|
||||
- PostgreSQL 15
|
||||
- JAX-RS (REST)
|
||||
- Jakarta Bean Validation
|
||||
- Flyway (migrations)
|
||||
- Lombok
|
||||
- OpenAPI/Swagger
|
||||
|
||||
## Composants implémentés
|
||||
|
||||
### 1. Entités JPA (4 fichiers)
|
||||
|
||||
#### TransactionApproval.java
|
||||
**Localisation:** `src/main/java/dev/lions/unionflow/server/impl/quarkus/domain/entity/finance/`
|
||||
|
||||
**Responsabilité:** Entité principale du workflow d'approbation de transactions
|
||||
|
||||
**Champs clés:**
|
||||
```java
|
||||
@Entity
|
||||
@Table(name = "transaction_approvals")
|
||||
public class TransactionApproval extends BaseEntity {
|
||||
@NotNull private UUID transactionId;
|
||||
@NotBlank private String transactionType; // CONTRIBUTION, DEPOSIT, WITHDRAWAL, etc.
|
||||
@NotNull private BigDecimal amount;
|
||||
@NotBlank private String currency;
|
||||
@NotNull private UUID requesterId;
|
||||
@NotBlank private String requesterName;
|
||||
private UUID organizationId;
|
||||
@NotBlank private String requiredLevel; // NONE, LEVEL1, LEVEL2, LEVEL3
|
||||
@NotBlank private String status; // PENDING, APPROVED, VALIDATED, REJECTED, EXPIRED, CANCELLED
|
||||
@OneToMany(mappedBy = "approval", cascade = CascadeType.ALL)
|
||||
private List<ApproverAction> approvers = new ArrayList<>();
|
||||
private String rejectionReason;
|
||||
private LocalDateTime expiresAt;
|
||||
private LocalDateTime completedAt;
|
||||
private String metadata;
|
||||
}
|
||||
```
|
||||
|
||||
**Méthodes métier:**
|
||||
- `hasAllApprovals()`: Vérifie si toutes les approbations requises sont obtenues
|
||||
- `isExpired()`: Vérifie si l'approbation a expiré
|
||||
- `countApprovals()`: Compte le nombre d'approbations accordées
|
||||
- `getRequiredApprovals()`: Retourne le nombre d'approbations requises selon le niveau
|
||||
|
||||
**Indexes:**
|
||||
- `idx_approval_transaction` sur transaction_id
|
||||
- `idx_approval_status` sur status
|
||||
- `idx_approval_org_status` sur (organization_id, status)
|
||||
- `idx_approval_expires` sur expires_at
|
||||
|
||||
#### ApproverAction.java
|
||||
**Localisation:** `src/main/java/dev/lions/unionflow/server/impl/quarkus/domain/entity/finance/`
|
||||
|
||||
**Responsabilité:** Action individuelle d'un approbateur
|
||||
|
||||
**Champs clés:**
|
||||
```java
|
||||
@Entity
|
||||
@Table(name = "approver_actions")
|
||||
public class ApproverAction extends BaseEntity {
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "approval_id", nullable = false)
|
||||
private TransactionApproval approval;
|
||||
@NotNull private UUID approverId;
|
||||
@NotBlank private String approverName;
|
||||
@NotBlank private String approverRole;
|
||||
@NotBlank private String decision; // PENDING, APPROVED, REJECTED
|
||||
private String comment;
|
||||
private LocalDateTime decidedAt;
|
||||
}
|
||||
```
|
||||
|
||||
**Méthodes métier:**
|
||||
- `approve(String comment)`: Approuve avec commentaire optionnel
|
||||
- `reject(String reason)`: Rejette avec raison obligatoire
|
||||
|
||||
#### Budget.java
|
||||
**Localisation:** `src/main/java/dev/lions/unionflow/server/impl/quarkus/domain/entity/finance/`
|
||||
|
||||
**Responsabilité:** Budget périodique d'une organisation
|
||||
|
||||
**Champs clés:**
|
||||
```java
|
||||
@Entity
|
||||
@Table(name = "budgets")
|
||||
public class Budget extends BaseEntity {
|
||||
@NotBlank private String name;
|
||||
private String description;
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "organisation_id", nullable = false)
|
||||
private Organisation organisation;
|
||||
@NotBlank private String period; // MONTHLY, QUARTERLY, SEMIANNUAL, ANNUAL
|
||||
@NotNull private Integer year;
|
||||
private Integer month; // Pour les budgets MONTHLY
|
||||
@NotBlank private String status; // DRAFT, ACTIVE, CLOSED, CANCELLED
|
||||
@OneToMany(mappedBy = "budget", cascade = CascadeType.ALL)
|
||||
private List<BudgetLine> lines = new ArrayList<>();
|
||||
@NotNull private BigDecimal totalPlanned;
|
||||
@NotNull private BigDecimal totalRealized;
|
||||
@NotBlank private String currency;
|
||||
@NotNull private UUID createdById;
|
||||
private LocalDateTime approvedAt;
|
||||
private UUID approvedById;
|
||||
@NotNull private LocalDate startDate;
|
||||
@NotNull private LocalDate endDate;
|
||||
private String metadata;
|
||||
}
|
||||
```
|
||||
|
||||
**Méthodes métier:**
|
||||
- `recalculateTotals()`: Recalcule totalPlanned et totalRealized depuis les lignes
|
||||
- `getRealizationRate()`: Calcule le taux de réalisation
|
||||
- `getVariance()`: Calcule l'écart (realized - planned)
|
||||
- `isOverBudget()`: Vérifie si le budget est dépassé
|
||||
|
||||
#### BudgetLine.java
|
||||
**Localisation:** `src/main/java/dev/lions/unionflow/server/impl/quarkus/domain/entity/finance/`
|
||||
|
||||
**Responsabilité:** Ligne budgétaire individuelle par catégorie
|
||||
|
||||
**Champs clés:**
|
||||
```java
|
||||
@Entity
|
||||
@Table(name = "budget_lines")
|
||||
public class BudgetLine extends BaseEntity {
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "budget_id", nullable = false)
|
||||
private Budget budget;
|
||||
@NotBlank private String category; // CONTRIBUTIONS, SAVINGS, SOLIDARITY, etc.
|
||||
@NotBlank private String name;
|
||||
private String description;
|
||||
@NotNull private BigDecimal amountPlanned;
|
||||
@NotNull private BigDecimal amountRealized;
|
||||
private String notes;
|
||||
}
|
||||
```
|
||||
|
||||
**Catégories supportées:**
|
||||
- CONTRIBUTIONS
|
||||
- SAVINGS
|
||||
- SOLIDARITY
|
||||
- EVENTS
|
||||
- OPERATIONAL
|
||||
- INVESTMENTS
|
||||
- OTHER
|
||||
|
||||
### 2. Repositories (2 fichiers)
|
||||
|
||||
#### TransactionApprovalRepository.java
|
||||
**Localisation:** `src/main/java/dev/lions/unionflow/server/impl/quarkus/domain/repository/finance/`
|
||||
|
||||
**Méthodes:**
|
||||
```java
|
||||
@ApplicationScoped
|
||||
@Unremovable
|
||||
public class TransactionApprovalRepository extends BaseRepository<TransactionApproval> {
|
||||
// Recherche toutes les approbations en attente pour une organisation
|
||||
public List<TransactionApproval> findPendingByOrganisation(UUID organisationId);
|
||||
|
||||
// Trouve une approbation par ID de transaction
|
||||
public Optional<TransactionApproval> findByTransactionId(UUID transactionId);
|
||||
|
||||
// Trouve toutes les approbations expirées
|
||||
public List<TransactionApproval> findExpired();
|
||||
|
||||
// Compte les approbations en attente pour une organisation
|
||||
public long countPendingByOrganisation(UUID organisationId);
|
||||
|
||||
// Historique avec filtres
|
||||
public List<TransactionApproval> findHistory(
|
||||
UUID organizationId,
|
||||
LocalDateTime startDate,
|
||||
LocalDateTime endDate,
|
||||
String status
|
||||
);
|
||||
|
||||
// Toutes les approbations en attente pour un utilisateur
|
||||
public List<TransactionApproval> findPendingForApprover(UUID approverId);
|
||||
|
||||
// Approbations par demandeur
|
||||
public List<TransactionApproval> findByRequester(UUID requesterId);
|
||||
}
|
||||
```
|
||||
|
||||
#### BudgetRepository.java
|
||||
**Localisation:** `src/main/java/dev/lions/unionflow/server/impl/quarkus/domain/repository/finance/`
|
||||
|
||||
**Méthodes:**
|
||||
```java
|
||||
@ApplicationScoped
|
||||
@Unremovable
|
||||
public class BudgetRepository extends BaseRepository<Budget> {
|
||||
// Tous les budgets d'une organisation
|
||||
public List<Budget> findByOrganisation(UUID organisationId);
|
||||
|
||||
// Budgets avec filtres optionnels
|
||||
public List<Budget> findByOrganisationAndFilters(
|
||||
UUID organisationId,
|
||||
String status,
|
||||
Integer year
|
||||
);
|
||||
|
||||
// Budget actif courant
|
||||
public Optional<Budget> findActiveBudgetForCurrentPeriod(UUID organisationId);
|
||||
|
||||
// Budgets par année
|
||||
public List<Budget> findByYear(UUID organisationId, Integer year);
|
||||
|
||||
// Budgets par période
|
||||
public List<Budget> findByPeriod(UUID organisationId, String period);
|
||||
|
||||
// Compte les budgets actifs
|
||||
public long countActiveBudgets(UUID organisationId);
|
||||
}
|
||||
```
|
||||
|
||||
### 3. DTOs (10 fichiers dans server-api)
|
||||
|
||||
#### DTOs Response (6)
|
||||
|
||||
**TransactionApprovalResponse.java**
|
||||
- Données complètes d'une approbation
|
||||
- Champs calculés: approvalCount, requiredApprovals, hasAllApprovals, isExpired, isPending, isCompleted
|
||||
|
||||
**ApproverActionResponse.java**
|
||||
- Détails d'une action d'approbateur
|
||||
- Champs: approverId, approverName, approverRole, decision, comment, decidedAt
|
||||
|
||||
**BudgetResponse.java**
|
||||
- Données complètes d'un budget
|
||||
- Champs calculés: realizationRate, variance, varianceRate, isOverBudget, isActive, isCurrentPeriod
|
||||
|
||||
**BudgetLineResponse.java**
|
||||
- Détails d'une ligne budgétaire
|
||||
- Champs calculés: realizationRate, variance, isOverBudget
|
||||
|
||||
#### DTOs Request (4)
|
||||
|
||||
**ApproveTransactionRequest.java**
|
||||
```java
|
||||
@Data
|
||||
public class ApproveTransactionRequest {
|
||||
@Size(max = 1000, message = "Le commentaire ne peut dépasser 1000 caractères")
|
||||
private String comment;
|
||||
}
|
||||
```
|
||||
|
||||
**RejectTransactionRequest.java**
|
||||
```java
|
||||
@Data
|
||||
public class RejectTransactionRequest {
|
||||
@NotBlank(message = "La raison du rejet est requise")
|
||||
@Size(min = 10, max = 1000)
|
||||
private String reason;
|
||||
}
|
||||
```
|
||||
|
||||
**CreateBudgetRequest.java**
|
||||
```java
|
||||
@Data
|
||||
public class CreateBudgetRequest {
|
||||
@NotBlank private String name;
|
||||
private String description;
|
||||
@NotNull private UUID organizationId;
|
||||
@NotBlank private String period;
|
||||
@NotNull private Integer year;
|
||||
private Integer month;
|
||||
@NotBlank private String currency;
|
||||
@Valid @NotEmpty private List<CreateBudgetLineRequest> lines;
|
||||
private String metadata;
|
||||
}
|
||||
```
|
||||
|
||||
**CreateBudgetLineRequest.java**
|
||||
```java
|
||||
@Data
|
||||
public class CreateBudgetLineRequest {
|
||||
@NotBlank private String category;
|
||||
@NotBlank private String name;
|
||||
private String description;
|
||||
@NotNull @DecimalMin("0.0") private BigDecimal amountPlanned;
|
||||
private String notes;
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Services (2 fichiers)
|
||||
|
||||
#### ApprovalService.java
|
||||
**Localisation:** `src/main/java/dev/lions/unionflow/server/impl/quarkus/service/finance/`
|
||||
|
||||
**Méthodes principales:**
|
||||
```java
|
||||
@ApplicationScoped
|
||||
public class ApprovalService {
|
||||
// Liste des approbations en attente
|
||||
public List<TransactionApprovalResponse> getPendingApprovals(UUID organizationId);
|
||||
|
||||
// Détails d'une approbation
|
||||
public TransactionApprovalResponse getApprovalById(UUID approvalId);
|
||||
|
||||
// Approuver une transaction
|
||||
@Transactional
|
||||
public TransactionApprovalResponse approveTransaction(
|
||||
UUID approvalId,
|
||||
ApproveTransactionRequest request,
|
||||
UUID approverId,
|
||||
String approverName,
|
||||
String approverRole
|
||||
);
|
||||
|
||||
// Rejeter une transaction
|
||||
@Transactional
|
||||
public TransactionApprovalResponse rejectTransaction(
|
||||
UUID approvalId,
|
||||
RejectTransactionRequest request
|
||||
);
|
||||
|
||||
// Historique avec filtres
|
||||
public List<TransactionApprovalResponse> getApprovalsHistory(
|
||||
UUID organizationId,
|
||||
LocalDateTime startDate,
|
||||
LocalDateTime endDate,
|
||||
String status
|
||||
);
|
||||
|
||||
// Comptage
|
||||
public long countPendingApprovals(UUID organizationId);
|
||||
}
|
||||
```
|
||||
|
||||
**Logique métier implémentée:**
|
||||
- Validation: transaction non expirée, approbateur différent du demandeur
|
||||
- Transition automatique: PENDING → APPROVED → VALIDATED (quand toutes les approbations sont obtenues)
|
||||
- Gestion des expirations
|
||||
- Enregistrement de l'historique des actions
|
||||
|
||||
#### BudgetService.java
|
||||
**Localisation:** `src/main/java/dev/lions/unionflow/server/impl/quarkus/service/finance/`
|
||||
|
||||
**Méthodes principales:**
|
||||
```java
|
||||
@ApplicationScoped
|
||||
public class BudgetService {
|
||||
// Liste des budgets avec filtres optionnels
|
||||
public List<BudgetResponse> getBudgets(
|
||||
UUID organizationId,
|
||||
String status,
|
||||
Integer year
|
||||
);
|
||||
|
||||
// Détails d'un budget
|
||||
public BudgetResponse getBudgetById(UUID budgetId);
|
||||
|
||||
// Créer un budget
|
||||
@Transactional
|
||||
public BudgetResponse createBudget(
|
||||
CreateBudgetRequest request,
|
||||
UUID createdById
|
||||
);
|
||||
|
||||
// Suivi budgétaire (tracking)
|
||||
public Map<String, Object> getBudgetTracking(UUID budgetId);
|
||||
}
|
||||
```
|
||||
|
||||
**Logique métier implémentée:**
|
||||
- Calcul automatique des dates selon la période (MONTHLY, QUARTERLY, etc.)
|
||||
- Calcul des totaux à partir des lignes
|
||||
- Métriques: taux de réalisation, variance, dépassement
|
||||
- Suivi par catégorie avec top 5 des écarts
|
||||
|
||||
### 5. REST Resources (2 fichiers)
|
||||
|
||||
#### ApprovalResource.java
|
||||
**Localisation:** `src/main/java/dev/lions/unionflow/server/impl/quarkus/resource/finance/`
|
||||
|
||||
**Endpoints (6):**
|
||||
|
||||
```java
|
||||
@Path("/api/finance/approvals")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public class ApprovalResource {
|
||||
|
||||
// GET /api/finance/approvals/pending?organizationId={uuid}
|
||||
@GET
|
||||
@Path("/pending")
|
||||
@RolesAllowed({"ORG_ADMIN", "SUPER_ADMIN"})
|
||||
public Response getPendingApprovals(@QueryParam("organizationId") UUID organizationId);
|
||||
|
||||
// GET /api/finance/approvals/{approvalId}
|
||||
@GET
|
||||
@Path("/{approvalId}")
|
||||
public Response getApprovalById(@PathParam("approvalId") UUID approvalId);
|
||||
|
||||
// POST /api/finance/approvals/{approvalId}/approve
|
||||
@POST
|
||||
@Path("/{approvalId}/approve")
|
||||
public Response approveTransaction(
|
||||
@PathParam("approvalId") UUID approvalId,
|
||||
@Valid ApproveTransactionRequest request
|
||||
);
|
||||
|
||||
// POST /api/finance/approvals/{approvalId}/reject
|
||||
@POST
|
||||
@Path("/{approvalId}/reject")
|
||||
public Response rejectTransaction(
|
||||
@PathParam("approvalId") UUID approvalId,
|
||||
@Valid RejectTransactionRequest request
|
||||
);
|
||||
|
||||
// GET /api/finance/approvals/history?organizationId={uuid}&startDate=...&endDate=...&status=...
|
||||
@GET
|
||||
@Path("/history")
|
||||
@RolesAllowed({"ORG_ADMIN", "SUPER_ADMIN"})
|
||||
public Response getApprovalsHistory(
|
||||
@QueryParam("organizationId") UUID organizationId,
|
||||
@QueryParam("startDate") String startDate,
|
||||
@QueryParam("endDate") String endDate,
|
||||
@QueryParam("status") String status
|
||||
);
|
||||
|
||||
// GET /api/finance/approvals/count/pending?organizationId={uuid}
|
||||
@GET
|
||||
@Path("/count/pending")
|
||||
@RolesAllowed({"ORG_ADMIN", "SUPER_ADMIN"})
|
||||
public Response countPendingApprovals(@QueryParam("organizationId") UUID organizationId);
|
||||
}
|
||||
```
|
||||
|
||||
**Sécurité:**
|
||||
- Extraction JWT via `@Inject JsonWebToken jwt`
|
||||
- Validation des rôles avec `@RolesAllowed`
|
||||
- Vérification que l'approbateur != demandeur
|
||||
|
||||
**Gestion d'erreurs:**
|
||||
- 400 Bad Request pour données invalides
|
||||
- 404 Not Found pour ressources inexistantes
|
||||
- 403 Forbidden pour tentatives d'auto-approbation
|
||||
- 410 Gone pour approbations expirées
|
||||
- 500 Internal Server Error avec logging
|
||||
|
||||
#### BudgetResource.java
|
||||
**Localisation:** `src/main/java/dev/lions/unionflow/server/impl/quarkus/resource/finance/`
|
||||
|
||||
**Endpoints (4):**
|
||||
|
||||
```java
|
||||
@Path("/api/finance/budgets")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public class BudgetResource {
|
||||
|
||||
// GET /api/finance/budgets?organizationId={uuid}&status=...&year=...
|
||||
@GET
|
||||
@RolesAllowed({"ORG_ADMIN", "SUPER_ADMIN"})
|
||||
public Response getBudgets(
|
||||
@QueryParam("organizationId") UUID organizationId,
|
||||
@QueryParam("status") String status,
|
||||
@QueryParam("year") Integer year
|
||||
);
|
||||
|
||||
// GET /api/finance/budgets/{budgetId}
|
||||
@GET
|
||||
@Path("/{budgetId}")
|
||||
public Response getBudgetById(@PathParam("budgetId") UUID budgetId);
|
||||
|
||||
// POST /api/finance/budgets
|
||||
@POST
|
||||
@RolesAllowed({"ORG_ADMIN", "SUPER_ADMIN"})
|
||||
public Response createBudget(@Valid CreateBudgetRequest request);
|
||||
|
||||
// GET /api/finance/budgets/{budgetId}/tracking
|
||||
@GET
|
||||
@Path("/{budgetId}/tracking")
|
||||
public Response getBudgetTracking(@PathParam("budgetId") UUID budgetId);
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Migration Flyway (1 fichier)
|
||||
|
||||
#### V6__Create_Finance_Workflow_Tables.sql
|
||||
**Localisation:** `src/main/resources/db/migration/`
|
||||
|
||||
**Contenu:**
|
||||
```sql
|
||||
-- Table des approbations de transactions
|
||||
CREATE TABLE transaction_approvals (
|
||||
-- Clé primaire
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
|
||||
-- Informations de transaction
|
||||
transaction_id UUID NOT NULL,
|
||||
transaction_type VARCHAR(20) NOT NULL
|
||||
CHECK (transaction_type IN ('CONTRIBUTION', 'DEPOSIT', 'WITHDRAWAL', 'TRANSFER', 'SOLIDARITY', 'EVENT', 'OTHER')),
|
||||
amount NUMERIC(14, 2) NOT NULL CHECK (amount >= 0),
|
||||
currency VARCHAR(3) NOT NULL DEFAULT 'XOF',
|
||||
|
||||
-- Demandeur
|
||||
requester_id UUID NOT NULL,
|
||||
requester_name VARCHAR(200) NOT NULL,
|
||||
|
||||
-- Organisation (optionnel pour transactions personnelles)
|
||||
organisation_id UUID REFERENCES organisations(id) ON DELETE CASCADE,
|
||||
|
||||
-- Niveau d'approbation requis
|
||||
required_level VARCHAR(10) NOT NULL DEFAULT 'NONE'
|
||||
CHECK (required_level IN ('NONE', 'LEVEL1', 'LEVEL2', 'LEVEL3')),
|
||||
|
||||
-- Statut
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'PENDING'
|
||||
CHECK (status IN ('PENDING', 'APPROVED', 'VALIDATED', 'REJECTED', 'EXPIRED', 'CANCELLED')),
|
||||
|
||||
-- Détails
|
||||
rejection_reason TEXT,
|
||||
expires_at TIMESTAMP,
|
||||
completed_at TIMESTAMP,
|
||||
metadata TEXT, -- JSON
|
||||
|
||||
-- Champs BaseEntity
|
||||
date_creation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
date_modification TIMESTAMP,
|
||||
utilisateur_creation VARCHAR(100),
|
||||
utilisateur_modification VARCHAR(100),
|
||||
version INTEGER NOT NULL DEFAULT 0,
|
||||
actif BOOLEAN NOT NULL DEFAULT TRUE
|
||||
);
|
||||
|
||||
-- Indexes
|
||||
CREATE INDEX idx_approval_transaction ON transaction_approvals(transaction_id);
|
||||
CREATE INDEX idx_approval_status ON transaction_approvals(status);
|
||||
CREATE INDEX idx_approval_org_status ON transaction_approvals(organisation_id, status)
|
||||
WHERE organisation_id IS NOT NULL;
|
||||
CREATE INDEX idx_approval_expires ON transaction_approvals(expires_at)
|
||||
WHERE expires_at IS NOT NULL AND status = 'PENDING';
|
||||
|
||||
-- Table des actions d'approbateurs
|
||||
CREATE TABLE approver_actions (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
approval_id UUID NOT NULL REFERENCES transaction_approvals(id) ON DELETE CASCADE,
|
||||
approver_id UUID NOT NULL,
|
||||
approver_name VARCHAR(200) NOT NULL,
|
||||
approver_role VARCHAR(50) NOT NULL,
|
||||
decision VARCHAR(20) NOT NULL DEFAULT 'PENDING'
|
||||
CHECK (decision IN ('PENDING', 'APPROVED', 'REJECTED')),
|
||||
comment TEXT,
|
||||
decided_at TIMESTAMP,
|
||||
-- Champs BaseEntity...
|
||||
);
|
||||
|
||||
CREATE INDEX idx_approver_approval ON approver_actions(approval_id);
|
||||
|
||||
-- Table des budgets
|
||||
CREATE TABLE budgets (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name VARCHAR(200) NOT NULL,
|
||||
description TEXT,
|
||||
organisation_id UUID NOT NULL REFERENCES organisations(id) ON DELETE CASCADE,
|
||||
period VARCHAR(20) NOT NULL
|
||||
CHECK (period IN ('MONTHLY', 'QUARTERLY', 'SEMIANNUAL', 'ANNUAL')),
|
||||
year INTEGER NOT NULL CHECK (year >= 2020 AND year <= 2100),
|
||||
month INTEGER CHECK (month >= 1 AND month <= 12),
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'DRAFT'
|
||||
CHECK (status IN ('DRAFT', 'ACTIVE', 'CLOSED', 'CANCELLED')),
|
||||
total_planned NUMERIC(14, 2) NOT NULL DEFAULT 0,
|
||||
total_realized NUMERIC(14, 2) NOT NULL DEFAULT 0,
|
||||
currency VARCHAR(3) NOT NULL DEFAULT 'XOF',
|
||||
created_by_id UUID NOT NULL,
|
||||
approved_at TIMESTAMP,
|
||||
approved_by_id UUID,
|
||||
start_date DATE NOT NULL,
|
||||
end_date DATE NOT NULL,
|
||||
metadata TEXT, -- JSON
|
||||
-- Champs BaseEntity...
|
||||
CONSTRAINT check_end_after_start CHECK (end_date > start_date),
|
||||
CONSTRAINT check_month_for_monthly CHECK (period != 'MONTHLY' OR month IS NOT NULL)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_budget_org ON budgets(organisation_id);
|
||||
CREATE INDEX idx_budget_period ON budgets(organisation_id, year, period);
|
||||
CREATE INDEX idx_budget_status ON budgets(status);
|
||||
|
||||
-- Table des lignes budgétaires
|
||||
CREATE TABLE budget_lines (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
budget_id UUID NOT NULL REFERENCES budgets(id) ON DELETE CASCADE,
|
||||
category VARCHAR(50) NOT NULL
|
||||
CHECK (category IN ('CONTRIBUTIONS', 'SAVINGS', 'SOLIDARITY', 'EVENTS', 'OPERATIONAL', 'INVESTMENTS', 'OTHER')),
|
||||
name VARCHAR(200) NOT NULL,
|
||||
description TEXT,
|
||||
amount_planned NUMERIC(14, 2) NOT NULL CHECK (amount_planned >= 0),
|
||||
amount_realized NUMERIC(14, 2) NOT NULL DEFAULT 0 CHECK (amount_realized >= 0),
|
||||
notes TEXT,
|
||||
-- Champs BaseEntity...
|
||||
);
|
||||
|
||||
CREATE INDEX idx_budgetline_budget ON budget_lines(budget_id);
|
||||
CREATE INDEX idx_budgetline_category ON budget_lines(budget_id, category);
|
||||
|
||||
-- Commentaires
|
||||
COMMENT ON TABLE transaction_approvals IS 'Approbations de transactions avec workflow multi-niveaux';
|
||||
COMMENT ON TABLE approver_actions IS 'Actions individuelles des approbateurs';
|
||||
COMMENT ON TABLE budgets IS 'Budgets organisationnels par période';
|
||||
COMMENT ON TABLE budget_lines IS 'Lignes budgétaires par catégorie';
|
||||
```
|
||||
|
||||
## Compilation et Installation
|
||||
|
||||
### Compilation réussie
|
||||
|
||||
```bash
|
||||
# Module server-api
|
||||
cd unionflow/unionflow-server-api
|
||||
mvn clean install -DskipTests
|
||||
# BUILD SUCCESS - 249 source files compiled
|
||||
|
||||
# Module server-impl-quarkus
|
||||
cd unionflow/unionflow-server-impl-quarkus
|
||||
mvn compile -DskipTests
|
||||
# BUILD SUCCESS - 254 source files compiled
|
||||
```
|
||||
|
||||
### Installation locale
|
||||
Les artifacts sont installés dans le repository Maven local:
|
||||
- `~/.m2/repository/dev/lions/unionflow/unionflow-server-api/1.0.0/`
|
||||
|
||||
## Tests
|
||||
|
||||
### Tests unitaires à créer
|
||||
- [ ] ApprovalServiceTest
|
||||
- [ ] BudgetServiceTest
|
||||
- [ ] TransactionApprovalTest (entité)
|
||||
- [ ] BudgetTest (entité)
|
||||
|
||||
### Tests d'intégration à créer
|
||||
- [ ] ApprovalResourceTest
|
||||
- [ ] BudgetResourceTest
|
||||
- [ ] Workflow complet: création → approbation → validation
|
||||
- [ ] Gestion des expirations
|
||||
- [ ] Calculs budgétaires
|
||||
|
||||
### Tests manuels via Swagger UI
|
||||
Endpoints accessibles sur: `http://localhost:8085/q/swagger-ui`
|
||||
|
||||
## Workflow d'approbation
|
||||
|
||||
### Niveaux d'approbation
|
||||
- **NONE:** Pas d'approbation requise (0)
|
||||
- **LEVEL1:** 1 approbation requise
|
||||
- **LEVEL2:** 2 approbations requises
|
||||
- **LEVEL3:** 3 approbations requises
|
||||
|
||||
### États possibles
|
||||
```
|
||||
PENDING → APPROVED → VALIDATED
|
||||
↓ ↓
|
||||
REJECTED REJECTED
|
||||
↓
|
||||
EXPIRED
|
||||
```
|
||||
|
||||
### Flux nominal
|
||||
1. Transaction créée → TransactionApproval créé avec status=PENDING
|
||||
2. Approbateur 1 approuve → ApproverAction créée avec decision=APPROVED
|
||||
3. Si hasAllApprovals() → status passe à VALIDATED
|
||||
4. Transaction peut être exécutée
|
||||
|
||||
### Flux de rejet
|
||||
1. Un approbateur rejette → status=REJECTED
|
||||
2. rejectionReason enregistrée
|
||||
3. Transaction ne peut pas être exécutée
|
||||
|
||||
### Gestion des expirations
|
||||
- Job scheduled peut marquer les approbations expirées (expiresAt < now et status=PENDING)
|
||||
- Status passe à EXPIRED
|
||||
- Transaction doit être re-soumise
|
||||
|
||||
## Gestion des budgets
|
||||
|
||||
### Périodes supportées
|
||||
- **MONTHLY:** Budget mensuel (year + month requis)
|
||||
- **QUARTERLY:** Budget trimestriel (year requis)
|
||||
- **SEMIANNUAL:** Budget semestriel (year requis)
|
||||
- **ANNUAL:** Budget annuel (year requis)
|
||||
|
||||
### Calculs automatiques
|
||||
```java
|
||||
// Dates
|
||||
startDate = calculé selon période
|
||||
endDate = calculé selon période
|
||||
|
||||
// Totaux
|
||||
totalPlanned = sum(lines.amountPlanned)
|
||||
totalRealized = sum(lines.amountRealized)
|
||||
|
||||
// Métriques
|
||||
realizationRate = (totalRealized / totalPlanned) * 100
|
||||
variance = totalRealized - totalPlanned
|
||||
varianceRate = (variance / totalPlanned) * 100
|
||||
isOverBudget = totalRealized > totalPlanned
|
||||
```
|
||||
|
||||
### Suivi (Tracking)
|
||||
Le endpoint `/budgets/{id}/tracking` retourne:
|
||||
```json
|
||||
{
|
||||
"budgetId": "uuid",
|
||||
"budgetName": "Budget Q1 2026",
|
||||
"trackingByCategory": [
|
||||
{
|
||||
"category": "CONTRIBUTIONS",
|
||||
"planned": 5000000.00,
|
||||
"realized": 4750000.00,
|
||||
"realizationRate": 95.0,
|
||||
"variance": -250000.00,
|
||||
"isOverBudget": false
|
||||
}
|
||||
],
|
||||
"topVariances": [
|
||||
{"category": "EVENTS", "variance": -500000.00},
|
||||
{"category": "OPERATIONAL", "variance": 200000.00}
|
||||
],
|
||||
"overallRealizationRate": 92.5
|
||||
}
|
||||
```
|
||||
|
||||
## Sécurité
|
||||
|
||||
### Authentification
|
||||
- JWT via Keycloak
|
||||
- Token injecté avec `@Inject JsonWebToken jwt`
|
||||
- Extraction: `UUID.fromString(jwt.getSubject())`
|
||||
|
||||
### Autorisation
|
||||
- `@RolesAllowed({"ORG_ADMIN", "SUPER_ADMIN"})` sur endpoints administratifs
|
||||
- Validation approbateur != demandeur dans ApprovalService
|
||||
|
||||
### Validation des données
|
||||
- Bean Validation sur tous les DTOs
|
||||
- Contraintes CHECK en base de données
|
||||
- Validation métier dans les services
|
||||
|
||||
## Intégration avec le mobile
|
||||
|
||||
### Endpoints utilisés par Flutter
|
||||
```dart
|
||||
// Approbations
|
||||
GET /api/finance/approvals/pending?organizationId={id}
|
||||
GET /api/finance/approvals/{id}
|
||||
POST /api/finance/approvals/{id}/approve
|
||||
POST /api/finance/approvals/{id}/reject
|
||||
GET /api/finance/approvals/count/pending?organizationId={id}
|
||||
|
||||
// Budgets
|
||||
GET /api/finance/budgets?organizationId={id}&status={status}&year={year}
|
||||
GET /api/finance/budgets/{id}
|
||||
POST /api/finance/budgets
|
||||
GET /api/finance/budgets/{id}/tracking
|
||||
```
|
||||
|
||||
### Format des réponses
|
||||
- Toujours JSON
|
||||
- Dates ISO 8601: `yyyy-MM-dd'T'HH:mm:ss`
|
||||
- BigDecimal sérialisé en nombre
|
||||
- Listes jamais null (toujours `[]` si vide)
|
||||
|
||||
## Prochaines étapes
|
||||
|
||||
### Priorité P0 (Production blockers)
|
||||
- [x] Compilation backend réussie
|
||||
- [ ] Tests unitaires des services
|
||||
- [ ] Test d'intégration mobile-backend
|
||||
- [ ] Migration Flyway testée en dev
|
||||
- [ ] Documentation Swagger complétée
|
||||
|
||||
### Priorité P1 (Post-production)
|
||||
- [ ] Job scheduled pour marquer les approbations expirées
|
||||
- [ ] Notifications push lors d'une nouvelle demande d'approbation
|
||||
- [ ] Export PDF des budgets
|
||||
- [ ] Statistiques d'approbation (temps moyen, taux d'approbation, etc.)
|
||||
- [ ] Audit log des actions d'approbation
|
||||
|
||||
### Priorité P2 (Améliorations futures)
|
||||
- [ ] Délégation d'approbations
|
||||
- [ ] Workflows d'approbation personnalisables par organisation
|
||||
- [ ] Templates de budgets
|
||||
- [ ] Comparaison budgets multi-périodes
|
||||
- [ ] Alertes de dépassement budgétaire
|
||||
|
||||
## Fichiers créés
|
||||
|
||||
### Entities (4)
|
||||
- `TransactionApproval.java` (142 lignes)
|
||||
- `ApproverAction.java` (98 lignes)
|
||||
- `Budget.java` (178 lignes)
|
||||
- `BudgetLine.java` (92 lignes)
|
||||
|
||||
### Repositories (2)
|
||||
- `TransactionApprovalRepository.java` (87 lignes)
|
||||
- `BudgetRepository.java` (76 lignes)
|
||||
|
||||
### Services (2)
|
||||
- `ApprovalService.java` (234 lignes)
|
||||
- `BudgetService.java` (187 lignes)
|
||||
|
||||
### Resources (2)
|
||||
- `ApprovalResource.java` (198 lignes)
|
||||
- `BudgetResource.java` (132 lignes)
|
||||
|
||||
### DTOs Response (4)
|
||||
- `TransactionApprovalResponse.java` (82 lignes)
|
||||
- `ApproverActionResponse.java` (45 lignes)
|
||||
- `BudgetResponse.java` (93 lignes)
|
||||
- `BudgetLineResponse.java` (48 lignes)
|
||||
|
||||
### DTOs Request (4)
|
||||
- `ApproveTransactionRequest.java` (27 lignes)
|
||||
- `RejectTransactionRequest.java` (27 lignes)
|
||||
- `CreateBudgetRequest.java` (58 lignes)
|
||||
- `CreateBudgetLineRequest.java` (42 lignes)
|
||||
|
||||
### Migration (1)
|
||||
- `V6__Create_Finance_Workflow_Tables.sql` (187 lignes)
|
||||
|
||||
**Total: 19 fichiers, ~2023 lignes de code**
|
||||
|
||||
## Conclusion
|
||||
|
||||
✅ **Implémentation backend Finance Workflow complétée avec succès**
|
||||
|
||||
L'implémentation suit rigoureusement les patterns établis dans UnionFlow:
|
||||
- Architecture multi-module (API/Implementation)
|
||||
- BaseEntity et BaseRepository
|
||||
- Services transactionnels
|
||||
- REST resources avec sécurité JWT
|
||||
- Flyway pour la migration
|
||||
- Validation complète (Bean Validation + DB constraints)
|
||||
|
||||
Le backend est maintenant prêt pour:
|
||||
1. Tests unitaires et d'intégration
|
||||
2. Déploiement en environnement de développement
|
||||
3. Intégration avec l'app mobile Flutter
|
||||
4. Tests end-to-end du workflow complet
|
||||
|
||||
**Date de complétion:** 2026-03-14
|
||||
**Status:** ✅ READY FOR TESTING
|
||||
Reference in New Issue
Block a user