871 lines
28 KiB
Markdown
871 lines
28 KiB
Markdown
# 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
|