Files
unionflow-server-impl-quarkus/BACKEND_FINANCE_WORKFLOW_IMPLEMENTATION.md

28 KiB

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

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

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

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

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

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

@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

@Data
public class ApproveTransactionRequest {
    @Size(max = 1000, message = "Le commentaire ne peut dépasser 1000 caractères")
    private String comment;
}

RejectTransactionRequest.java

@Data
public class RejectTransactionRequest {
    @NotBlank(message = "La raison du rejet est requise")
    @Size(min = 10, max = 1000)
    private String reason;
}

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

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

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

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

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

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

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

# 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

// 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:

{
  "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

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

  • 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