Story AB-020 : Ajustement Manuel du Solde (Backend + Frontend)¶
Module : absence-management Tranche : VS-AB-015 de l'architecture Contexte de marque : [LES DEUX] Papillon HR Suite + ALTARYS ENTERPRISE HR Suite Priorité : 20 Dépend de : AB-012 Parallèle avec : AB-021 Ordre de fusion : Ordre quelconque avec AB-021 Complexité estimée : S PRD User Stories : N/A - Technical Story
Objectif¶
Permettre aux responsables RH et aux DG d'ajuster manuellement le solde de congé d'un employé (ajouter ou retirer des jours) avec un motif obligatoire, en créant une entrée de grand livre MANUAL_ADJUSTMENT auditée. Utilise le même pattern de verrouillage pessimiste que l'approbation de congé (depuis BalanceMutationService d'AB-012) pour éviter la corruption concurrente des soldes.
Périmètre Backend¶
Entités et Enregistrements¶
Aucune nouvelle entité. Cette story étend le modèle de grand livre existant d'AB-012.
Type de transaction d'entrée de grand livre (étendre l'enum) :
| Valeur | Description |
|---|---|
| MANUAL_ADJUSTMENT | Ajustement manuel du solde par RH/DG — montant signé (positif=ajouter, négatif=retirer) |
DTOs :
public record AdjustBalanceRequest(
@NotBlank String leaveType, // "ANNUAL" (MVP uniquement)
@NotNull BigDecimal amount, // positif=ajouter, négatif=retirer, jamais zéro
@NotBlank @Size(min = 10) String reason,
@NotNull LeaveUnit unit // OUVRABLES, CALENDAIRES, OUVRES
) {}
Couche Repository¶
Aucun nouveau repository. Utilise les repositories de grand livre et LeaveEmployeeProfile existants d'AB-012.
Couche Service¶
BalanceMutationService.java (extension depuis AB-012) :
- adjustBalance(UUID tenantId, UUID employeeId, String leaveType, BigDecimal amount, String reason, LeaveUnit unit) -> LeaveEmployeeProfile :
1. Acquérir le verrou pessimiste sur LeaveEmployeeProfile (SELECT FOR UPDATE)
2. Convertir le montant en jours ouvrables si l'unité d'entrée est CALENDAIRES ou OUVRES
3. Valider que le solde résultant n'est pas négatif (bloquer si le retrait ferait passer le solde en dessous de 0)
4. Insérer l'entrée de grand livre MANUAL_ADJUSTMENT (signé : positif pour ajouter, négatif pour retirer)
5. Mettre à jour le cache annual_balance_ouvrables
6. Publier l'événement AuditEntryRequested avec motif, montant, employeeId, performedBy
7. Libérer le verrou (fin de transaction)
Points de terminaison API¶
| Méthode | Chemin | Corps de la requête | Réponse | Auth |
|---|---|---|---|---|
| PATCH | /api/v1/absence/employees/{employeeId}/adjust-balance | AdjustBalanceRequest | LeaveEmployeeProfileResponse | HR_MANAGER, DG |
Règles de validation¶
reasonest obligatoire : non vide, minimum 10 caractèresamountne doit pas être zéroleaveTypedoit être "ANNUAL" (MVP — seul le solde annuel est ajustable)unitdoit être une valeur d'enum LeaveUnit valide- Le solde résultant ne doit pas être négatif (bloquer si le retrait causerait un solde < 0)
- Seuls les rôles HR_MANAGER ou DG peuvent invoquer cet endpoint
Considérations Multi-Tenant¶
- Toutes les opérations limitées par tenant_id depuis TenantResolutionFilter
- Le verrou pessimiste est isolé par tenant (SELECT FOR UPDATE sur le LeaveEmployeeProfile limité au tenant)
- L'entrée de grand livre enregistre tenant_id pour la piste d'audit
- L'employé doit appartenir au même tenant que l'utilisateur authentifié
Migrations Flyway¶
Aucune nouvelle migration. MANUAL_ADJUSTMENT fait déjà partie de l'enum transaction_type depuis la configuration du grand livre d'AB-012.
Périmètre Frontend¶
Pages et Routes¶
/absences/employes/:id/ajustement— Page de formulaire d'ajustement manuel du solde- Accès : HR_MANAGER, DG
Composants¶
ManualBalanceAdjustmentPage.tsx— Page complète (mobile) / Modal (desktop) pour l'ajustement du soldeBalancePreviewCard.tsx— Affiche le solde actuel et le nouveau solde calculé en temps réelAdjustmentForm.tsx— Formulaire avec radio de direction, champ de saisie du montant, textarea du motif
États UI (TOUS REQUIS)¶
| État | Comportement | Copie Française |
|---|---|---|
| Chargement | Squelette pour la zone d'affichage du solde | — |
| Vide | N/A (le formulaire s'affiche toujours) | — |
| Erreur (résultat négatif) | Erreur inline sous le champ de saisie du montant | "Le solde ne peut pas etre negatif." |
| Erreur (aucun motif) | Erreur inline sur le textarea | "Veuillez indiquer le motif de l'ajustement." |
| Erreur (motif trop court) | Erreur inline sur le textarea | "Le motif doit contenir au moins 10 caracteres." |
| Erreur (API) | Toast | "Impossible d'effectuer l'ajustement." |
| Hors ligne | Formulaire bloqué, bandeau affiché | "Cette action necessite une connexion internet" |
| Succès | Toast + navigation retour | "Solde ajuste -- {+/-n} jours" |
Comportement Responsive¶
- 360px (mobile) : Mise en page pleine page.
- Titre de page : "Ajustement de solde" avec sous-titre nom de l'employé
- Affichage du solde actuel : "Solde actuel : {n} JC"
- RadioGroup : "Ajouter des jours" / "Retirer des jours"
- Input (type=number) : "Nombre de jours (calendaires)"
- Calcul en temps réel : "Nouveau solde : {n} JC" (se met à jour pendant la saisie)
- Textarea (obligatoire) : label "Motif (obligatoire)", placeholder "Expliquez la raison..."
- Bandeau d'avertissement : "Cet ajustement sera enregistre dans l'historique d'audit."
- Button (primary, full-width) : "Confirmer l'ajustement"
- 768px (tablette) : Même mise en page, formulaire plus large avec max-width 500px centré.
- 1024px+ (desktop) : Modal ou panneau latéral, max-width 500px. Même contenu de formulaire.
Interactions¶
- Bascule de direction : RadioGroup bascule entre "Ajouter des jours" et "Retirer des jours". Le basculement met à jour le signe du calcul en temps réel.
- Aperçu du solde en temps réel : Au fur et à mesure que l'utilisateur saisit un montant, "Nouveau solde : {n} JC" se met à jour en temps réel. Si le résultat serait négatif, afficher une erreur inline immédiatement.
- Envoi : Appelle l'endpoint PATCH. En cas de succès, toast "Solde ajuste -- {+/-n} jours" et navigation vers le détail du solde employé (AB-021).
- Blocage hors ligne : Si l'appareil est hors ligne, les champs du formulaire sont désactivés et un bandeau est affiché. Aucune mise en file d'attente hors ligne pour cette action.
Critères d'Acceptation¶
AC-064 : Le RH peut ajuster le solde de n'importe quel employé
Étant donné que je suis authentifié en tant que HR_MANAGER ou DG
Quand je soumets un ajustement manuel du solde pour un employé avec amount=5, reason="Rattrapage jours non comptabilises en janvier", unit=OUVRABLES
Alors le solde est mis à jour de +5 jours ouvrables
Et une entrée de grand livre MANUAL_ADJUSTMENT est créée avec un montant positif, le motif et created_by
Et un événement AuditEntryRequested est publié
AC-065 : L'ajustement nécessite un motif obligatoire
Étant donné que j'essaie de soumettre un ajustement du solde
Quand le champ motif est vide ou inférieur à 10 caractères
Alors la soumission est bloquée avec "Veuillez indiquer le motif de l'ajustement."
Et aucune entrée de grand livre n'est créée
AC-066 : L'ajustement est audité dans le grand livre
Étant donné qu'un ajustement manuel est soumis avec motif "Correction suite a erreur de saisie" et amount=-3
Quand l'ajustement est traité
Alors une entrée de grand livre MANUAL_ADJUSTMENT est créée avec amount=-3, reason="Correction suite a erreur de saisie" et created_by={utilisateur actuel}
Et un événement AuditEntryRequested est publié
Et le cache annual_balance_ouvrables est décrémenté de 3
Règles OHADA et Réglementaires¶
- FR-ABS-120 : Types de transactions du grand livre — MANUAL_ADJUSTMENT est un type de transaction valide pour le grand livre du solde de congé
- FR-ABS-121 : Champs de l'entrée de grand livre — chaque entrée doit inclure : date, transaction_type, amount, reason, created_by, employee_id, tenant_id
- FR-ABS-123 : Grand livre immuable — les corrections sont toujours de nouvelles entrées, jamais des modifications d'entrées existantes. Un MANUAL_ADJUSTMENT pour corriger une erreur précédente crée une nouvelle entrée compensatrice.
Standards et Conventions¶
docs/standards/java-spring-guidelines.md— Verrouillage pessimiste, patterns couche service, publication d'événementsdocs/standards/database-guidelines.md— BigDecimal pour les calculs financiers/de solde, isolation tenantdocs/standards/api-guidelines.md— Sémantique PATCH, enveloppe ApiResponse standard, formats d'erreur 403/422docs/standards/react-typescript-guidelines.md— Patterns de formulaires, validation inline, notifications toastdocs/standards/design-system.md— Composants RadioGroup, Input, Button, Toast, Bannerdocs/standards/offline-sync-guidelines.md— Pattern d'action en ligne uniquement (pas de mise en file d'attente hors ligne)docs/standards/multi-datasource-patterns.md— Accès aux données limité au tenant via JdbcClient
Exigences de Test¶
Tests Unitaires¶
- BalanceMutationService : adjustBalance avec montant positif (ajouter des jours)
- BalanceMutationService : adjustBalance avec montant négatif (retirer des jours)
- BalanceMutationService : rejeter l'ajustement qui causerait un solde négatif
- BalanceMutationService : rejeter le motif vide ou court (< 10 caractères)
- BalanceMutationService : rejeter le montant zéro
- BalanceMutationService : conversion d'unité (CALENDAIRES/OUVRES vers OUVRABLES)
Tests d'Intégration¶
- PATCH /api/v1/absence/employees/{id}/adjust-balance — flux complet avec verrouillage pessimiste
- Endpoint PATCH — 403 pour le rôle EMPLOYE (non autorisé)
- Endpoint PATCH — 422 pour solde résultant négatif
- Endpoint PATCH — 422 pour motif manquant/court
- Isolation tenant : l'ajustement pour l'employé du tenant A n'est pas accessible depuis le tenant B
- Événement d'audit publié avec le payload correct
Ce que QA Validera¶
- Le formulaire d'ajustement s'affiche correctement sur mobile (360px), tablette (768px), desktop (1024px+)
- L'aperçu du solde en temps réel se met à jour pendant la saisie du montant
- Les erreurs de validation inline s'affichent pour : motif vide, motif court, solde résultant négatif
- L'état hors ligne bloque le formulaire et affiche le bandeau
- Le toast de succès s'affiche et navigue vers le détail du solde
- L'entrée de grand livre est visible dans le détail du solde employé (AB-021) après l'ajustement
Hors Périmètre¶
- Ajustements de solde en masse (ajuster plusieurs employés à la fois) — Enterprise V2
- Ajustement des compteurs de congé maladie ou exceptionnel — MVP ajuste uniquement le solde annuel
- Workflow d'approbation pour les ajustements — MVP permet l'ajustement direct par RH/DG sans approbation secondaire
- UI d'annulation/inversion d'ajustement — les corrections sont de nouvelles entrées MANUAL_ADJUSTMENT conformément à FR-ABS-123
Définition de Done¶
- [ ] Tous les endpoints backend implémentés et retournant les réponses correctes
- [ ] Toutes les pages frontend s'affichent correctement à 360px, 768px, 1024px
- [ ] Tous les états UI implémentés (chargement, variantes d'erreur, hors ligne, succès)
- [ ] La micro-copie française correspond exactement à la spécification design
- [ ] Tous les critères d'acceptation passent manuellement
- [ ] Tests unitaires écrits et passants
- [ ] Tests d'intégration écrits et passants
- [ ] Isolation multi-tenant vérifiée (aucune fuite de données inter-tenant)
- [ ] Synchronisation hors ligne (Papillon) — N/A (action en ligne uniquement)
- [ ] Pas de
anyTypeScript — tous les types explicites - [ ] Pas de types bruts Java — tous les génériques explicites
- [ ] Les réponses API suivent le format d'enveloppe standard
- [ ] Entrées de piste d'audit créées pour chaque mutation de données