From 5ba5a3577ca78be9f9a03b078cc014097957e510 Mon Sep 17 00:00:00 2001 From: dahoud Date: Sun, 30 Nov 2025 11:22:00 +0000 Subject: [PATCH] feat: PHASE 4.3 - Service ComptabiliteService MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../server/service/ComptabiliteService.java | 479 ++++++++++++++++++ 1 file changed, 479 insertions(+) create mode 100644 unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/ComptabiliteService.java diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/ComptabiliteService.java b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/ComptabiliteService.java new file mode 100644 index 0000000..5f31156 --- /dev/null +++ b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/service/ComptabiliteService.java @@ -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 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 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 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 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; + } +} +