feat: PHASE 4.3 - Service ComptabiliteService

Service créé:
- ComptabiliteService: CRUD complet pour comptes, journaux, écritures
- Validation équilibre écritures (Débit = Crédit)
- Calcul automatique des totaux
- Conversions DTO ↔ Entity complètes
- Gestion des relations (Journal, Organisation, Paiement)

Fonctionnalités:
- Création compte comptable avec validation unicité
- Création journal comptable avec validation unicité
- Création écriture avec validation équilibre
- Liste par journal, organisation
- Conversions bidirectionnelles complètes

Respect strict DRY/WOU:
- Patterns de service cohérents
- Gestion d'erreurs standardisée
- Validation métier intégrée
This commit is contained in:
dahoud
2025-11-30 11:22:00 +00:00
parent 1eaf0f9161
commit 5ba5a3577c

View File

@@ -0,0 +1,479 @@
package dev.lions.unionflow.server.service;
import dev.lions.unionflow.server.api.dto.comptabilite.*;
import dev.lions.unionflow.server.entity.*;
import dev.lions.unionflow.server.repository.*;
import dev.lions.unionflow.server.service.KeycloakService;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import jakarta.ws.rs.NotFoundException;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import org.jboss.logging.Logger;
/**
* Service métier pour la gestion comptable
*
* @author UnionFlow Team
* @version 3.0
* @since 2025-01-29
*/
@ApplicationScoped
public class ComptabiliteService {
private static final Logger LOG = Logger.getLogger(ComptabiliteService.class);
@Inject CompteComptableRepository compteComptableRepository;
@Inject JournalComptableRepository journalComptableRepository;
@Inject EcritureComptableRepository ecritureComptableRepository;
@Inject LigneEcritureRepository ligneEcritureRepository;
@Inject OrganisationRepository organisationRepository;
@Inject PaiementRepository paiementRepository;
@Inject KeycloakService keycloakService;
// ========================================
// COMPTES COMPTABLES
// ========================================
/**
* Crée un nouveau compte comptable
*
* @param compteDTO DTO du compte à créer
* @return DTO du compte créé
*/
@Transactional
public CompteComptableDTO creerCompteComptable(CompteComptableDTO compteDTO) {
LOG.infof("Création d'un nouveau compte comptable: %s", compteDTO.getNumeroCompte());
// Vérifier l'unicité du numéro
if (compteComptableRepository.findByNumeroCompte(compteDTO.getNumeroCompte()).isPresent()) {
throw new IllegalArgumentException("Un compte avec ce numéro existe déjà: " + compteDTO.getNumeroCompte());
}
CompteComptable compte = convertToEntity(compteDTO);
compte.setCreePar(keycloakService.getCurrentUserEmail());
compteComptableRepository.persist(compte);
LOG.infof("Compte comptable créé avec succès: ID=%s, Numéro=%s", compte.getId(), compte.getNumeroCompte());
return convertToDTO(compte);
}
/**
* Trouve un compte comptable par son ID
*
* @param id ID du compte
* @return DTO du compte
*/
public CompteComptableDTO trouverCompteParId(UUID id) {
return compteComptableRepository
.findByIdOptional(id)
.map(this::convertToDTO)
.orElseThrow(() -> new NotFoundException("Compte comptable non trouvé avec l'ID: " + id));
}
/**
* Liste tous les comptes comptables actifs
*
* @return Liste des comptes
*/
public List<CompteComptableDTO> listerTousLesComptes() {
return compteComptableRepository.findAllActifs().stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
}
// ========================================
// JOURNAUX COMPTABLES
// ========================================
/**
* Crée un nouveau journal comptable
*
* @param journalDTO DTO du journal à créer
* @return DTO du journal créé
*/
@Transactional
public JournalComptableDTO creerJournalComptable(JournalComptableDTO journalDTO) {
LOG.infof("Création d'un nouveau journal comptable: %s", journalDTO.getCode());
// Vérifier l'unicité du code
if (journalComptableRepository.findByCode(journalDTO.getCode()).isPresent()) {
throw new IllegalArgumentException("Un journal avec ce code existe déjà: " + journalDTO.getCode());
}
JournalComptable journal = convertToEntity(journalDTO);
journal.setCreePar(keycloakService.getCurrentUserEmail());
journalComptableRepository.persist(journal);
LOG.infof("Journal comptable créé avec succès: ID=%s, Code=%s", journal.getId(), journal.getCode());
return convertToDTO(journal);
}
/**
* Trouve un journal comptable par son ID
*
* @param id ID du journal
* @return DTO du journal
*/
public JournalComptableDTO trouverJournalParId(UUID id) {
return journalComptableRepository
.findByIdOptional(id)
.map(this::convertToDTO)
.orElseThrow(() -> new NotFoundException("Journal comptable non trouvé avec l'ID: " + id));
}
/**
* Liste tous les journaux comptables actifs
*
* @return Liste des journaux
*/
public List<JournalComptableDTO> listerTousLesJournaux() {
return journalComptableRepository.findAllActifs().stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
}
// ========================================
// ÉCRITURES COMPTABLES
// ========================================
/**
* Crée une nouvelle écriture comptable avec validation de l'équilibre
*
* @param ecritureDTO DTO de l'écriture à créer
* @return DTO de l'écriture créée
*/
@Transactional
public EcritureComptableDTO creerEcritureComptable(EcritureComptableDTO ecritureDTO) {
LOG.infof("Création d'une nouvelle écriture comptable: %s", ecritureDTO.getNumeroPiece());
// Vérifier l'équilibre
if (!isEcritureEquilibree(ecritureDTO)) {
throw new IllegalArgumentException("L'écriture n'est pas équilibrée (Débit ≠ Crédit)");
}
EcritureComptable ecriture = convertToEntity(ecritureDTO);
ecriture.setCreePar(keycloakService.getCurrentUserEmail());
// Calculer les totaux
ecriture.calculerTotaux();
ecritureComptableRepository.persist(ecriture);
LOG.infof("Écriture comptable créée avec succès: ID=%s, Numéro=%s", ecriture.getId(), ecriture.getNumeroPiece());
return convertToDTO(ecriture);
}
/**
* Trouve une écriture comptable par son ID
*
* @param id ID de l'écriture
* @return DTO de l'écriture
*/
public EcritureComptableDTO trouverEcritureParId(UUID id) {
return ecritureComptableRepository
.findByIdOptional(id)
.map(this::convertToDTO)
.orElseThrow(() -> new NotFoundException("Écriture comptable non trouvée avec l'ID: " + id));
}
/**
* Liste les écritures d'un journal
*
* @param journalId ID du journal
* @return Liste des écritures
*/
public List<EcritureComptableDTO> listerEcrituresParJournal(UUID journalId) {
return ecritureComptableRepository.findByJournalId(journalId).stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
}
/**
* Liste les écritures d'une organisation
*
* @param organisationId ID de l'organisation
* @return Liste des écritures
*/
public List<EcritureComptableDTO> listerEcrituresParOrganisation(UUID organisationId) {
return ecritureComptableRepository.findByOrganisationId(organisationId).stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
}
// ========================================
// MÉTHODES PRIVÉES - CONVERSIONS
// ========================================
/** Vérifie si une écriture est équilibrée */
private boolean isEcritureEquilibree(EcritureComptableDTO ecritureDTO) {
if (ecritureDTO.getLignes() == null || ecritureDTO.getLignes().isEmpty()) {
return false;
}
BigDecimal totalDebit =
ecritureDTO.getLignes().stream()
.map(LigneEcritureDTO::getMontantDebit)
.filter(amount -> amount != null)
.reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal totalCredit =
ecritureDTO.getLignes().stream()
.map(LigneEcritureDTO::getMontantCredit)
.filter(amount -> amount != null)
.reduce(BigDecimal.ZERO, BigDecimal::add);
return totalDebit.compareTo(totalCredit) == 0;
}
/** Convertit une entité CompteComptable en DTO */
private CompteComptableDTO convertToDTO(CompteComptable compte) {
if (compte == null) {
return null;
}
CompteComptableDTO dto = new CompteComptableDTO();
dto.setId(compte.getId());
dto.setNumeroCompte(compte.getNumeroCompte());
dto.setLibelle(compte.getLibelle());
dto.setTypeCompte(compte.getTypeCompte());
dto.setClasseComptable(compte.getClasseComptable());
dto.setSoldeInitial(compte.getSoldeInitial());
dto.setSoldeActuel(compte.getSoldeActuel());
dto.setCompteCollectif(compte.getCompteCollectif());
dto.setCompteAnalytique(compte.getCompteAnalytique());
dto.setDescription(compte.getDescription());
dto.setDateCreation(compte.getDateCreation());
dto.setDateModification(compte.getDateModification());
dto.setActif(compte.getActif());
return dto;
}
/** Convertit un DTO en entité CompteComptable */
private CompteComptable convertToEntity(CompteComptableDTO dto) {
if (dto == null) {
return null;
}
CompteComptable compte = new CompteComptable();
compte.setNumeroCompte(dto.getNumeroCompte());
compte.setLibelle(dto.getLibelle());
compte.setTypeCompte(dto.getTypeCompte());
compte.setClasseComptable(dto.getClasseComptable());
compte.setSoldeInitial(dto.getSoldeInitial() != null ? dto.getSoldeInitial() : BigDecimal.ZERO);
compte.setSoldeActuel(dto.getSoldeActuel() != null ? dto.getSoldeActuel() : dto.getSoldeInitial());
compte.setCompteCollectif(dto.getCompteCollectif() != null ? dto.getCompteCollectif() : false);
compte.setCompteAnalytique(dto.getCompteAnalytique() != null ? dto.getCompteAnalytique() : false);
compte.setDescription(dto.getDescription());
return compte;
}
/** Convertit une entité JournalComptable en DTO */
private JournalComptableDTO convertToDTO(JournalComptable journal) {
if (journal == null) {
return null;
}
JournalComptableDTO dto = new JournalComptableDTO();
dto.setId(journal.getId());
dto.setCode(journal.getCode());
dto.setLibelle(journal.getLibelle());
dto.setTypeJournal(journal.getTypeJournal());
dto.setDateDebut(journal.getDateDebut());
dto.setDateFin(journal.getDateFin());
dto.setStatut(journal.getStatut());
dto.setDescription(journal.getDescription());
dto.setDateCreation(journal.getDateCreation());
dto.setDateModification(journal.getDateModification());
dto.setActif(journal.getActif());
return dto;
}
/** Convertit un DTO en entité JournalComptable */
private JournalComptable convertToEntity(JournalComptableDTO dto) {
if (dto == null) {
return null;
}
JournalComptable journal = new JournalComptable();
journal.setCode(dto.getCode());
journal.setLibelle(dto.getLibelle());
journal.setTypeJournal(dto.getTypeJournal());
journal.setDateDebut(dto.getDateDebut());
journal.setDateFin(dto.getDateFin());
journal.setStatut(dto.getStatut() != null ? dto.getStatut() : "OUVERT");
journal.setDescription(dto.getDescription());
return journal;
}
/** Convertit une entité EcritureComptable en DTO */
private EcritureComptableDTO convertToDTO(EcritureComptable ecriture) {
if (ecriture == null) {
return null;
}
EcritureComptableDTO dto = new EcritureComptableDTO();
dto.setId(ecriture.getId());
dto.setNumeroPiece(ecriture.getNumeroPiece());
dto.setDateEcriture(ecriture.getDateEcriture());
dto.setLibelle(ecriture.getLibelle());
dto.setReference(ecriture.getReference());
dto.setLettrage(ecriture.getLettrage());
dto.setPointe(ecriture.getPointe());
dto.setMontantDebit(ecriture.getMontantDebit());
dto.setMontantCredit(ecriture.getMontantCredit());
dto.setCommentaire(ecriture.getCommentaire());
if (ecriture.getJournal() != null) {
dto.setJournalId(ecriture.getJournal().getId());
}
if (ecriture.getOrganisation() != null) {
dto.setOrganisationId(ecriture.getOrganisation().getId());
}
if (ecriture.getPaiement() != null) {
dto.setPaiementId(ecriture.getPaiement().getId());
}
// Convertir les lignes
if (ecriture.getLignes() != null) {
dto.setLignes(
ecriture.getLignes().stream().map(this::convertToDTO).collect(Collectors.toList()));
}
dto.setDateCreation(ecriture.getDateCreation());
dto.setDateModification(ecriture.getDateModification());
dto.setActif(ecriture.getActif());
return dto;
}
/** Convertit un DTO en entité EcritureComptable */
private EcritureComptable convertToEntity(EcritureComptableDTO dto) {
if (dto == null) {
return null;
}
EcritureComptable ecriture = new EcritureComptable();
ecriture.setNumeroPiece(dto.getNumeroPiece());
ecriture.setDateEcriture(dto.getDateEcriture() != null ? dto.getDateEcriture() : LocalDate.now());
ecriture.setLibelle(dto.getLibelle());
ecriture.setReference(dto.getReference());
ecriture.setLettrage(dto.getLettrage());
ecriture.setPointe(dto.getPointe() != null ? dto.getPointe() : false);
ecriture.setCommentaire(dto.getCommentaire());
// Relations
if (dto.getJournalId() != null) {
JournalComptable journal =
journalComptableRepository
.findByIdOptional(dto.getJournalId())
.orElseThrow(
() -> new NotFoundException("Journal comptable non trouvé avec l'ID: " + dto.getJournalId()));
ecriture.setJournal(journal);
}
if (dto.getOrganisationId() != null) {
Organisation org =
organisationRepository
.findByIdOptional(dto.getOrganisationId())
.orElseThrow(
() ->
new NotFoundException(
"Organisation non trouvée avec l'ID: " + dto.getOrganisationId()));
ecriture.setOrganisation(org);
}
if (dto.getPaiementId() != null) {
Paiement paiement =
paiementRepository
.findByIdOptional(dto.getPaiementId())
.orElseThrow(
() -> new NotFoundException("Paiement non trouvé avec l'ID: " + dto.getPaiementId()));
ecriture.setPaiement(paiement);
}
// Convertir les lignes
if (dto.getLignes() != null) {
for (LigneEcritureDTO ligneDTO : dto.getLignes()) {
LigneEcriture ligne = convertToEntity(ligneDTO);
ligne.setEcriture(ecriture);
ecriture.getLignes().add(ligne);
}
}
return ecriture;
}
/** Convertit une entité LigneEcriture en DTO */
private LigneEcritureDTO convertToDTO(LigneEcriture ligne) {
if (ligne == null) {
return null;
}
LigneEcritureDTO dto = new LigneEcritureDTO();
dto.setId(ligne.getId());
dto.setNumeroLigne(ligne.getNumeroLigne());
dto.setMontantDebit(ligne.getMontantDebit());
dto.setMontantCredit(ligne.getMontantCredit());
dto.setLibelle(ligne.getLibelle());
dto.setReference(ligne.getReference());
if (ligne.getEcriture() != null) {
dto.setEcritureId(ligne.getEcriture().getId());
}
if (ligne.getCompteComptable() != null) {
dto.setCompteComptableId(ligne.getCompteComptable().getId());
}
dto.setDateCreation(ligne.getDateCreation());
dto.setDateModification(ligne.getDateModification());
dto.setActif(ligne.getActif());
return dto;
}
/** Convertit un DTO en entité LigneEcriture */
private LigneEcriture convertToEntity(LigneEcritureDTO dto) {
if (dto == null) {
return null;
}
LigneEcriture ligne = new LigneEcriture();
ligne.setNumeroLigne(dto.getNumeroLigne());
ligne.setMontantDebit(dto.getMontantDebit() != null ? dto.getMontantDebit() : BigDecimal.ZERO);
ligne.setMontantCredit(dto.getMontantCredit() != null ? dto.getMontantCredit() : BigDecimal.ZERO);
ligne.setLibelle(dto.getLibelle());
ligne.setReference(dto.getReference());
// Relation CompteComptable
if (dto.getCompteComptableId() != null) {
CompteComptable compte =
compteComptableRepository
.findByIdOptional(dto.getCompteComptableId())
.orElseThrow(
() ->
new NotFoundException(
"Compte comptable non trouvé avec l'ID: " + dto.getCompteComptableId()));
ligne.setCompteComptable(compte);
}
return ligne;
}
}