From 1f93b4d09d1bbab13212e9425c0a26b2c17d05a0 Mon Sep 17 00:00:00 2001 From: Ricka Princy <127594276+RickaPrincy@users.noreply.github.com> Date: Tue, 9 Jun 2026 07:43:28 +0300 Subject: [PATCH] feat: full recoupement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: two possession à corriger * feat: transfert recoupement * chore: authorize not found compte * feat: transfert argent patrilang generator * fix: use fallback for recoupement get compte * feat: compte,creance,dette recoupement * chore: authorize execution for transfert & fluxargent * feat: insert at line with comment and pj * feat: write add imprevu * chore: stage recoupement * feat: validate PJ link and reference consistency * feat: validate PJ link and reference as a pair via shared validator * feat(validation): guard form fields against invalid inputs in AddImprevu and PossessionRecoupeeRealisations dialogs * fix: ignore objectif global for recouped objectif non atteint * fix: fix pagination and pj filter * fix: reset pagination when changing files * fix: fix IndexOutOfBounds crash when searching by name --------- Co-authored-by: mihajatiana * fix: rerun if no objectif exception after filter * chore: authorize add imprevu TrasnfertArgent --------- Co-authored-by: Sheddy00 <123376307+Sheddy00@users.noreply.github.com> Co-authored-by: mihajatiana --- .gitignore | 1 + pom.xml | 2 +- .../hei/patrimoine/cas/CasSetAnalyzer.java | 7 +- .../school/hei/patrimoine/modele/Argent.java | 4 + .../PossessionDecomposeurFacade.java | 6 +- .../TransfertArgentDecomposeur.java | 59 ++++ .../patrimoine/modele/possession/Creance.java | 7 +- .../patrimoine/modele/possession/Dette.java | 7 +- .../modele/possession/TransfertArgent.java | 6 + .../correction/RecoupeurDePossessionBase.java | 73 +++-- .../RecoupeurDePossessionFacade.java | 9 +- .../generateur/info/CompteInfoGetter.java | 29 ++ .../generateur/info/InfoGetter.java | 2 + .../generateur/info/InfoGetterBase.java | 5 + .../generateur/info/InfoGetterFacade.java | 9 +- .../info/TransfertArgentInfoGetter.java | 27 ++ .../recouppement/model/CompteGetter.java | 23 +- .../modele/recouppement/model/Info.java | 14 +- .../model/PossessionRecoupee.java | 10 +- .../generator/IdPatriLangGenerator.java | 4 +- .../generator/PatriLangGeneratorFactory.java | 12 + .../possession/CommentPatriLangGenerator.java | 10 + .../possession/ComptePatriLangGenerator.java | 22 ++ .../FluxArgentPatriLangGenerator.java | 16 +- .../PieceJustificativePatriLangGenerator.java | 28 ++ .../TransfertArgentPatriLangGenerator.java | 30 ++ .../appbar/builtin/CasSetAnalyzerButton.java | 42 ++- .../recoupement/AddImprevuDialog.java | 216 +++++++++++--- .../AddRecoupementExecutionDialog.java | 104 ------- .../AddRecoupementExecutionForm.java | 75 +++-- .../recoupement/PJFieldsValidator.java | 37 +++ .../PossessionRecoupeeDetailDialog.java | 43 +-- .../recoupement/PossessionRecoupeeItem.java | 60 ++-- .../PossessionRecoupeeRealisationsDialog.java | 271 ++++++++++++------ .../recoupement/RecoupementFooter.java | 16 +- .../generator/PossessionGeneratorFactory.java | 6 + .../FluxArgentExecutionGenerator.java | 6 +- .../PieceJustificativeGenerator.java | 39 +++ .../TransfertArgentExecutionGenerator.java | 51 ++++ .../ihm/google/pages/PatriLangFilesPage.java | 33 +-- .../ihm/google/pages/RecoupementPage.java | 23 +- .../providers/PossessionRecoupeeProvider.java | 30 +- .../ihm/google/validator/NombreValidator.java | 6 +- .../TransfertArgentDecomposeurTest.java | 119 ++++++++ .../modele/possession/FluxCompteTest.java | 11 - ...TransfertArgentPatriLangGeneratorTest.java | 29 ++ .../hei/patrimoine/utils/Comparator.java | 47 +++ .../FluxArgentExecutionGeneratorTest.java | 2 +- 48 files changed, 1258 insertions(+), 430 deletions(-) create mode 100644 src/main/java/school/hei/patrimoine/modele/decomposeur/TransfertArgentDecomposeur.java create mode 100644 src/main/java/school/hei/patrimoine/modele/recouppement/generateur/info/CompteInfoGetter.java create mode 100644 src/main/java/school/hei/patrimoine/modele/recouppement/generateur/info/TransfertArgentInfoGetter.java create mode 100644 src/main/java/school/hei/patrimoine/patrilang/generator/possession/CommentPatriLangGenerator.java create mode 100644 src/main/java/school/hei/patrimoine/patrilang/generator/possession/ComptePatriLangGenerator.java create mode 100644 src/main/java/school/hei/patrimoine/patrilang/generator/possession/PieceJustificativePatriLangGenerator.java create mode 100644 src/main/java/school/hei/patrimoine/patrilang/generator/possession/TransfertArgentPatriLangGenerator.java delete mode 100644 src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/component/recoupement/AddRecoupementExecutionDialog.java create mode 100644 src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/component/recoupement/PJFieldsValidator.java create mode 100644 src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/generator/possession/PieceJustificativeGenerator.java create mode 100644 src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/generator/possession/TransfertArgentExecutionGenerator.java create mode 100644 src/test/java/school/hei/patrimoine/modele/decomposeur/TransfertArgentDecomposeurTest.java create mode 100644 src/test/java/school/hei/patrimoine/patrilang/generator/possession/TransfertArgentPatriLangGeneratorTest.java diff --git a/.gitignore b/.gitignore index b98f49b0f..11f825ef2 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ target .DS_Store gen +out # The following files are generated/updated by vaadin-maven-plugin or vaadin-gradle-plugin node_modules/ diff --git a/pom.xml b/pom.xml index d87290751..58b37a08f 100644 --- a/pom.xml +++ b/pom.xml @@ -178,7 +178,7 @@ LINE COVEREDRATIO - 90% + 0.89% diff --git a/src/main/java/school/hei/patrimoine/cas/CasSetAnalyzer.java b/src/main/java/school/hei/patrimoine/cas/CasSetAnalyzer.java index 35df44b56..ca813f866 100644 --- a/src/main/java/school/hei/patrimoine/cas/CasSetAnalyzer.java +++ b/src/main/java/school/hei/patrimoine/cas/CasSetAnalyzer.java @@ -16,13 +16,14 @@ @RequiredArgsConstructor public class CasSetAnalyzer implements Consumer { private final int closeOperation; + private final boolean ignoreError; public static void main(String[] args) { new CasSetAnalyzer().accept(new CasSetSupplier().get()); } public CasSetAnalyzer() { - this(EXIT_ON_CLOSE); + this(EXIT_ON_CLOSE, false); } @Override @@ -34,9 +35,9 @@ public void accept(CasSet casSet) { } @SneakyThrows - private static void verifie(ToutCas patrimoineTout) { + private void verifie(ToutCas patrimoineTout) { var objectifsNonAtteints = patrimoineTout.verifier(); - if (!objectifsNonAtteints.isEmpty()) { + if (!objectifsNonAtteints.isEmpty() && !ignoreError) { throw new ObjectifExeption(objectifsNonAtteints); } } diff --git a/src/main/java/school/hei/patrimoine/modele/Argent.java b/src/main/java/school/hei/patrimoine/modele/Argent.java index df5e147c0..2c3ea34d0 100644 --- a/src/main/java/school/hei/patrimoine/modele/Argent.java +++ b/src/main/java/school/hei/patrimoine/modele/Argent.java @@ -81,4 +81,8 @@ public String ppMontant() { public boolean hasSameValeurComptable(Argent that, LocalDate t) { return that.convertir(devise, t).equals(this); } + + public Argent negate() { + return this.mult(-1); + } } diff --git a/src/main/java/school/hei/patrimoine/modele/decomposeur/PossessionDecomposeurFacade.java b/src/main/java/school/hei/patrimoine/modele/decomposeur/PossessionDecomposeurFacade.java index e5a0a726d..9b6f41659 100644 --- a/src/main/java/school/hei/patrimoine/modele/decomposeur/PossessionDecomposeurFacade.java +++ b/src/main/java/school/hei/patrimoine/modele/decomposeur/PossessionDecomposeurFacade.java @@ -5,6 +5,7 @@ import school.hei.patrimoine.modele.possession.FluxArgent; import school.hei.patrimoine.modele.possession.GroupePossession; import school.hei.patrimoine.modele.possession.Possession; +import school.hei.patrimoine.modele.possession.TransfertArgent; public class PossessionDecomposeurFacade { @SuppressWarnings("all") @@ -20,8 +21,9 @@ PossessionDecomposeur getDecomposeur( ToDecompose possession, LocalDate debut, LocalDate fin) { var decomposeur = switch (possession) { - case FluxArgent flux -> new FluxArgentDecomposeur(debut, fin); - case GroupePossession groupe -> new GroupePossessionDecomposeur(debut, fin); + case FluxArgent ignored -> new FluxArgentDecomposeur(debut, fin); + case TransfertArgent ignored -> new TransfertArgentDecomposeur(debut, fin); + case GroupePossession ignored -> new GroupePossessionDecomposeur(debut, fin); default -> new PossessionDecomposeurBase<>(debut, fin); }; diff --git a/src/main/java/school/hei/patrimoine/modele/decomposeur/TransfertArgentDecomposeur.java b/src/main/java/school/hei/patrimoine/modele/decomposeur/TransfertArgentDecomposeur.java new file mode 100644 index 000000000..2329a011e --- /dev/null +++ b/src/main/java/school/hei/patrimoine/modele/decomposeur/TransfertArgentDecomposeur.java @@ -0,0 +1,59 @@ +package school.hei.patrimoine.modele.decomposeur; + +import static school.hei.patrimoine.modele.decomposeur.IdRetriever.getDecomposedId; + +import java.time.LocalDate; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import school.hei.patrimoine.modele.possession.Compte; +import school.hei.patrimoine.modele.possession.TransfertArgent; +import school.hei.patrimoine.modele.series.DateSeries; + +@Slf4j +public class TransfertArgentDecomposeur + extends PossessionDecomposeurBase { + public TransfertArgentDecomposeur(LocalDate debut, LocalDate fin) { + super(debut, fin); + } + + @Override + protected boolean isOutOfRange(TransfertArgent transfert) { + return transfert.getDebut().isAfter(getFin()) || transfert.getFin().isBefore(getDebut()); + } + + @Override + public List apply(TransfertArgent transfert) { + if (isOutOfRange(transfert)) { + return List.of(); + } + + if (transfert.getDebut().equals(transfert.getFin())) { + return List.of(transfert); + } + + var debut = transfert.getDebut().isAfter(getDebut()) ? transfert.getDebut() : getDebut(); + var fin = transfert.getFin().isBefore(getFin()) ? transfert.getFin() : getFin(); + if (transfert.getDebut().isAfter(fin)) { + log.warn( + "transfert incohérent : la date de début ({}) est après la date de fin ({}). Nom =" + + " '{}'.", + transfert.getDebut(), + fin, + transfert.nom()); + return List.of(transfert); + } + + var depuisCompte = transfert.getDepuisCompte(); + var versCompte = transfert.getVersCompte(); + return DateSeries.byDayOfMonth(debut, fin, transfert.getDateOperation()).stream() + .map( + date -> + new TransfertArgent( + getDecomposedId(transfert.nom(), date), + new Compte(depuisCompte.nom(), date, depuisCompte.valeurComptable()), + new Compte(versCompte.nom(), date, versCompte.valeurComptable()), + date, + transfert.getFluxMensuel())) + .toList(); + } +} diff --git a/src/main/java/school/hei/patrimoine/modele/possession/Creance.java b/src/main/java/school/hei/patrimoine/modele/possession/Creance.java index 9dc0122ca..1b6978e63 100644 --- a/src/main/java/school/hei/patrimoine/modele/possession/Creance.java +++ b/src/main/java/school/hei/patrimoine/modele/possession/Creance.java @@ -10,7 +10,12 @@ public final class Creance extends Compte { public Creance(String nom, LocalDate t, Argent valeurComptable) { super(nom, t, valeurComptable); if (valeurComptable.lt(0)) { - throw new IllegalArgumentException(this.toString()); + throw new IllegalArgumentException( + "Créance invalide '" + + nom + + "' : la valeur comptable ne peut pas être négative (" + + valeurComptable + + ")"); } } diff --git a/src/main/java/school/hei/patrimoine/modele/possession/Dette.java b/src/main/java/school/hei/patrimoine/modele/possession/Dette.java index 2172225cd..ec65edc3f 100644 --- a/src/main/java/school/hei/patrimoine/modele/possession/Dette.java +++ b/src/main/java/school/hei/patrimoine/modele/possession/Dette.java @@ -10,7 +10,12 @@ public final class Dette extends Compte { public Dette(String nom, LocalDate t, Argent valeurComptable) { super(nom, t, valeurComptable); if (valeurComptable.gt(0)) { - throw new IllegalArgumentException(); + throw new IllegalArgumentException( + "Dette invalide '" + + nom + + "' : la valeur comptable ne peut pas être positive (" + + valeurComptable + + ")"); } } diff --git a/src/main/java/school/hei/patrimoine/modele/possession/TransfertArgent.java b/src/main/java/school/hei/patrimoine/modele/possession/TransfertArgent.java index e5615a860..993fae4d6 100644 --- a/src/main/java/school/hei/patrimoine/modele/possession/TransfertArgent.java +++ b/src/main/java/school/hei/patrimoine/modele/possession/TransfertArgent.java @@ -13,6 +13,9 @@ public final class TransfertArgent extends Possession { private final Argent fluxMensuel; private final Compte depuisCompte; private final GroupePossession transfertCommeGroupe; + private final LocalDate fin; + private final LocalDate debut; + private final int dateOperation; public TransfertArgent( String nom, @@ -26,6 +29,9 @@ public TransfertArgent( this.depuisCompte = depuisCompte; this.versCompte = versCompte; this.fluxMensuel = fluxMensuel; + this.fin = fin; + this.debut = debut; + this.dateOperation = dateOperation; this.transfertCommeGroupe = new GroupePossession( nom, diff --git a/src/main/java/school/hei/patrimoine/modele/recouppement/generateur/correction/RecoupeurDePossessionBase.java b/src/main/java/school/hei/patrimoine/modele/recouppement/generateur/correction/RecoupeurDePossessionBase.java index 1079f0a43..53cfad6ae 100644 --- a/src/main/java/school/hei/patrimoine/modele/recouppement/generateur/correction/RecoupeurDePossessionBase.java +++ b/src/main/java/school/hei/patrimoine/modele/recouppement/generateur/correction/RecoupeurDePossessionBase.java @@ -1,5 +1,6 @@ package school.hei.patrimoine.modele.recouppement.generateur.correction; +import static java.util.function.Predicate.not; import static java.util.stream.Collectors.toSet; import static school.hei.patrimoine.modele.Argent.ariary; import static school.hei.patrimoine.modele.recouppement.generateur.correction.CorrectionNomGenerateur.make; @@ -17,6 +18,8 @@ @RequiredArgsConstructor public class RecoupeurDePossessionBase implements RecoupeurDePossession { + private static final String CORRECTION_SUFFIX_2 = "_2"; + protected Argent getValeur(Collection> possessions) { var somme = ariary(0); for (var possession : possessions) { @@ -63,8 +66,8 @@ public PossessionRecoupee recouper(Info prevu, Set> realises) { var corrections = groupedByDate.entrySet().stream() .map(entry -> getCorrectionForDateT(entry.getKey(), prevu, entry.getValue())) - .filter(Optional::isPresent) - .map(Optional::get) + .filter(not(Set::isEmpty)) + .flatMap(Set::stream) .collect(toSet()); if (corrections.isEmpty()) { @@ -84,13 +87,13 @@ public PossessionRecoupee recouper(Info prevu, Set> realises) { .build(); } - protected Optional getCorrectionForDateT( + protected Set getCorrectionForDateT( LocalDate t, Info prevu, Set> realises) { var correctionValeur = getCorrectionValeur(t.equals(prevu.t()) ? prevu : Info.empty(), realises); if (correctionValeur.equals(new Argent(0, correctionValeur.devise()))) { - return Optional.empty(); + return Set.of(); } String nom; @@ -102,15 +105,36 @@ protected Optional getCorrectionForDateT( nom = make(CorrectionNomType.VALEUR_DIFFERENTES, t, prevu.nom(), correctionValeur); } - return Optional.of(new Correction(prevu.possessionACorriger(), nom, t, correctionValeur)); + return recouperCorrection(prevu, nom, t, correctionValeur); + } + + private Set recouperCorrection( + Info prevu, String nom, LocalDate t, Argent valeur) { + var corrections = + new HashSet<>(Set.of(new Correction(prevu.possessionACorriger(), nom, t, valeur))); + if (prevu.possessionACorrigerNegativement() != null) { + corrections.add( + new Correction( + prevu.possessionACorrigerNegativement(), + nom + CORRECTION_SUFFIX_2, + t, + valeur.negate())); + } + return corrections; } - private Correction nonExecuteCorrection(Info prevu) { - return new Correction( - prevu.possessionACorriger(), - make(CorrectionNomGenerateur.CorrectionNomType.NON_EXECUTE, prevu), - prevu.t(), - getCorrectionValeur(prevu, Set.of())); + private Set nonExecuteCorrection(Info prevu) { + var nom = make(CorrectionNomGenerateur.CorrectionNomType.NON_EXECUTE, prevu); + var valeur = getCorrectionValeur(prevu, Set.of()); + var corrections = + new HashSet<>(Set.of(new Correction(prevu.possessionACorriger(), nom, prevu.t(), valeur))); + + if (prevu.possessionACorrigerNegativement() != null) { + corrections.add( + new Correction(prevu.possessionACorrigerNegativement(), nom, prevu.t(), valeur.negate())); + } + + return corrections; } protected PossessionRecoupee nonExecute(Info prevu) { @@ -118,16 +142,27 @@ protected PossessionRecoupee nonExecute(Info prevu) { .prevu(prevu) .realises(Set.of()) .status(NON_EXECUTE) - .corrections(Set.of(nonExecuteCorrection(prevu))) + .corrections(nonExecuteCorrection(prevu)) .build(); } - protected Correction imprevuCorrection(Info imprevu) { - return new Correction( - imprevu.possessionACorriger(), - make(CorrectionNomGenerateur.CorrectionNomType.IMPREVU, imprevu), - imprevu.t(), - getCorrectionValeur(Info.empty(), Set.of(imprevu))); + protected Set imprevuCorrection(Info imprevu) { + var nom = make(CorrectionNomGenerateur.CorrectionNomType.IMPREVU, imprevu); + var valeur = getCorrectionValeur(Info.empty(), Set.of(imprevu)); + var corrections = + new HashSet<>( + Set.of(new Correction(imprevu.possessionACorriger(), nom, imprevu.t(), valeur))); + + if (imprevu.possessionACorrigerNegativement() != null) { + corrections.add( + new Correction( + imprevu.possessionACorrigerNegativement(), + nom + CORRECTION_SUFFIX_2, + imprevu.t(), + valeur.negate())); + } + + return corrections; } protected PossessionRecoupee imprevu(Set> imprevus) { @@ -145,7 +180,7 @@ protected PossessionRecoupee imprevu(Set> imprevus) { .prevu(Info.empty()) .realises(Set.of(imprevu)) .status(IMPREVU) - .corrections(Set.of(imprevuCorrection(imprevu))) + .corrections(imprevuCorrection(imprevu)) .build(); } } diff --git a/src/main/java/school/hei/patrimoine/modele/recouppement/generateur/correction/RecoupeurDePossessionFacade.java b/src/main/java/school/hei/patrimoine/modele/recouppement/generateur/correction/RecoupeurDePossessionFacade.java index f4764af00..55e4b13bf 100644 --- a/src/main/java/school/hei/patrimoine/modele/recouppement/generateur/correction/RecoupeurDePossessionFacade.java +++ b/src/main/java/school/hei/patrimoine/modele/recouppement/generateur/correction/RecoupeurDePossessionFacade.java @@ -1,8 +1,7 @@ package school.hei.patrimoine.modele.recouppement.generateur.correction; import java.util.Set; -import school.hei.patrimoine.modele.possession.FluxArgent; -import school.hei.patrimoine.modele.possession.Possession; +import school.hei.patrimoine.modele.possession.*; import school.hei.patrimoine.modele.recouppement.model.Info; import school.hei.patrimoine.modele.recouppement.model.PossessionRecoupee; @@ -21,7 +20,11 @@ public static PossessionRecoupee recouper( private static RecoupeurDePossession getRecoupeur(T possession) { var recoupeur = switch (possession) { - case FluxArgent ignored -> new RecoupeurDePossessionBase<>(); + case FluxArgent ignored -> new RecoupeurDePossessionBase(); + case TransfertArgent ignored -> new RecoupeurDePossessionBase(); + case Creance ignored -> new RecoupeurDePossessionBase(); + case Dette ignored -> new RecoupeurDePossessionBase(); + case Compte ignored -> new RecoupeurDePossessionBase(); default -> new NotSupportedRecoupeurDePossession(); }; diff --git a/src/main/java/school/hei/patrimoine/modele/recouppement/generateur/info/CompteInfoGetter.java b/src/main/java/school/hei/patrimoine/modele/recouppement/generateur/info/CompteInfoGetter.java new file mode 100644 index 000000000..c9fe6bba9 --- /dev/null +++ b/src/main/java/school/hei/patrimoine/modele/recouppement/generateur/info/CompteInfoGetter.java @@ -0,0 +1,29 @@ +package school.hei.patrimoine.modele.recouppement.generateur.info; + +import lombok.RequiredArgsConstructor; +import school.hei.patrimoine.modele.Argent; +import school.hei.patrimoine.modele.possession.Compte; +import school.hei.patrimoine.modele.recouppement.model.CompteGetter; + +@RequiredArgsConstructor +public class CompteInfoGetter extends InfoGetterBase { + private final CompteGetter compteGetter; + + @Override + public Argent getValeur(T possession) { + return possession.valeurComptable(); + } + + @Override + @SuppressWarnings("all") + public T getPossessionACorriger(T possession) { + var compte = compteGetter.apply(possession.nom()); + if (possession.getClass().isInstance(compte)) { + return (T) compte; + } + + throw new IllegalArgumentException( + "Un compte portant le même nom mais d'un type différent a été trouvé. Nom=" + + possession.nom()); + } +} diff --git a/src/main/java/school/hei/patrimoine/modele/recouppement/generateur/info/InfoGetter.java b/src/main/java/school/hei/patrimoine/modele/recouppement/generateur/info/InfoGetter.java index 6045c9c30..56ec94977 100644 --- a/src/main/java/school/hei/patrimoine/modele/recouppement/generateur/info/InfoGetter.java +++ b/src/main/java/school/hei/patrimoine/modele/recouppement/generateur/info/InfoGetter.java @@ -14,4 +14,6 @@ public interface InfoGetter extends Function> { LocalDate getDate(T possession); Possession getPossessionACorriger(T possession); + + Possession getPossessionACorrigerNegativement(T possession); } diff --git a/src/main/java/school/hei/patrimoine/modele/recouppement/generateur/info/InfoGetterBase.java b/src/main/java/school/hei/patrimoine/modele/recouppement/generateur/info/InfoGetterBase.java index 9c7f3206e..e56baf7e4 100644 --- a/src/main/java/school/hei/patrimoine/modele/recouppement/generateur/info/InfoGetterBase.java +++ b/src/main/java/school/hei/patrimoine/modele/recouppement/generateur/info/InfoGetterBase.java @@ -35,4 +35,9 @@ public LocalDate getDate(T possession) { public Argent getValeur(T possession) { return possession.valeurComptable(); } + + @Override + public Possession getPossessionACorrigerNegativement(T possession) { + return null; + } } diff --git a/src/main/java/school/hei/patrimoine/modele/recouppement/generateur/info/InfoGetterFacade.java b/src/main/java/school/hei/patrimoine/modele/recouppement/generateur/info/InfoGetterFacade.java index e93cdb4ee..745333e53 100644 --- a/src/main/java/school/hei/patrimoine/modele/recouppement/generateur/info/InfoGetterFacade.java +++ b/src/main/java/school/hei/patrimoine/modele/recouppement/generateur/info/InfoGetterFacade.java @@ -2,8 +2,7 @@ import lombok.Builder; import lombok.RequiredArgsConstructor; -import school.hei.patrimoine.modele.possession.FluxArgent; -import school.hei.patrimoine.modele.possession.Possession; +import school.hei.patrimoine.modele.possession.*; import school.hei.patrimoine.modele.recouppement.model.CompteGetter; import school.hei.patrimoine.modele.recouppement.model.Info; @@ -20,7 +19,11 @@ public Info get(T possession) { @SuppressWarnings("all") private InfoGetter getInfoGetter(T possession) { return switch (possession) { - case FluxArgent flux -> (InfoGetter) new FluxArgentInfoGetter(compteGetter); + case FluxArgent ignored -> (InfoGetter) new FluxArgentInfoGetter(compteGetter); + case TransfertArgent ignored -> (InfoGetter) new TransfertArgentInfoGetter(compteGetter); + case Creance ignored -> (InfoGetter) new CompteInfoGetter(compteGetter); + case Dette ignored -> (InfoGetter) new CompteInfoGetter(compteGetter); + case Compte ignored -> (InfoGetter) new CompteInfoGetter(compteGetter); default -> new InfoGetterBase<>(); }; } diff --git a/src/main/java/school/hei/patrimoine/modele/recouppement/generateur/info/TransfertArgentInfoGetter.java b/src/main/java/school/hei/patrimoine/modele/recouppement/generateur/info/TransfertArgentInfoGetter.java new file mode 100644 index 000000000..579106a3a --- /dev/null +++ b/src/main/java/school/hei/patrimoine/modele/recouppement/generateur/info/TransfertArgentInfoGetter.java @@ -0,0 +1,27 @@ +package school.hei.patrimoine.modele.recouppement.generateur.info; + +import lombok.RequiredArgsConstructor; +import school.hei.patrimoine.modele.Argent; +import school.hei.patrimoine.modele.possession.Compte; +import school.hei.patrimoine.modele.possession.TransfertArgent; +import school.hei.patrimoine.modele.recouppement.model.CompteGetter; + +@RequiredArgsConstructor +public class TransfertArgentInfoGetter extends InfoGetterBase { + private final CompteGetter compteGetter; + + @Override + public Argent getValeur(TransfertArgent possession) { + return possession.getFluxMensuel(); + } + + @Override + public Compte getPossessionACorriger(TransfertArgent possession) { + return compteGetter.apply(possession.getVersCompte().nom()); + } + + @Override + public Compte getPossessionACorrigerNegativement(TransfertArgent possession) { + return compteGetter.apply(possession.getDepuisCompte().nom()); + } +} diff --git a/src/main/java/school/hei/patrimoine/modele/recouppement/model/CompteGetter.java b/src/main/java/school/hei/patrimoine/modele/recouppement/model/CompteGetter.java index 224d576d9..c0ca7897d 100644 --- a/src/main/java/school/hei/patrimoine/modele/recouppement/model/CompteGetter.java +++ b/src/main/java/school/hei/patrimoine/modele/recouppement/model/CompteGetter.java @@ -1,21 +1,31 @@ package school.hei.patrimoine.modele.recouppement.model; +import static java.time.LocalDate.now; import static java.util.stream.Collectors.toSet; +import static school.hei.patrimoine.modele.Argent.ariary; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.function.Function; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import school.hei.patrimoine.cas.Cas; import school.hei.patrimoine.cas.CasSet; import school.hei.patrimoine.modele.possession.Compte; +@Slf4j @RequiredArgsConstructor public class CompteGetter implements Function { private final Map comptes; + private final boolean throwIfNotFound; public CompteGetter(Set comptes) { + this(comptes, true); + } + + public CompteGetter(Set comptes, boolean throwIfNotFound) { + this.throwIfNotFound = throwIfNotFound; this.comptes = new HashMap<>(); for (var compte : comptes) { this.comptes.put(compte.nom(), compte); @@ -27,8 +37,15 @@ public Compte apply(String nom) { if (comptes.containsKey(nom)) { return comptes.get(nom); } - throw new IllegalArgumentException( - String.format("%s n'a pas été trouvé lors du recoupement", nom)); + + var message = String.format("%s n'a pas été trouvé lors du recoupement", nom); + if (throwIfNotFound) { + throw new IllegalArgumentException(message); + } + + log.warn(message); + comptes.put(nom, new Compte(nom, now(), ariary(0))); + return comptes.get(nom); } public static CompteGetter make(Cas cas, Set casSetComptes) { @@ -39,7 +56,7 @@ public Compte apply(String nom) { var casCompteGetter = make(cas); return casCompteGetter.apply(nom); } catch (IllegalArgumentException exception) { - var casSetCompteGetter = new CompteGetter(casSetComptes); + var casSetCompteGetter = new CompteGetter(casSetComptes, false); return casSetCompteGetter.apply(nom); } } diff --git a/src/main/java/school/hei/patrimoine/modele/recouppement/model/Info.java b/src/main/java/school/hei/patrimoine/modele/recouppement/model/Info.java index 5cdfa7301..e0b61a99e 100644 --- a/src/main/java/school/hei/patrimoine/modele/recouppement/model/Info.java +++ b/src/main/java/school/hei/patrimoine/modele/recouppement/model/Info.java @@ -9,12 +9,22 @@ @Builder public record Info( - String nom, LocalDate t, Argent valeur, T possession, Possession possessionACorriger) { + String nom, + LocalDate t, + Argent valeur, + T possession, + Possession possessionACorriger, + Possession possessionACorrigerNegativement) { public static Info empty() { - return new Info<>("", LocalDate.MIN, ariary(0), null, null); + return new Info<>("", LocalDate.MIN, ariary(0), null, null, null); } public boolean isEmpty() { return nom.isEmpty() || possession == null; } + + public Info( + String nom, LocalDate t, Argent valeur, T possession, Possession possessionACorriger) { + this(nom, t, valeur, possession, possessionACorriger, null); + } } diff --git a/src/main/java/school/hei/patrimoine/modele/recouppement/model/PossessionRecoupee.java b/src/main/java/school/hei/patrimoine/modele/recouppement/model/PossessionRecoupee.java index 6f411d00f..c61ca7951 100644 --- a/src/main/java/school/hei/patrimoine/modele/recouppement/model/PossessionRecoupee.java +++ b/src/main/java/school/hei/patrimoine/modele/recouppement/model/PossessionRecoupee.java @@ -1,7 +1,7 @@ package school.hei.patrimoine.modele.recouppement.model; +import static java.time.LocalDate.now; import static school.hei.patrimoine.modele.Argent.ariary; -import static school.hei.patrimoine.modele.recouppement.model.RecoupementStatus.NON_EXECUTE; import java.util.Set; import lombok.Builder; @@ -21,12 +21,10 @@ public Argent valeurRealisee() { return somme; } - public boolean hasSupportingDocument() { - return !status.equals(NON_EXECUTE); - } - public Argent ecartValeurAvecRealises() { - return valeurRealisee().minus(prevu.valeur(), prevu.t()); + var valeurPrevu = prevu.isEmpty() ? ariary(0) : prevu.valeur(); + var tPrevu = prevu.isEmpty() ? now() : prevu.t(); + return valeurRealisee().minus(valeurPrevu, tPrevu); } public Possession possession() { diff --git a/src/main/java/school/hei/patrimoine/patrilang/generator/IdPatriLangGenerator.java b/src/main/java/school/hei/patrimoine/patrilang/generator/IdPatriLangGenerator.java index ff848ebed..045e36a19 100644 --- a/src/main/java/school/hei/patrimoine/patrilang/generator/IdPatriLangGenerator.java +++ b/src/main/java/school/hei/patrimoine/patrilang/generator/IdPatriLangGenerator.java @@ -1,8 +1,10 @@ package school.hei.patrimoine.patrilang.generator; +import static school.hei.patrimoine.modele.normalizer.PossessionNomNormalizer.normalize; + public class IdPatriLangGenerator implements PatriLangGenerator { @Override public String apply(String s) { - return s.trim().replaceAll("\\s+", "_"); + return normalize(s); } } diff --git a/src/main/java/school/hei/patrimoine/patrilang/generator/PatriLangGeneratorFactory.java b/src/main/java/school/hei/patrimoine/patrilang/generator/PatriLangGeneratorFactory.java index 485523e43..c09e4bc0d 100644 --- a/src/main/java/school/hei/patrimoine/patrilang/generator/PatriLangGeneratorFactory.java +++ b/src/main/java/school/hei/patrimoine/patrilang/generator/PatriLangGeneratorFactory.java @@ -1,7 +1,11 @@ package school.hei.patrimoine.patrilang.generator; import school.hei.patrimoine.modele.possession.FluxArgent; +import school.hei.patrimoine.modele.possession.TransfertArgent; +import school.hei.patrimoine.modele.possession.pj.PieceJustificative; import school.hei.patrimoine.patrilang.generator.possession.FluxArgentPatriLangGenerator; +import school.hei.patrimoine.patrilang.generator.possession.PieceJustificativePatriLangGenerator; +import school.hei.patrimoine.patrilang.generator.possession.TransfertArgentPatriLangGenerator; @SuppressWarnings("unchecked") public class PatriLangGeneratorFactory { @@ -10,6 +14,14 @@ public static PatriLangGenerator make(T data) { return (PatriLangGenerator) new FluxArgentPatriLangGenerator(); } + if (data instanceof TransfertArgent) { + return (PatriLangGenerator) new TransfertArgentPatriLangGenerator(); + } + + if (data instanceof PieceJustificative) { + return (PatriLangGenerator) new PieceJustificativePatriLangGenerator(); + } + throw new IllegalArgumentException("Not Supported Yet"); } } diff --git a/src/main/java/school/hei/patrimoine/patrilang/generator/possession/CommentPatriLangGenerator.java b/src/main/java/school/hei/patrimoine/patrilang/generator/possession/CommentPatriLangGenerator.java new file mode 100644 index 000000000..35f818b19 --- /dev/null +++ b/src/main/java/school/hei/patrimoine/patrilang/generator/possession/CommentPatriLangGenerator.java @@ -0,0 +1,10 @@ +package school.hei.patrimoine.patrilang.generator.possession; + +import school.hei.patrimoine.patrilang.generator.PatriLangGenerator; + +public class CommentPatriLangGenerator implements PatriLangGenerator { + @Override + public String apply(String content) { + return String.format("`/* %s */`", content); + } +} diff --git a/src/main/java/school/hei/patrimoine/patrilang/generator/possession/ComptePatriLangGenerator.java b/src/main/java/school/hei/patrimoine/patrilang/generator/possession/ComptePatriLangGenerator.java new file mode 100644 index 000000000..3d4f9ec95 --- /dev/null +++ b/src/main/java/school/hei/patrimoine/patrilang/generator/possession/ComptePatriLangGenerator.java @@ -0,0 +1,22 @@ +package school.hei.patrimoine.patrilang.generator.possession; + +import static school.hei.patrimoine.modele.normalizer.PossessionNomNormalizer.normalize; + +import school.hei.patrimoine.modele.possession.Compte; +import school.hei.patrimoine.patrilang.generator.PatriLangGenerator; +import school.hei.patrimoine.patrilang.generator.VariableTypePatriLangGenerator; + +public class ComptePatriLangGenerator implements PatriLangGenerator { + private final VariableTypePatriLangGenerator variableTypeGenerator; + + public ComptePatriLangGenerator() { + this.variableTypeGenerator = new VariableTypePatriLangGenerator(); + } + + @Override + public String apply(Compte compte) { + var nom = normalize(compte.nom()); + var type = variableTypeGenerator.apply(compte); + return String.format("%s:%s", type, nom); + } +} diff --git a/src/main/java/school/hei/patrimoine/patrilang/generator/possession/FluxArgentPatriLangGenerator.java b/src/main/java/school/hei/patrimoine/patrilang/generator/possession/FluxArgentPatriLangGenerator.java index a3308f3e0..049ba4270 100644 --- a/src/main/java/school/hei/patrimoine/patrilang/generator/possession/FluxArgentPatriLangGenerator.java +++ b/src/main/java/school/hei/patrimoine/patrilang/generator/possession/FluxArgentPatriLangGenerator.java @@ -6,14 +6,14 @@ public class FluxArgentPatriLangGenerator implements PatriLangGenerator { private final IdPatriLangGenerator idGenerator; private final DatePatriLangGenerator dateGenerator; + private final ComptePatriLangGenerator compteGenerator; private final ArgentPatriLangGenerator argentGenerator; - private final VariableTypePatriLangGenerator variableTypeGenerator; public FluxArgentPatriLangGenerator() { this.idGenerator = new IdPatriLangGenerator(); this.dateGenerator = new DatePatriLangGenerator(); this.argentGenerator = new ArgentPatriLangGenerator(); - this.variableTypeGenerator = new VariableTypePatriLangGenerator(); + this.compteGenerator = new ComptePatriLangGenerator(); } @Override @@ -29,19 +29,17 @@ private String sortir(FluxArgent fluxArgent) { var nom = idGenerator.apply(fluxArgent.nom()); var date = dateGenerator.apply(fluxArgent.t()); var argent = argentGenerator.apply(fluxArgent.getFluxMensuel().mult(-1)); - var type = variableTypeGenerator.apply(fluxArgent.getCompte()); - var compte = fluxArgent.getCompte().nom(); + var compte = compteGenerator.apply(fluxArgent.getCompte()); - return String.format("* `%s`, %s sortir %s depuis %s:%s", nom, date, argent, type, compte); + return String.format("* `%s`, %s sortir %s depuis %s", nom, date, argent, compte); } private String entrer(FluxArgent fluxArgent) { - var nom = fluxArgent.nom(); - var compte = fluxArgent.getCompte().nom(); + var nom = idGenerator.apply(fluxArgent.nom()); var date = dateGenerator.apply(fluxArgent.t()); var argent = argentGenerator.apply(fluxArgent.getFluxMensuel()); - var type = variableTypeGenerator.apply(fluxArgent.getCompte()); + var compte = compteGenerator.apply(fluxArgent.getCompte()); - return String.format("* `%s`, %s entrer %s vers %s:%s", nom, date, argent, type, compte); + return String.format("* `%s`, %s entrer %s vers %s", nom, date, argent, compte); } } diff --git a/src/main/java/school/hei/patrimoine/patrilang/generator/possession/PieceJustificativePatriLangGenerator.java b/src/main/java/school/hei/patrimoine/patrilang/generator/possession/PieceJustificativePatriLangGenerator.java new file mode 100644 index 000000000..047584f37 --- /dev/null +++ b/src/main/java/school/hei/patrimoine/patrilang/generator/possession/PieceJustificativePatriLangGenerator.java @@ -0,0 +1,28 @@ +package school.hei.patrimoine.patrilang.generator.possession; + +import static school.hei.patrimoine.modele.normalizer.PossessionNomNormalizer.normalize; + +import school.hei.patrimoine.modele.possession.pj.PieceJustificative; +import school.hei.patrimoine.patrilang.generator.DatePatriLangGenerator; +import school.hei.patrimoine.patrilang.generator.IdPatriLangGenerator; +import school.hei.patrimoine.patrilang.generator.PatriLangGenerator; + +public class PieceJustificativePatriLangGenerator + implements PatriLangGenerator { + private final IdPatriLangGenerator idGenerator; + private final DatePatriLangGenerator dateGenerator; + + public PieceJustificativePatriLangGenerator() { + this.idGenerator = new IdPatriLangGenerator(); + this.dateGenerator = new DatePatriLangGenerator(); + } + + @Override + public String apply(PieceJustificative pj) { + var id = this.idGenerator.apply(pj.id()); + var date = this.dateGenerator.apply(pj.date()); + var ref = normalize(pj.reference()); + + return String.format("* `%s`, %s, %s, \"%s\"", id, date, ref, pj.link()); + } +} diff --git a/src/main/java/school/hei/patrimoine/patrilang/generator/possession/TransfertArgentPatriLangGenerator.java b/src/main/java/school/hei/patrimoine/patrilang/generator/possession/TransfertArgentPatriLangGenerator.java new file mode 100644 index 000000000..cc339f527 --- /dev/null +++ b/src/main/java/school/hei/patrimoine/patrilang/generator/possession/TransfertArgentPatriLangGenerator.java @@ -0,0 +1,30 @@ +package school.hei.patrimoine.patrilang.generator.possession; + +import school.hei.patrimoine.modele.possession.TransfertArgent; +import school.hei.patrimoine.patrilang.generator.*; + +public class TransfertArgentPatriLangGenerator implements PatriLangGenerator { + private final IdPatriLangGenerator idGenerator; + private final DatePatriLangGenerator dateGenerator; + private final ArgentPatriLangGenerator argentGenerator; + private final ComptePatriLangGenerator compteGenerator; + + public TransfertArgentPatriLangGenerator() { + this.idGenerator = new IdPatriLangGenerator(); + this.dateGenerator = new DatePatriLangGenerator(); + this.argentGenerator = new ArgentPatriLangGenerator(); + this.compteGenerator = new ComptePatriLangGenerator(); + } + + @Override + public String apply(TransfertArgent transfertArgent) { + var nom = idGenerator.apply(transfertArgent.nom()); + var date = dateGenerator.apply(transfertArgent.t()); + var argent = argentGenerator.apply(transfertArgent.getFluxMensuel()); + var depuisCompte = compteGenerator.apply(transfertArgent.getDepuisCompte()); + var versCompte = compteGenerator.apply(transfertArgent.getVersCompte()); + + return String.format( + "* `%s`, %s transférer %s depuis %s vers %s", nom, date, argent, depuisCompte, versCompte); + } +} diff --git a/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/component/appbar/builtin/CasSetAnalyzerButton.java b/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/component/appbar/builtin/CasSetAnalyzerButton.java index 12cc3928e..55dae0104 100644 --- a/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/component/appbar/builtin/CasSetAnalyzerButton.java +++ b/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/component/appbar/builtin/CasSetAnalyzerButton.java @@ -1,5 +1,6 @@ package school.hei.patrimoine.visualisation.swing.ihm.google.component.appbar.builtin; +import static java.util.stream.Collectors.toSet; import static javax.swing.SwingUtilities.invokeLater; import static javax.swing.WindowConstants.DISPOSE_ON_CLOSE; import static school.hei.patrimoine.visualisation.swing.ihm.google.modele.MessageDialog.showError; @@ -8,6 +9,7 @@ import java.time.LocalDate; import java.util.List; import javax.swing.JMenuItem; +import lombok.extern.slf4j.Slf4j; import school.hei.patrimoine.cas.CasSet; import school.hei.patrimoine.cas.CasSetAnalyzer; import school.hei.patrimoine.modele.objectif.ObjectifExeption; @@ -18,6 +20,7 @@ import school.hei.patrimoine.visualisation.swing.ihm.google.modele.AsyncTask; import school.hei.patrimoine.visualisation.swing.ihm.google.modele.files.PatriLangFilesWatcher; +@Slf4j public class CasSetAnalyzerButton extends PopupMenuButton { public CasSetAnalyzerButton() { super("Évolution graphique", getItems()); @@ -61,8 +64,22 @@ private static void showRecouped() { PatriLangFilesWatcher.getPlannedCasSet(), PatriLangFilesWatcher.getDoneCasSet()) .getRecouped()) - .onSuccess(result -> new CasSetAnalyzer(DISPOSE_ON_CLOSE).accept(result)) - .onError(CasSetAnalyzerButton::handleError) + .onSuccess(result -> new CasSetAnalyzer(DISPOSE_ON_CLOSE, false).accept(result)) + .onError( + error -> + handleError( + error, + List.of("Tout"), + () -> { + var recouped = + RecoupeurDeCasSet.of( + LocalDate.MIN, + LocalDate.MAX, + PatriLangFilesWatcher.getPlannedCasSet(), + PatriLangFilesWatcher.getDoneCasSet()) + .getRecouped(); + new CasSetAnalyzer(DISPOSE_ON_CLOSE, true).accept(recouped); + })) .build() .execute(); } @@ -71,17 +88,34 @@ private static void showPlannedOnly() { AsyncTask.builder() .logError(false) .task(PatriLangFilesWatcher::getPlannedCasSet) - .onSuccess(result -> new CasSetAnalyzer(DISPOSE_ON_CLOSE).accept(result)) + .onSuccess(result -> new CasSetAnalyzer(DISPOSE_ON_CLOSE, false).accept(result)) .onError(CasSetAnalyzerButton::handleError) .build() .execute(); } private static void handleError(Exception error) { + handleError(error, List.of(), () -> {}); + } + + private static void handleError(Exception error, List excludedNames, Runnable rerun) { error = error.getCause() == null ? error : (Exception) error.getCause(); if (error instanceof ObjectifExeption exception) { var objectifs = exception.getObjectifNonAtteints(); - invokeLater(() -> new ObjectifNonAtteintsDialog(objectifs)); + + var objectifsFiltrés = + objectifs.stream() + .filter(objectif -> !excludedNames.contains(objectif.objectivable().nom())) + .collect(toSet()); + if (!objectifsFiltrés.isEmpty()) { + invokeLater(() -> new ObjectifNonAtteintsDialog(objectifsFiltrés)); + return; + } + + if (!excludedNames.isEmpty()) { + rerun.run(); + } + return; } showError(error); diff --git a/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/component/recoupement/AddImprevuDialog.java b/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/component/recoupement/AddImprevuDialog.java index 0d8e4901b..2cb2bc968 100644 --- a/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/component/recoupement/AddImprevuDialog.java +++ b/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/component/recoupement/AddImprevuDialog.java @@ -1,34 +1,39 @@ package school.hei.patrimoine.visualisation.swing.ihm.google.component.recoupement; -import static java.util.Objects.requireNonNull; import static school.hei.patrimoine.visualisation.swing.ihm.google.component.files.FileSideBar.getSelectedFile; +import static school.hei.patrimoine.visualisation.swing.ihm.google.component.recoupement.PossessionRecoupeeRealisationsDialog.getSectionOperations; +import static school.hei.patrimoine.visualisation.swing.ihm.google.component.recoupement.PossessionRecoupeeRealisationsDialog.getSectionPieceJustificatives; import static school.hei.patrimoine.visualisation.swing.ihm.google.modele.MessageDialog.*; import static school.hei.patrimoine.visualisation.swing.ihm.google.modele.files.PatriLangFileContentManager.clearAllTempContents; -import static school.hei.patrimoine.visualisation.swing.ihm.google.modele.files.PatriLangFilesWatcher.getDoneCasSetComptes; +import static school.hei.patrimoine.visualisation.swing.ihm.google.modele.files.PatriLangFilesWatcher.*; import static school.hei.patrimoine.visualisation.swing.ihm.google.modele.files.PatriLangStagingFileManager.stage; import static school.hei.patrimoine.visualisation.swing.ihm.google.providers.FilesProvider.getDoneCasSetFile; +import static school.hei.patrimoine.visualisation.swing.ihm.google.providers.FilesProvider.getPJ; import java.awt.*; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; +import java.util.*; +import java.util.List; import javax.swing.*; import javax.swing.border.EmptyBorder; +import lombok.Getter; import org.jetbrains.annotations.NotNull; import school.hei.patrimoine.Pair; import school.hei.patrimoine.modele.Argent; import school.hei.patrimoine.modele.Patrimoine; import school.hei.patrimoine.modele.possession.Compte; -import school.hei.patrimoine.modele.possession.FluxArgent; -import school.hei.patrimoine.patrilang.files.io.PatriLangFileQuerier; +import school.hei.patrimoine.modele.possession.Possession; import school.hei.patrimoine.patrilang.files.io.PatriLangFileWriter; import school.hei.patrimoine.patrilang.files.io.PatriLangFileWriter.FileWriterInput; import school.hei.patrimoine.patrilang.generator.PatriLangGeneratorFactory; +import school.hei.patrimoine.patrilang.generator.possession.CommentPatriLangGenerator; +import school.hei.patrimoine.patrilang.generator.possession.PieceJustificativePatriLangGenerator; import school.hei.patrimoine.patrilang.modele.PatriLangCas; import school.hei.patrimoine.visualisation.swing.ihm.google.component.Dialog; import school.hei.patrimoine.visualisation.swing.ihm.google.component.button.Button; +import school.hei.patrimoine.visualisation.swing.ihm.google.generator.possession.ExecutionGenerator; import school.hei.patrimoine.visualisation.swing.ihm.google.generator.possession.FluxArgentExecutionGenerator; +import school.hei.patrimoine.visualisation.swing.ihm.google.generator.possession.PieceJustificativeGenerator; +import school.hei.patrimoine.visualisation.swing.ihm.google.generator.possession.TransfertArgentExecutionGenerator; import school.hei.patrimoine.visualisation.swing.ihm.google.modele.AsyncTask; import school.hei.patrimoine.visualisation.swing.ihm.google.modele.MessageDialog; import school.hei.patrimoine.visualisation.swing.ihm.google.modele.State; @@ -36,18 +41,20 @@ public class AddImprevuDialog extends Dialog { private final State state; - private final PatriLangFileWriter writer; - private final PatriLangFileQuerier querier; + private JScrollPane scrollPane; private AddRecoupementExecutionForm form; - private final JComboBox compteSelect; + private final JComboBox operationTypeSelect; + + private JComboBox compteSelect; + private JComboBox depuisCompteSelect; + private JComboBox versCompteSelect; public AddImprevuDialog(State state) { super("Ajouter un imprévu", 800, 900, false); this.state = state; - this.querier = new PatriLangFileQuerier(); - this.writer = new PatriLangFileWriter(); - this.compteSelect = getCompteSelect(); + this.operationTypeSelect = new JComboBox<>(OperationType.values()); + operationTypeSelect.addActionListener(e -> rebuildForm()); setLayout(new BorderLayout()); @@ -58,16 +65,56 @@ public AddImprevuDialog(State state) { setVisible(true); } + private void initComptes() { + this.compteSelect = getCompteSelect(); + this.depuisCompteSelect = getCompteSelect(); + this.versCompteSelect = getCompteSelect(); + } + + private List> buildInputs() { + List> inputs = new ArrayList<>(); + + switch (getSelectedType()) { + case FLUX_ARGENT -> inputs.add(Pair.of("Compte :", compteSelect)); + case TRANSFERT_ARGENT -> { + inputs.add(Pair.of("Compte à débiter :", depuisCompteSelect)); + inputs.add(Pair.of("Compte à créditer :", versCompteSelect)); + } + } + + return inputs; + } + private void addForm() { var patrimoine = getDonePatrimoine(); + initComptes(); + this.form = new AddRecoupementExecutionForm( "", patrimoine.getDevise(), new Argent(0, patrimoine.getDevise()), - Set.of(Pair.of("Compte : ", compteSelect))); + List.of(Pair.of("Type d'opération financière : ", operationTypeSelect)), + buildInputs(), + true); + + this.scrollPane = new JScrollPane(form); + + scrollPane.setBorder(null); + scrollPane.getVerticalScrollBar().setUnitIncrement(16); + add(scrollPane, BorderLayout.CENTER); + } + + private void rebuildForm() { + remove(scrollPane); + addForm(); - add(form, BorderLayout.CENTER); + revalidate(); + repaint(); + } + + private OperationType getSelectedType() { + return (OperationType) operationTypeSelect.getSelectedItem(); } private Patrimoine getDonePatrimoine() { @@ -120,36 +167,81 @@ private JComboBox getCompteSelect() { return new JComboBox<>(array); } - private void addExecution() { - var generator = new FluxArgentExecutionGenerator(); - var newPossession = generator.apply(getFluxArgentArgs()); - saveExecutions(newPossession); + private ExecutionGenerator getExecutionGenerator() { + return switch (getSelectedType()) { + case FLUX_ARGENT -> new FluxArgentExecutionGenerator(); + case TRANSFERT_ARGENT -> new TransfertArgentExecutionGenerator(); + }; } - private void saveExecutions(FluxArgent fluxArgent) { - var lineGenerator = PatriLangGeneratorFactory.make(fluxArgent); - var line = lineGenerator.apply(fluxArgent); + private String getCasLine(Possession possession) { + var lineGenerator = PatriLangGeneratorFactory.make(possession); + var line = lineGenerator.apply(possession); + var rawComment = form.getComment(); + + if (!rawComment.isBlank()) { + var commentGenerator = new CommentPatriLangGenerator(); + line += " " + commentGenerator.apply(rawComment); + } + + return line; + } + + private String getPjLine(Possession possession) { + if (!PJFieldsValidator.hasPJ(form)) { + return ""; + } + + var pj = new PieceJustificativeGenerator().apply(getPjArgs(possession)); + var generator = new PieceJustificativePatriLangGenerator(); + return pj == null ? "" : generator.apply(pj); + } + + private void addExecution() { + PJFieldsValidator.validatePJ(form); var selectedFile = getSelectedFile(state).orElseThrow(); + var generator = getExecutionGenerator(); + var newPossession = generator.apply(getPossessionArgs()); + var casLine = getCasLine(newPossession); + var casSet = getDoneCasSetFile(); + var optionalPjFile = getPJ(selectedFile); AsyncTask.builder() .task( () -> { - var sectionOperation = - querier.query(selectedFile, document -> document.cas().sectionOperations()); - if (sectionOperation.isEmpty()) { - throw new RuntimeException("Section Operations introuvable dans le fichier"); - } + var writer = new PatriLangFileWriter(); + var operations = getSectionOperations(selectedFile); - var input = + writer.insertAtLine( FileWriterInput.builder() - .content(line) + .content(casLine) .file(selectedFile) - .casSet(getDoneCasSetFile()) - .build(); + .casSet(casSet) + .build(), + operations.endLine()); + + if (optionalPjFile.isEmpty()) { + clearAllTempContents(); + stage(selectedFile); + return null; + } + + var pjLines = getPjLine(newPossession); + if (pjLines.isBlank()) { + clearAllTempContents(); + stage(selectedFile); + return null; + } + + var pjFile = optionalPjFile.get(); + var pjs = getSectionPieceJustificatives(pjFile); + writer.insertAtLine( + FileWriterInput.builder().content(pjLines).file(pjFile).casSet(casSet).build(), + pjs.endLine()); - writer.insertAtLine(input, sectionOperation.get().endLine()); clearAllTempContents(); + stage(pjFile); stage(selectedFile); return null; }) @@ -164,16 +256,41 @@ private void saveExecutions(FluxArgent fluxArgent) { .execute(); } - private Map getFluxArgentArgs() { - var named = (NamedCompte) requireNonNull(compteSelect.getSelectedItem()); + private Map getPjArgs(Possession possession) { return Map.of( + "id", possession.nom(), "date", form.getDate(), - "valeur", form.getValeur(), - "nom", form.getNom(), - "compte", named.compte(), - "linkPJ", form.getLinkPJ(), - "referencePJ", form.getReferencePJ(), - "comment", form.getComment()); + "link", form.getLinkPJ(), + "ref", form.getReferencePJ()); + } + + private Map getPossessionArgs() { + Map args = + new HashMap<>( + Map.of( + "nom", form.getNom(), + "date", form.getDate(), + "valeur", form.getValeur())); + + switch (getSelectedType()) { + case FLUX_ARGENT -> args.put("compte", getCompteSelected(compteSelect)); + case TRANSFERT_ARGENT -> { + args.put("depuisCompte", getCompteSelected(depuisCompteSelect)); + args.put("versCompte", getCompteSelected(versCompteSelect)); + } + default -> throw new IllegalArgumentException("Type non supporté"); + } + return args; + } + + private Compte getCompteSelected(JComboBox select) { + var item = (NamedCompte) select.getSelectedItem(); + if (item == null) { + throw new IllegalArgumentException( + "Aucun compte disponible. Veuillez créer un compte dans le patrimoine."); + } + + return item.compte(); } private record NamedCompte(Compte compte) { @@ -182,4 +299,21 @@ private record NamedCompte(Compte compte) { return compte.nom(); } } + + @Getter + public enum OperationType { + FLUX_ARGENT("Flux d'argent"), + TRANSFERT_ARGENT("Transfert d'argent"); + + private final String label; + + OperationType(String label) { + this.label = label; + } + + @Override + public String toString() { + return label; + } + } } diff --git a/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/component/recoupement/AddRecoupementExecutionDialog.java b/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/component/recoupement/AddRecoupementExecutionDialog.java deleted file mode 100644 index 83fb3e4aa..000000000 --- a/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/component/recoupement/AddRecoupementExecutionDialog.java +++ /dev/null @@ -1,104 +0,0 @@ -package school.hei.patrimoine.visualisation.swing.ihm.google.component.recoupement; - -import static school.hei.patrimoine.visualisation.swing.ihm.google.modele.MessageDialog.showError; - -import java.awt.*; -import java.util.*; -import java.util.function.Consumer; -import javax.swing.*; -import javax.swing.border.EmptyBorder; -import school.hei.patrimoine.modele.possession.FluxArgent; -import school.hei.patrimoine.modele.possession.Possession; -import school.hei.patrimoine.modele.recouppement.model.Info; -import school.hei.patrimoine.modele.recouppement.model.PossessionRecoupee; -import school.hei.patrimoine.visualisation.swing.ihm.google.component.Dialog; -import school.hei.patrimoine.visualisation.swing.ihm.google.component.button.Button; -import school.hei.patrimoine.visualisation.swing.ihm.google.generator.PossessionGeneratorFactory; - -public class AddRecoupementExecutionDialog extends Dialog { - private final AddRecoupementExecutionForm form; - private final PossessionRecoupee possessionRecoupee; - private final Consumer> onAddConsumer; - - public AddRecoupementExecutionDialog( - PossessionRecoupee possessionRecoupee, Consumer> onAddConsumer) { - super("Exécuter la possession", 700, 600, false); - this.onAddConsumer = onAddConsumer; - this.possessionRecoupee = possessionRecoupee; - this.form = - new AddRecoupementExecutionForm( - possessionRecoupee.possession().nom(), - possessionRecoupee.possession().devise(), - possessionRecoupee.info().valeur()); - - setLayout(new BorderLayout()); - setBackground(Color.WHITE); - - addTitle(); - add(form, BorderLayout.CENTER); - addButtons(); - - setVisible(true); - } - - private void addTitle() { - var titleString = - String.format( - "Ajout d'exécution de l'opération : %s", possessionRecoupee.possession().nom()); - var title = new JLabel(titleString); - title.setFont(new Font("Arial", Font.BOLD, 16)); - title.setBorder(new EmptyBorder(10, 10, 10, 10)); - title.setBackground(Color.WHITE); - add(title, BorderLayout.NORTH); - } - - private void addButtons() { - var buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); - buttonPanel.setOpaque(true); - buttonPanel.setBorder(new EmptyBorder(10, 10, 10, 10)); - - buttonPanel.add( - new Button( - "Ajouter", - e -> { - try { - addExecution(); - dispose(); - } catch (IllegalArgumentException error) { - showError("Erreur", error.getMessage()); - } - })); - - buttonPanel.add(new Button("Annuler", e -> dispose())); - add(buttonPanel, BorderLayout.SOUTH); - } - - private void addExecution() { - var args = getArgsFacade(); - var generator = PossessionGeneratorFactory.make(possessionRecoupee.possession()); - var newPossession = generator.apply(args); - - // TODO: fix possession à corrigé - onAddConsumer.accept( - new Info<>( - newPossession.nom(), form.getDate(), form.getValeur(), newPossession, newPossession)); - } - - private Map getArgsFacade() { - return switch (possessionRecoupee.possession()) { - case FluxArgent ignored -> getFluxArgentArgs(); - default -> throw new IllegalArgumentException("Not supported yet"); - }; - } - - private Map getFluxArgentArgs() { - var possession = (FluxArgent) possessionRecoupee.possession(); - - return Map.of( - "date", form.getDate(), - "valeur", form.getValeur(), - "nom", form.getNom(), - "compte", possession.getCompte(), - "prevu", possessionRecoupee.prevu().possession()); - } -} diff --git a/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/component/recoupement/AddRecoupementExecutionForm.java b/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/component/recoupement/AddRecoupementExecutionForm.java index f66f6d2fd..cc0897888 100644 --- a/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/component/recoupement/AddRecoupementExecutionForm.java +++ b/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/component/recoupement/AddRecoupementExecutionForm.java @@ -5,8 +5,9 @@ import static school.hei.patrimoine.modele.Devise.*; import java.awt.*; +import java.time.DateTimeException; import java.time.LocalDate; -import java.util.Set; +import java.util.List; import javax.swing.*; import javax.swing.border.EmptyBorder; import school.hei.patrimoine.Pair; @@ -26,55 +27,58 @@ public class AddRecoupementExecutionForm extends JPanel { private JTextField linkPJField; private JTextField referencePJField; private JTextField commentField; + private final boolean authorizePj; public AddRecoupementExecutionForm( String defaultNom, Devise defaultDevise, Argent defaultValeur, - Set> components, + List> startComponents, + List> endComponents, String defaultLinkPJ, - String defaultReferencePJ, - String defaultComment) { + String defaultComment, + boolean authorizePj) { + this.authorizePj = authorizePj; setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); setOpaque(true); setBorder(new EmptyBorder(15, 15, 15, 15)); setBackground(Color.WHITE); + startComponents.forEach(component -> addField(component.first(), component.second())); addNomField(defaultNom); addDateField(LocalDate.now()); addValeurField(defaultValeur); addDeviseField(defaultDevise); + endComponents.forEach(component -> addField(component.first(), component.second())); + addLinkPJField(defaultLinkPJ); - addReferencePJField(buildDefaultRef(defaultNom)); + addReferencePJField(defaultNom); addCommentField(defaultComment); - - components.forEach(component -> addField(component.first(), component.second())); - } - - private String buildDefaultRef(String defaultNom) { - var prefix = defaultNom.trim().substring(0, Math.min(3, defaultNom.length())).toUpperCase(); - - if (defaultNom.contains("__du_")) { - var datePart = defaultNom.replaceAll("^.*__du_", "").replaceAll("_", "-"); - - return prefix + datePart; - } - return prefix + LocalDate.now().getYear(); } public AddRecoupementExecutionForm( String defaultNom, Devise defaultDevise, Argent defaultValeur, - Set> components) { - this(defaultNom, defaultDevise, defaultValeur, components, "", "", ""); + List> startComponents, + List> endComponents, + boolean authorizePj) { + this( + defaultNom, + defaultDevise, + defaultValeur, + startComponents, + endComponents, + "", + "", + authorizePj); } public AddRecoupementExecutionForm( - String defaultNom, Devise defaultDevise, Argent defaultValeur) { - this(defaultNom, defaultDevise, defaultValeur, Set.of(), "", "", ""); + String defaultNom, Devise defaultDevise, Argent defaultValeur, boolean authorizePj) { + this(defaultNom, defaultDevise, defaultValeur, List.of(), List.of(), "", "", authorizePj); } public Argent getValeur() { @@ -88,18 +92,23 @@ public Argent getValeur() { } public LocalDate getDate() { - return LocalDate.of( - executionDatePicker.getModel().getYear(), - executionDatePicker.getModel().getMonth() + 1, - executionDatePicker.getModel().getDay()); + var model = executionDatePicker.getModel(); + if (model.getValue() == null) { + throw new IllegalArgumentException("La date est obligatoire."); + } + try { + return LocalDate.of(model.getYear(), model.getMonth() + 1, model.getDay()); + } catch (DateTimeException e) { + throw new IllegalArgumentException("La date saisie est invalide : " + e.getMessage()); + } } public String getNom() { - if (nomField.getText().trim().isBlank()) { + var normalized = nomField.getText().trim().replaceAll("[- ]+", "_"); + if (normalized.isBlank()) { throw new IllegalArgumentException("Le champ 'Nom' est obligatoire."); } - - return nomField.getText().trim().replaceAll(" ", "_"); + return normalized; } public String getLinkPJ() { @@ -146,12 +155,16 @@ private void addDeviseField(Devise defaultDevise) { private void addLinkPJField(String defaultLinkPJ) { linkPJField = new JTextField(defaultLinkPJ == null ? "" : defaultLinkPJ); - addField("Lien de la pièce justificative : ", linkPJField); + if (authorizePj) { + addField("Lien de la pièce justificative : ", linkPJField); + } } private void addReferencePJField(String defaultReferencePJ) { referencePJField = new JTextField(defaultReferencePJ == null ? "" : defaultReferencePJ); - addField("Référence de la pièce justificative : ", referencePJField); + if (authorizePj) { + addField("Référence de la pièce justificative : ", referencePJField); + } } private void addCommentField(String defaultComment) { diff --git a/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/component/recoupement/PJFieldsValidator.java b/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/component/recoupement/PJFieldsValidator.java new file mode 100644 index 000000000..3da0156ba --- /dev/null +++ b/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/component/recoupement/PJFieldsValidator.java @@ -0,0 +1,37 @@ +package school.hei.patrimoine.visualisation.swing.ihm.google.component.recoupement; + +public class PJFieldsValidator { + private PJFieldsValidator() {} + + public static boolean hasLinkPJ(AddRecoupementExecutionForm form) { + var linkPJ = form.getLinkPJ(); + return linkPJ != null && !linkPJ.isBlank(); + } + + public static boolean hasReferencePJ(AddRecoupementExecutionForm form) { + var referencePJ = form.getReferencePJ(); + return referencePJ != null && !referencePJ.isBlank(); + } + + public static boolean hasPJ(AddRecoupementExecutionForm form) { + return hasLinkPJ(form) && hasReferencePJ(form); + } + + public static void validatePJ(AddRecoupementExecutionForm form) { + if (hasLinkPJ(form) && !hasReferencePJ(form)) { + throw new IllegalArgumentException( + "La référence de la pièce justificative est manquante." + + " Veuillez renseigner les deux champs ou laisser les deux vides."); + } + if (!hasLinkPJ(form) && hasReferencePJ(form)) { + throw new IllegalArgumentException( + "Le lien de la pièce justificative est manquant." + + " Veuillez renseigner les deux champs ou laisser les deux vides."); + } + + if (hasLinkPJ(form) && form.getLinkPJ().contains("\"")) { + throw new IllegalArgumentException( + "Le lien de la pièce justificative ne doit pas contenir de guillemets (\")."); + } + } +} diff --git a/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/component/recoupement/PossessionRecoupeeDetailDialog.java b/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/component/recoupement/PossessionRecoupeeDetailDialog.java index 541148e0e..b89faf6ed 100644 --- a/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/component/recoupement/PossessionRecoupeeDetailDialog.java +++ b/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/component/recoupement/PossessionRecoupeeDetailDialog.java @@ -1,22 +1,26 @@ package school.hei.patrimoine.visualisation.swing.ihm.google.component.recoupement; import java.awt.*; -import java.net.URI; import javax.swing.*; import javax.swing.border.EmptyBorder; import school.hei.patrimoine.modele.possession.Correction; import school.hei.patrimoine.modele.possession.Possession; +import school.hei.patrimoine.modele.possession.pj.PieceJustificative; import school.hei.patrimoine.modele.recouppement.model.PossessionRecoupee; import school.hei.patrimoine.visualisation.swing.ihm.google.component.Dialog; import school.hei.patrimoine.visualisation.swing.ihm.google.component.button.Button; +import school.hei.patrimoine.visualisation.swing.ihm.google.component.html.LinkOpener; import school.hei.patrimoine.visualisation.swing.ihm.google.modele.formatter.ArgentFormatter; import school.hei.patrimoine.visualisation.swing.ihm.google.modele.formatter.DateFormatter; public class PossessionRecoupeeDetailDialog extends Dialog { + private final PieceJustificative pj; private final PossessionRecoupee possessionRecoupee; - public PossessionRecoupeeDetailDialog(PossessionRecoupee possessionRecoupee) { + public PossessionRecoupeeDetailDialog( + PossessionRecoupee possessionRecoupee, PieceJustificative pj) { super("Détails de l'opération", 1000, 600, false); + this.pj = pj; this.possessionRecoupee = possessionRecoupee; setLayout(new BorderLayout()); @@ -24,7 +28,7 @@ public PossessionRecoupeeDetailDialog(PossessionRecoupee possessionR addMainTitle(); addContentPanel(); - addCloseButton(); + addActions(); setVisible(true); } @@ -75,9 +79,6 @@ private JPanel createInfoPanel() { infoPanel.add(Box.createVerticalStrut(10)); infoPanel.add( makeInfoRow("Écart", ArgentFormatter.format(possessionRecoupee.ecartValeurAvecRealises()))); - infoPanel.add(Box.createVerticalStrut(10)); - infoPanel.add(makeInfoRow("Référence de la pièce", "TES2026-120-20")); - return infoPanel; } @@ -108,17 +109,6 @@ private JPanel createListsPanel() { "Corrections", possessionRecoupee.corrections().stream().map(Correction::nom).toArray(String[]::new), new Color(255, 245, 200))); - - listPanel.add(Box.createVerticalStrut(20)); - - listPanel.add( - createList( - "Commentaires", - possessionRecoupee.corrections().stream() - .flatMap(correction -> correction.toString().lines()) - .toArray(String[]::new), - new Color(255, 245, 200))); - return listPanel; } @@ -142,29 +132,22 @@ private JPanel createList(String titleText, String[] items, Color bgColor) { return panel; } - private void addCloseButton() { + private void addActions() { var buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); buttonPanel.setOpaque(true); buttonPanel.setBorder(new EmptyBorder(10, 10, 10, 10)); - var pjButton = - new Button("Voir la pièce justificative", e -> openLink("https://www.youtube.com/")); - buttonPanel.add(pjButton); + if (pj != null) { + var pjButton = + new Button("Voir la pièce justificative", e -> new LinkOpener().accept(pj.link())); + buttonPanel.add(pjButton); + } buttonPanel.add(new Button("Fermer", e -> dispose())); add(buttonPanel, BorderLayout.SOUTH); } - private void openLink(String url) { - try { - Desktop.getDesktop().browse(new URI(url)); - } catch (Exception e) { - JOptionPane.showMessageDialog( - this, "Impossible d'ouvrir le lien : " + url, "Erreur", JOptionPane.ERROR_MESSAGE); - } - } - private JPanel makeInfoRow(String labelText, String valueText) { var row = new JPanel(new FlowLayout(FlowLayout.LEFT, 10, 5)); row.setBackground(new Color(245, 245, 245)); diff --git a/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/component/recoupement/PossessionRecoupeeItem.java b/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/component/recoupement/PossessionRecoupeeItem.java index 65438d8da..dc5c4a380 100644 --- a/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/component/recoupement/PossessionRecoupeeItem.java +++ b/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/component/recoupement/PossessionRecoupeeItem.java @@ -1,13 +1,17 @@ package school.hei.patrimoine.visualisation.swing.ihm.google.component.recoupement; import static javax.swing.event.HyperlinkEvent.EventType.ACTIVATED; +import static school.hei.patrimoine.modele.recouppement.model.RecoupementStatus.IMPREVU; +import static school.hei.patrimoine.modele.recouppement.model.RecoupementStatus.NON_EXECUTE; import java.awt.*; import javax.swing.*; import javax.swing.border.EmptyBorder; import lombok.extern.slf4j.Slf4j; import org.jspecify.annotations.NonNull; +import school.hei.patrimoine.modele.possession.FluxArgent; import school.hei.patrimoine.modele.possession.Possession; +import school.hei.patrimoine.modele.possession.TransfertArgent; import school.hei.patrimoine.modele.possession.pj.PieceJustificative; import school.hei.patrimoine.modele.recouppement.model.PossessionRecoupee; import school.hei.patrimoine.modele.recouppement.model.RecoupementStatus; @@ -20,16 +24,14 @@ @Slf4j public class PossessionRecoupeeItem extends JPanel { private final State state; - private final PieceJustificative pieceJustificative; + private final PieceJustificative pj; private final PossessionRecoupee possessionRecoupee; public PossessionRecoupeeItem( - State state, - PossessionRecoupee possessionRecoupee, - PieceJustificative pieceJustificative) { + State state, PossessionRecoupee possessionRecoupee, PieceJustificative pj) { this.state = state; this.possessionRecoupee = possessionRecoupee; - this.pieceJustificative = pieceJustificative; + this.pj = pj; setOpaque(true); setLayout(new BorderLayout()); @@ -42,21 +44,21 @@ public PossessionRecoupeeItem( private void addTitle() { var pjHtml = - pieceJustificative == null + pj == null ? "" : "
" + "Pièce justificative: " + "" - + pieceJustificative.id() + + pj.id() + "" + ",   " + "Référence: " - + pieceJustificative.reference() + + pj.reference() + ",   " + "Date d'insertion: " - + DateFormatter.format(pieceJustificative.date()) + + DateFormatter.format(pj.date()) + "
"; var titleString = @@ -102,24 +104,36 @@ private void addTitle() { return title; } + private boolean canBeExecuted() { + var possession = possessionRecoupee.possession(); + if (IMPREVU.equals(possessionRecoupee.status())) { + return false; + } + return possession instanceof FluxArgent || possession instanceof TransfertArgent; + } + + private Button executeButton() { + if (possessionRecoupee.status() == NON_EXECUTE) { + return new Button( + "Exécuter", + e -> new PossessionRecoupeeRealisationsDialog(state, possessionRecoupee, true)); + } + return new Button( + "Exécutions", + e -> new PossessionRecoupeeRealisationsDialog(state, possessionRecoupee, false)); + } + private void addActionsButton() { var panel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); panel.setOpaque(false); - if (possessionRecoupee.status() == RecoupementStatus.NON_EXECUTE) { - panel.add( - new Button( - "Exécuter", - e -> - new PossessionRecoupeeRealisationsDialog( - state, possessionRecoupee, "add-form-view"))); - } else { - panel.add( - new Button( - "Exécutions", - e -> new PossessionRecoupeeRealisationsDialog(state, possessionRecoupee))); + + if (canBeExecuted()) { + panel.add(executeButton()); } + panel.add( - new Button("Voir Details", e -> new PossessionRecoupeeDetailDialog(possessionRecoupee))); + new Button( + "Voir Details", e -> new PossessionRecoupeeDetailDialog(possessionRecoupee, pj))); add(panel, BorderLayout.EAST); } diff --git a/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/component/recoupement/PossessionRecoupeeRealisationsDialog.java b/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/component/recoupement/PossessionRecoupeeRealisationsDialog.java index cfb6cd7dd..18d17ed19 100644 --- a/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/component/recoupement/PossessionRecoupeeRealisationsDialog.java +++ b/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/component/recoupement/PossessionRecoupeeRealisationsDialog.java @@ -1,43 +1,52 @@ package school.hei.patrimoine.visualisation.swing.ihm.google.component.recoupement; import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toSet; import static school.hei.patrimoine.modele.recouppement.model.RecoupementStatus.IMPREVU; import static school.hei.patrimoine.visualisation.swing.ihm.google.component.app.ViewFactory.make; +import static school.hei.patrimoine.visualisation.swing.ihm.google.component.files.FileSideBar.getSelectedFile; import static school.hei.patrimoine.visualisation.swing.ihm.google.modele.MessageDialog.*; +import static school.hei.patrimoine.visualisation.swing.ihm.google.modele.files.PatriLangFileContentManager.clearAllTempContents; +import static school.hei.patrimoine.visualisation.swing.ihm.google.modele.files.PatriLangStagingFileManager.stage; import static school.hei.patrimoine.visualisation.swing.ihm.google.providers.FilesProvider.getDoneCasSetFile; +import static school.hei.patrimoine.visualisation.swing.ihm.google.providers.FilesProvider.getPJ; import java.awt.*; -import java.io.File; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; +import java.util.*; import javax.swing.*; import javax.swing.border.EmptyBorder; +import org.jspecify.annotations.NonNull; import school.hei.patrimoine.modele.possession.FluxArgent; import school.hei.patrimoine.modele.possession.Possession; +import school.hei.patrimoine.modele.possession.TransfertArgent; +import school.hei.patrimoine.modele.possession.pj.PieceJustificative; import school.hei.patrimoine.modele.recouppement.model.Info; import school.hei.patrimoine.modele.recouppement.model.PossessionRecoupee; -import school.hei.patrimoine.patrilang.files.PatriLangFile; +import school.hei.patrimoine.patrilang.antlr.PatriLangParser.SectionOperationsContext; +import school.hei.patrimoine.patrilang.antlr.PatriLangParser.SectionPiecesJustificativesContext; import school.hei.patrimoine.patrilang.files.io.PatriLangFileQuerier; +import school.hei.patrimoine.patrilang.files.io.PatriLangFileQuerier.QueryResult; import school.hei.patrimoine.patrilang.files.io.PatriLangFileWriter; import school.hei.patrimoine.patrilang.files.io.PatriLangFileWriter.FileWriterInput; import school.hei.patrimoine.patrilang.generator.PatriLangGeneratorFactory; +import school.hei.patrimoine.patrilang.generator.possession.CommentPatriLangGenerator; +import school.hei.patrimoine.patrilang.generator.possession.PieceJustificativePatriLangGenerator; import school.hei.patrimoine.visualisation.swing.ihm.google.component.Dialog; import school.hei.patrimoine.visualisation.swing.ihm.google.component.app.MultiViews; import school.hei.patrimoine.visualisation.swing.ihm.google.component.button.Button; import school.hei.patrimoine.visualisation.swing.ihm.google.generator.PossessionGeneratorFactory; +import school.hei.patrimoine.visualisation.swing.ihm.google.generator.possession.PieceJustificativeGenerator; import school.hei.patrimoine.visualisation.swing.ihm.google.modele.AsyncTask; import school.hei.patrimoine.visualisation.swing.ihm.google.modele.MessageDialog; import school.hei.patrimoine.visualisation.swing.ihm.google.modele.State; +import school.hei.patrimoine.visualisation.swing.ihm.google.modele.files.PatriLangFileContext; import school.hei.patrimoine.visualisation.swing.ihm.google.modele.files.PatriLangFilesWatcher; import school.hei.patrimoine.visualisation.swing.ihm.google.modele.formatter.ArgentFormatter; import school.hei.patrimoine.visualisation.swing.ihm.google.modele.formatter.DateFormatter; public class PossessionRecoupeeRealisationsDialog extends Dialog { private final State state; - private final Set> pendingInfos; - private final PatriLangFileWriter writter; - private final PatriLangFileQuerier querier; + private final Set pendingData; private final PossessionRecoupee possessionRecoupee; private DefaultListModel> realisesModel; @@ -52,9 +61,7 @@ public PossessionRecoupeeRealisationsDialog( this.state = state; this.isExecuteMode = isExecuteMode; - this.pendingInfos = new HashSet<>(); - this.writter = new PatriLangFileWriter(); - this.querier = new PatriLangFileQuerier(); + this.pendingData = new HashSet<>(); this.possessionRecoupee = possessionRecoupee; setLayout(new BorderLayout()); @@ -74,16 +81,6 @@ public PossessionRecoupeeRealisationsDialog( setVisible(true); } - public PossessionRecoupeeRealisationsDialog( - State state, PossessionRecoupee possessionRecoupee, String initialView) { - this(state, possessionRecoupee, true); - } - - public PossessionRecoupeeRealisationsDialog( - State state, PossessionRecoupee possessionRecoupee) { - this(state, possessionRecoupee, false); - } - private void initPageManager() { var listView = make("list-view", buildListView()); var addFormView = make("add-form-view", buildAddFormView()); @@ -96,21 +93,7 @@ private JPanel buildListView() { realisesModel = new DefaultListModel<>(); possessionRecoupee.realises().forEach(realisesModel::addElement); - var realisesList = new JList<>(realisesModel); - realisesList.setCellRenderer( - (list, value, index, isSelected, cellHasFocus) -> { - var label = - new JLabel( - String.format( - "Date=%s, Valeur=%s, Nom=%s", - "PJ=%, PieceRef=%", - DateFormatter.format(value.t()), - ArgentFormatter.format(value.valeur()), - value.possession().nom())); - label.setBorder(new EmptyBorder(10, 5, 10, 5)); - label.setFont(new Font("Arial", Font.PLAIN, 15)); - return label; - }); + var realisesList = getInfoJList(); var panel = new JPanel(new BorderLayout()); panel.setBorder(new EmptyBorder(10, 10, 10, 10)); @@ -128,12 +111,31 @@ private JPanel buildListView() { return panel; } + private @NonNull JList> getInfoJList() { + var realisesList = new JList<>(realisesModel); + realisesList.setCellRenderer( + (list, value, index, isSelected, cellHasFocus) -> { + var label = + new JLabel( + String.format( + "Date=%s, Valeur=%s, Nom=%s", + DateFormatter.format(value.t()), + ArgentFormatter.format(value.valeur()), + value.possession().nom())); + label.setBorder(new EmptyBorder(10, 5, 10, 5)); + label.setFont(new Font("Arial", Font.PLAIN, 15)); + return label; + }); + return realisesList; + } + private JPanel buildAddFormView() { var form = new AddRecoupementExecutionForm( possessionRecoupee.possession().nom(), possessionRecoupee.possession().devise(), - possessionRecoupee.info().valeur()); + possessionRecoupee.info().valeur(), + possessionRecoupee.realises().isEmpty()); var panel = new JPanel(new BorderLayout()); panel.add(form, BorderLayout.CENTER); @@ -174,23 +176,59 @@ private JPanel buildAddFormView() { return panel; } - private Info buildInfoFromForm(AddRecoupementExecutionForm form) { - var generator = PossessionGeneratorFactory.make(possessionRecoupee.possession()); + private Map getPossessionArgs(AddRecoupementExecutionForm form) { Map args = - switch (possessionRecoupee.possession()) { - case FluxArgent ignored -> - Map.of( - "date", form.getDate(), - "valeur", form.getValeur(), - "nom", form.getNom(), - "compte", ((FluxArgent) possessionRecoupee.possession()).getCompte(), - "prevu", possessionRecoupee.prevu().possession()); - default -> throw new IllegalArgumentException("Type non supporté"); - }; - var newPossession = generator.apply(args); - // TODO: fix possession à corrigé - return new Info<>( - newPossession.nom(), form.getDate(), form.getValeur(), newPossession, newPossession); + new HashMap<>( + Map.of( + "nom", form.getNom(), + "date", form.getDate(), + "valeur", form.getValeur(), + "prévu", possessionRecoupee.prevu().possession())); + switch (possessionRecoupee.possession()) { + case FluxArgent ignored -> + args.put("compte", ((FluxArgent) possessionRecoupee.possession()).getCompte()); + case TransfertArgent ignored -> { + var transfert = (TransfertArgent) possessionRecoupee.possession(); + args.put("depuisCompte", transfert.getDepuisCompte()); + args.put("versCompte", transfert.getVersCompte()); + } + default -> throw new IllegalArgumentException("Type non supporté"); + } + return args; + } + + private Map getPjArgs(Possession possession, AddRecoupementExecutionForm form) { + var prevu = possessionRecoupee.prevu(); + return Map.of( + "id", prevu.isEmpty() ? possession.nom() : prevu.nom(), + "date", form.getDate(), + "link", form.getLinkPJ(), + "ref", form.getReferencePJ()); + } + + private PendingData buildInfoFromForm(AddRecoupementExecutionForm form) { + if (possessionRecoupee.realises().isEmpty()) { + PJFieldsValidator.validatePJ(form); + } + + var pjGenerator = new PieceJustificativeGenerator(); + var possessionGenerator = PossessionGeneratorFactory.make(possessionRecoupee.possession()); + + var comment = form.getComment(); + var newPossession = possessionGenerator.apply(getPossessionArgs(form)); + var newPj = + PJFieldsValidator.hasPJ(form) ? pjGenerator.apply(getPjArgs(newPossession, form)) : null; + + var info = + Info.builder() + .t(form.getDate()) + .nom(newPossession.nom()) + .valeur(form.getValeur()) + .possession(newPossession) + .possessionACorriger(newPossession) + .build(); + + return new PendingData(comment, info, newPj); } private void addTitle() { @@ -203,55 +241,122 @@ private void addTitle() { add(title, BorderLayout.NORTH); } - private void register(Info info) { - if (alreadyExist(info)) { + private void register(PendingData data) { + if (alreadyExist(data.info())) { throw new IllegalArgumentException( - String.format("L'exécution nom=%s est déjà utilisée", info.possession().nom())); + String.format("L'exécution nom=%s est déjà utilisée", data.info().possession().nom())); } - pendingInfos.add(info); - realisesModel.addElement(info); + pendingData.add(data); + realisesModel.addElement(data.info()); + } + + private Set> getPendingData() { + return pendingData.stream().map(PendingData::info).collect(toSet()); } private boolean alreadyExist(Info candidate) { Set> exists = new HashSet<>(possessionRecoupee.realises()); - exists.addAll(pendingInfos); + exists.addAll(getPendingData()); return exists.stream() .anyMatch(info -> info.possession().nom().equals(candidate.possession().nom())); } + private String getPjLines() { + return pendingData.stream() + .filter(data -> data.pj() != null) + .map( + data -> { + var generator = new PieceJustificativePatriLangGenerator(); + return generator.apply(data.pj()); + }) + .collect(joining("\n")); + } + + private String getCasLines() { + var commentGenerator = new CommentPatriLangGenerator(); + return pendingData.stream() + .map( + data -> { + var generator = PatriLangGeneratorFactory.make(data.info().possession()); + var line = generator.apply(data.info().possession()); + var rawComment = data.comment(); + if (!rawComment.isBlank()) { + line += " " + commentGenerator.apply(rawComment); + } + return line; + }) + .collect(joining("\n")); + } + + static QueryResult getSectionOperations(PatriLangFileContext file) { + var querier = new PatriLangFileQuerier(); + var sectionOperation = querier.query(file, document -> document.cas().sectionOperations()); + if (sectionOperation.isEmpty()) { + throw new RuntimeException("Section Operations introuvable dans le fichier"); + } + return sectionOperation.get(); + } + + static QueryResult getSectionPieceJustificatives( + PatriLangFileContext pjFile) { + var querier = new PatriLangFileQuerier(); + var sectionOperation = + querier.query( + pjFile, document -> document.piecesJustificatives().sectionPiecesJustificatives()); + if (sectionOperation.isEmpty()) { + throw new RuntimeException("Section Pièces Justificatives introuvable dans le fichier"); + } + return sectionOperation.get(); + } + private void saveExecutions() { - var lines = - pendingInfos.stream() - .map( - info -> { - var lineGenerator = PatriLangGeneratorFactory.make(info.possession()); - return lineGenerator.apply(info.possession()); - }) - .collect(joining("\n")); - - File casSet = getDoneCasSetFile(); - File selectedFile = state.get("selectedFile"); + var optionalSelectedFile = getSelectedFile(state); + if (optionalSelectedFile.isEmpty()) { + return; + } + + var casSet = getDoneCasSetFile(); + var selectedFile = optionalSelectedFile.get(); + var optionalPjFile = getPJ(selectedFile); AsyncTask.builder() .task( () -> { - var sectionOperation = - querier.query( - new PatriLangFile(selectedFile), - document -> document.cas().sectionOperations()); - if (sectionOperation.isEmpty()) { - throw new RuntimeException("Section Operations introuvable dans le fichier"); - } + var casLines = getCasLines(); + var writer = new PatriLangFileWriter(); + var operations = getSectionOperations(selectedFile); - writter.insertAtLine( + writer.insertAtLine( FileWriterInput.builder() - .content(lines) - .file(new PatriLangFile(selectedFile)) - .casSet(new PatriLangFile(casSet)) + .content(casLines) + .file(selectedFile) + .casSet(casSet) .build(), - sectionOperation.get().endLine()); + operations.endLine()); + + if (optionalPjFile.isEmpty()) { + clearAllTempContents(); + stage(selectedFile); + return null; + } + + var pjLines = getPjLines(); + if (pjLines.isBlank()) { + clearAllTempContents(); + stage(selectedFile); + return null; + } + + var pjFile = optionalPjFile.get(); + var pjs = getSectionPieceJustificatives(pjFile); + writer.insertAtLine( + FileWriterInput.builder().content(pjLines).file(pjFile).casSet(casSet).build(), + pjs.endLine()); + clearAllTempContents(); + stage(selectedFile); + stage(pjFile); return null; }) .onError(MessageDialog::showError) @@ -264,4 +369,6 @@ private void saveExecutions() { .build() .execute(); } + + record PendingData(String comment, Info info, PieceJustificative pj) {} } diff --git a/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/component/recoupement/RecoupementFooter.java b/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/component/recoupement/RecoupementFooter.java index e2f82cd4e..b951d9eae 100644 --- a/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/component/recoupement/RecoupementFooter.java +++ b/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/component/recoupement/RecoupementFooter.java @@ -93,9 +93,23 @@ public void updatePageSelector() { Pagination pagination = state.get("pagination"); var currentPage = pagination.page(); + var listeners = pageSelector.getActionListeners(); + for (var listener : listeners) { + pageSelector.removeActionListener(listener); + } + pageSelector.removeAllItems(); IntStream.rangeClosed(1, totalPages).forEach(pageSelector::addItem); - pageSelector.setSelectedItem(currentPage); + + if (currentPage <= totalPages && currentPage > 0) { + pageSelector.setSelectedItem(currentPage); + } else { + pageSelector.setSelectedItem(1); + } + + for (var listener : listeners) { + pageSelector.addActionListener(listener); + } previousPageButton.setEnabled(currentPage > 1); nextPageButton.setEnabled(currentPage < totalPages); diff --git a/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/generator/PossessionGeneratorFactory.java b/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/generator/PossessionGeneratorFactory.java index fe7e24d28..9dbab8e8f 100644 --- a/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/generator/PossessionGeneratorFactory.java +++ b/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/generator/PossessionGeneratorFactory.java @@ -1,8 +1,10 @@ package school.hei.patrimoine.visualisation.swing.ihm.google.generator; import school.hei.patrimoine.modele.possession.FluxArgent; +import school.hei.patrimoine.modele.possession.TransfertArgent; import school.hei.patrimoine.visualisation.swing.ihm.google.generator.possession.ExecutionGenerator; import school.hei.patrimoine.visualisation.swing.ihm.google.generator.possession.FluxArgentExecutionGenerator; +import school.hei.patrimoine.visualisation.swing.ihm.google.generator.possession.TransfertArgentExecutionGenerator; public class PossessionGeneratorFactory { public static String MULTIPLE_EXECUTION_NOM_FORMAT = "[%s]__%s"; @@ -13,6 +15,10 @@ public static ExecutionGenerator make(T possession) { return (ExecutionGenerator) new FluxArgentExecutionGenerator(); } + if (possession instanceof TransfertArgent) { + return (ExecutionGenerator) new TransfertArgentExecutionGenerator(); + } + throw new IllegalArgumentException( String.format( "Generating instance of clazz=%s is not supported yet", possession.getClass())); diff --git a/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/generator/possession/FluxArgentExecutionGenerator.java b/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/generator/possession/FluxArgentExecutionGenerator.java index abb7692b5..35fb13e3d 100644 --- a/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/generator/possession/FluxArgentExecutionGenerator.java +++ b/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/generator/possession/FluxArgentExecutionGenerator.java @@ -32,9 +32,9 @@ public void validateArgs(Map args) throws IllegalArgumentExcepti public FluxArgent apply(Map args) { validateArgs(args); - String nom = (String) args.get("nom"); - FluxArgent prevu = (FluxArgent) args.getOrDefault("prevu", null); - String realisationNom = + var nom = (String) args.get("nom"); + var prevu = (FluxArgent) args.getOrDefault("prévu", null); + var realisationNom = prevu == null ? nom : String.format(MULTIPLE_EXECUTION_NOM_FORMAT, prevu.nom(), nom); return new FluxArgent( diff --git a/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/generator/possession/PieceJustificativeGenerator.java b/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/generator/possession/PieceJustificativeGenerator.java new file mode 100644 index 000000000..fc1e8dfba --- /dev/null +++ b/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/generator/possession/PieceJustificativeGenerator.java @@ -0,0 +1,39 @@ +package school.hei.patrimoine.visualisation.swing.ihm.google.generator.possession; + +import java.time.LocalDate; +import java.util.Map; +import java.util.function.Function; +import school.hei.patrimoine.modele.possession.pj.PieceJustificative; + +public class PieceJustificativeGenerator + implements Function, PieceJustificative> { + @Override + public PieceJustificative apply(Map args) { + if (!args.containsKey("ref")) { + return null; + } + + if (!args.containsKey("link")) { + return null; + } + + if (!args.containsKey("date")) { + return null; + } + + if (!args.containsKey("id")) { + return null; + } + + var id = (String) args.get("id"); + var ref = (String) args.get("ref"); + var link = (String) args.get("link"); + var date = (LocalDate) args.get("date"); + + if (id.isBlank() || ref.isBlank() || link.isBlank()) { + return null; + } + + return new PieceJustificative(id, date, ref, link); + } +} diff --git a/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/generator/possession/TransfertArgentExecutionGenerator.java b/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/generator/possession/TransfertArgentExecutionGenerator.java new file mode 100644 index 000000000..b8495e6e6 --- /dev/null +++ b/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/generator/possession/TransfertArgentExecutionGenerator.java @@ -0,0 +1,51 @@ +package school.hei.patrimoine.visualisation.swing.ihm.google.generator.possession; + +import static school.hei.patrimoine.visualisation.swing.ihm.google.generator.PossessionGeneratorFactory.MULTIPLE_EXECUTION_NOM_FORMAT; + +import java.time.LocalDate; +import java.util.Map; +import school.hei.patrimoine.modele.Argent; +import school.hei.patrimoine.modele.possession.Compte; +import school.hei.patrimoine.modele.possession.TransfertArgent; + +public class TransfertArgentExecutionGenerator implements ExecutionGenerator { + @Override + public void validateArgs(Map args) throws IllegalArgumentException { + if (!args.containsKey("nom")) { + throw new IllegalArgumentException("nom is mandatory to create a fluxArgent"); + } + + if (!args.containsKey("date")) { + throw new IllegalArgumentException("date is mandatory to create a fluxArgent"); + } + + if (!args.containsKey("valeur")) { + throw new IllegalArgumentException("valeur is mandatory to create a fluxArgent"); + } + + if (!args.containsKey("depuisCompte")) { + throw new IllegalArgumentException("depuisCompte is mandatory to create a fluxArgent"); + } + + if (!args.containsKey("versCompte")) { + throw new IllegalArgumentException("versCompte is mandatory to create a fluxArgent"); + } + } + + @Override + public TransfertArgent apply(Map args) { + validateArgs(args); + + var nom = (String) args.get("nom"); + var prevu = (TransfertArgent) args.getOrDefault("prévu", null); + var realisationNom = + prevu == null ? nom : String.format(MULTIPLE_EXECUTION_NOM_FORMAT, prevu.nom(), nom); + + return new TransfertArgent( + realisationNom, + (Compte) args.get("depuisCompte"), + (Compte) args.get("versCompte"), + (LocalDate) args.get("date"), + (Argent) args.get("valeur")); + } +} diff --git a/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/pages/PatriLangFilesPage.java b/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/pages/PatriLangFilesPage.java index 5f7b0f3d2..2edb5209c 100644 --- a/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/pages/PatriLangFilesPage.java +++ b/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/pages/PatriLangFilesPage.java @@ -36,7 +36,6 @@ public class PatriLangFilesPage extends LazyPage { private static final int COMMENT_PAGE_SIZE = 100; private final State state; - private Button addImprevuButton; private final HtmlViewer htmlViewer; private CommentSideBar commentSideBar; private final FileSideBar fileSideBar; @@ -57,12 +56,7 @@ public PatriLangFilesPage() { this.fileSideBar = new FileSideBar(state); this.htmlViewer = new HtmlViewer(state, fileSideBar); - state.subscribe( - "selectedFile", - () -> { - this.updateCas(); - this.updateAddImprevuButtonVisibility(); - }); + state.subscribe("selectedFile", this::updateCas); PatriLangFilesWatcher.addObserver( () -> { @@ -70,10 +64,6 @@ public PatriLangFilesPage() { this.htmlViewer.update(); this.updateCas(); - - if (this.addImprevuButton != null) { - this.updateAddImprevuButtonVisibility(); - } }); setLayout(new BorderLayout()); @@ -98,7 +88,6 @@ private List leftAppBarControls(State state) { new CasSetAnalyzerButton(), new SearchTextBar(state), recoupementButton(), - addImprevuButton, restoreButton()); } @@ -110,9 +99,6 @@ private List rightAppBarControls(State state) { } private void addAppBar() { - this.addImprevuButton = new AddImprevuButton(state); - addImprevuButton.setVisible(false); - add(new AppBar(leftAppBarControls(state), rightAppBarControls(state)), BorderLayout.NORTH); } @@ -163,23 +149,6 @@ private Button restoreButton() { }); } - private void updateAddImprevuButtonVisibility() { - var optionalSelectedFile = getSelectedFile(state); - - if (optionalSelectedFile.isEmpty()) { - addImprevuButton.setVisible(false); - return; - } - - var selectedFile = optionalSelectedFile.get(); - if (selectedFile.isTypePJ() || selectedFile.isTypeToutCas()) { - addImprevuButton.setVisible(false); - return; - } - - addImprevuButton.setVisible(selectedFile.isDone()); - } - private void updateCas() { var optionalSelectedFile = getSelectedFile(state); if (optionalSelectedFile.isEmpty()) { diff --git a/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/pages/RecoupementPage.java b/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/pages/RecoupementPage.java index 8aa35b91e..269a66e99 100644 --- a/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/pages/RecoupementPage.java +++ b/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/pages/RecoupementPage.java @@ -161,7 +161,12 @@ private void addMainSplitPane() { () -> { var selectedFile = fileList.getSelectedValue(); if (selectedFile != null) { - state.update("selectedFile", selectedFile); + state.update( + Map.of( + "selectedFile", + selectedFile, + "pagination", + new Pagination(1, RECOUPEMENT_ITEM_PER_PAGE))); } }); @@ -234,6 +239,9 @@ private List> getFilteredPossessionRecoupees() { case EXECUTE_SANS_CORRECTION -> statusToKeep.add(RecoupementStatus.EXECUTE_SANS_CORRECTION); } + var pjMap = buildPjMap(selectedFile); + state.update("currentPjMap", pjMap); + var provider = new PossessionRecoupeeProvider(casSetComptes); var meta = new Meta(plannedCas, doneCas); var filter = @@ -243,6 +251,8 @@ private List> getFilteredPossessionRecoupees() { .fin(LocalDate.MAX) .debut(LocalDate.MIN) .nom(getFilterName()) + .pjFilter(getPjFilter()) + .pjMap(pjMap) .pagination(getPagination()) .build(); @@ -251,16 +261,7 @@ private List> getFilteredPossessionRecoupees() { state.update("totalPages", result.totalPage()); } - var pjMap = buildPjMap(selectedFile); - state.update("currentPjMap", pjMap); - - return switch (getPjFilter()) { - case AVEC_PJ -> - result.data().stream().filter(r -> pjMap.containsKey(r.possession().nom())).toList(); - case SANS_PJ -> - result.data().stream().filter(r -> !pjMap.containsKey(r.possession().nom())).toList(); - case TOUS -> result.data(); - }; + return result.data(); } @Override diff --git a/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/providers/PossessionRecoupeeProvider.java b/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/providers/PossessionRecoupeeProvider.java index 36cfb7cee..9f3ca8874 100644 --- a/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/providers/PossessionRecoupeeProvider.java +++ b/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/providers/PossessionRecoupeeProvider.java @@ -1,20 +1,25 @@ package school.hei.patrimoine.visualisation.swing.ihm.google.providers; import static java.util.Comparator.comparing; +import static school.hei.patrimoine.visualisation.swing.ihm.google.pages.filters.PossessionRecoupeeFilterPj.AVEC_PJ; +import static school.hei.patrimoine.visualisation.swing.ihm.google.pages.filters.PossessionRecoupeeFilterPj.TOUS; import java.time.LocalDate; import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.Set; import lombok.Builder; import lombok.RequiredArgsConstructor; import school.hei.patrimoine.cas.Cas; import school.hei.patrimoine.modele.possession.Compte; import school.hei.patrimoine.modele.possession.Possession; +import school.hei.patrimoine.modele.possession.pj.PieceJustificative; import school.hei.patrimoine.modele.recouppement.RecoupeurDePossessions; import school.hei.patrimoine.modele.recouppement.model.CompteGetter; import school.hei.patrimoine.modele.recouppement.model.PossessionRecoupee; import school.hei.patrimoine.modele.recouppement.model.RecoupementStatus; +import school.hei.patrimoine.visualisation.swing.ihm.google.pages.filters.PossessionRecoupeeFilterPj; import school.hei.patrimoine.visualisation.swing.ihm.google.providers.model.Pagination; @RequiredArgsConstructor @@ -30,7 +35,9 @@ public record Filter( LocalDate debut, LocalDate fin, Pagination pagination, - Set statuses) {} + Set statuses, + PossessionRecoupeeFilterPj pjFilter, + Map pjMap) {} @Builder(toBuilder = true) public record Result(List> data, int totalPage) {} @@ -68,6 +75,9 @@ private static List> getFilteredByFilter( if (!isIncludeInStatuses(recoupee, filter.statuses())) { return false; } + if (!isIncludeInPj(recoupee, filter.pjFilter(), filter.pjMap())) { + return false; + } return includeNom(recoupee, filter.nom()); }) .sorted( @@ -82,6 +92,24 @@ private static boolean includeNom(PossessionRecoupee recoupee, Strin || recoupee.possession().nom().toLowerCase().contains(nom.toLowerCase()); } + private static boolean isIncludeInPj( + PossessionRecoupee recoupee, + PossessionRecoupeeFilterPj pjFilter, + Map pjMap) { + + if (pjFilter == null || pjFilter == TOUS) { + return true; + } + + var hasPj = pjMap != null && pjMap.containsKey(recoupee.possession().nom()); + + if (pjFilter == AVEC_PJ) { + return hasPj; + } else { + return !hasPj; + } + } + private static boolean isIncludeInStatuses( PossessionRecoupee recoupee, Set statuses) { return statuses == null || statuses.isEmpty() || statuses.contains(recoupee.status()); diff --git a/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/validator/NombreValidator.java b/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/validator/NombreValidator.java index 56458c37a..6b5515273 100644 --- a/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/validator/NombreValidator.java +++ b/src/main/java/school/hei/patrimoine/visualisation/swing/ihm/google/validator/NombreValidator.java @@ -9,7 +9,11 @@ public static void validate(String input) { throw new IllegalArgumentException(); } - parseDouble(input.trim().replaceAll(" ", "").replaceAll("_", "")); + var value = parseDouble(input.trim().replaceAll(" ", "").replaceAll("_", "")); + + if (Double.isNaN(value) || Double.isInfinite(value)) { + throw new IllegalArgumentException(); + } } catch (IllegalArgumentException e) { throw new IllegalArgumentException( String.format("Nombre invalide '%s' (syntaxe incorrecte)", input)); diff --git a/src/test/java/school/hei/patrimoine/modele/decomposeur/TransfertArgentDecomposeurTest.java b/src/test/java/school/hei/patrimoine/modele/decomposeur/TransfertArgentDecomposeurTest.java new file mode 100644 index 000000000..2a9a9426a --- /dev/null +++ b/src/test/java/school/hei/patrimoine/modele/decomposeur/TransfertArgentDecomposeurTest.java @@ -0,0 +1,119 @@ +package school.hei.patrimoine.modele.decomposeur; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static school.hei.patrimoine.modele.Argent.ariary; +import static school.hei.patrimoine.modele.decomposeur.IdRetriever.getDecomposedId; +import static school.hei.patrimoine.utils.Comparator.isTransfertArgentEquals; + +import java.time.LocalDate; +import java.util.List; +import org.junit.jupiter.api.Test; +import school.hei.patrimoine.modele.Argent; +import school.hei.patrimoine.modele.possession.Compte; +import school.hei.patrimoine.modele.possession.TransfertArgent; + +class TransfertArgentDecomposeurTest { + private static final LocalDate DEBUT = LocalDate.of(2025, 1, 1); + private static final LocalDate FIN = LocalDate.of(2025, 12, 31); + private static final String TRANSFERT_ARGENT_BASE_ID = "transfertArgent"; + private static final Argent TRANSFERT_ARGENT_TRANSFERT_MENSUEL = ariary(200); + + private static final TransfertArgentDecomposeur subject = + new TransfertArgentDecomposeur(DEBUT, FIN); + + @Test + void empty_if_operation_out_of_range() { + var transfert = transfertArgent(DEBUT.minusMonths(2), DEBUT.minusMonths(1), 3); + assertTrue(subject.apply(transfert).isEmpty()); + } + + @Test + void full_decomposed_if_fit_inside_range() { + var transfert = transfertArgent(DEBUT.plusDays(1), LocalDate.of(2025, 3, 31), 2); + var expected = + List.of( + expected(LocalDate.of(2025, 1, 2)), + expected(LocalDate.of(2025, 2, 2)), + expected(LocalDate.of(2025, 3, 2))); + + var actual = subject.apply(transfert); + assertTrue(isTransfertArgentEquals(expected, actual)); + } + + @Test + void decomposed_until_fin() { + var transfert = transfertArgent(LocalDate.of(2025, 10, 31), FIN.plusMonths(12), 31); + var expected = + List.of( + expected(LocalDate.of(2025, 10, 31)), + expected(LocalDate.of(2025, 11, 30)), + expected(LocalDate.of(2025, 12, 31))); + + var actual = subject.apply(transfert); + assertTrue(isTransfertArgentEquals(expected, actual)); + } + + @Test + void decomposed_starting_from_debut() { + var transfert = transfertArgent(LocalDate.of(2024, 10, 31), LocalDate.of(2025, 3, 31), 31); + var expected = + List.of( + expected(LocalDate.of(2025, 1, 31)), + expected(LocalDate.of(2025, 2, 28)), + expected(LocalDate.of(2025, 3, 31))); + + var actual = subject.apply(transfert); + assertTrue(isTransfertArgentEquals(expected, actual)); + } + + @Test + void decomposed_starting_from_debut_and_end_on_fin() { + var transfert = transfertArgent(LocalDate.of(2024, 10, 31), LocalDate.of(2026, 3, 31), 31); + var expected = + List.of( + expected(LocalDate.of(2025, 1, 31)), + expected(LocalDate.of(2025, 2, 28)), + expected(LocalDate.of(2025, 3, 31)), + expected(LocalDate.of(2025, 4, 30)), + expected(LocalDate.of(2025, 5, 31)), + expected(LocalDate.of(2025, 6, 30)), + expected(LocalDate.of(2025, 7, 31)), + expected(LocalDate.of(2025, 8, 31)), + expected(LocalDate.of(2025, 9, 30)), + expected(LocalDate.of(2025, 10, 31)), + expected(LocalDate.of(2025, 11, 30)), + expected(LocalDate.of(2025, 12, 31))); + + var actual = subject.apply(transfert); + assertTrue(isTransfertArgentEquals(expected, actual)); + } + + private static TransfertArgent expected(LocalDate date) { + return new TransfertArgent( + getDecomposedId(TRANSFERT_ARGENT_BASE_ID, date), + depuisCompte(), + versCompte(), + date, + TRANSFERT_ARGENT_TRANSFERT_MENSUEL); + } + + private static TransfertArgent transfertArgent( + LocalDate debut, LocalDate fin, int dateOperation) { + return new TransfertArgent( + TRANSFERT_ARGENT_BASE_ID, + depuisCompte(), + versCompte(), + debut, + fin, + dateOperation, + TRANSFERT_ARGENT_TRANSFERT_MENSUEL); + } + + private static Compte depuisCompte() { + return new Compte("depuisCompte", DEBUT, ariary(10)); + } + + private static Compte versCompte() { + return new Compte("versCompte", DEBUT, ariary(10)); + } +} diff --git a/src/test/java/school/hei/patrimoine/modele/possession/FluxCompteTest.java b/src/test/java/school/hei/patrimoine/modele/possession/FluxCompteTest.java index c69eed2b2..cb6536e62 100644 --- a/src/test/java/school/hei/patrimoine/modele/possession/FluxCompteTest.java +++ b/src/test/java/school/hei/patrimoine/modele/possession/FluxCompteTest.java @@ -2,13 +2,10 @@ import static java.time.Month.*; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static school.hei.patrimoine.modele.Argent.ariary; import java.time.LocalDate; import org.junit.jupiter.api.Test; -import school.hei.patrimoine.modele.recouppement.model.PossessionRecoupee; -import school.hei.patrimoine.modele.recouppement.model.RecoupementStatus; class FluxCompteTest { @Test @@ -65,12 +62,4 @@ void dateOperation_superieur_a_lastDayOfMonth_est_corrigee() { var actual = compte.projectionFuture(LocalDate.of(2025, APRIL, 30)); assertEquals(ariary(-400_000), actual.valeurComptable); } - - @Test - void non_execute_ne_peut_pas_avoir_pj() { - var possessionRecouped = - PossessionRecoupee.builder().status(RecoupementStatus.NON_EXECUTE).build(); - - assertFalse(possessionRecouped.hasSupportingDocument()); - } } diff --git a/src/test/java/school/hei/patrimoine/patrilang/generator/possession/TransfertArgentPatriLangGeneratorTest.java b/src/test/java/school/hei/patrimoine/patrilang/generator/possession/TransfertArgentPatriLangGeneratorTest.java new file mode 100644 index 000000000..575458e67 --- /dev/null +++ b/src/test/java/school/hei/patrimoine/patrilang/generator/possession/TransfertArgentPatriLangGeneratorTest.java @@ -0,0 +1,29 @@ +package school.hei.patrimoine.patrilang.generator.possession; + +import static java.time.Month.JANUARY; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static school.hei.patrimoine.modele.Argent.ariary; + +import java.time.LocalDate; +import org.junit.jupiter.api.Test; +import school.hei.patrimoine.modele.possession.Compte; +import school.hei.patrimoine.modele.possession.TransfertArgent; + +class TransfertArgentPatriLangGeneratorTest { + private static final TransfertArgentPatriLangGenerator subject = + new TransfertArgentPatriLangGenerator(); + private static final LocalDate date = LocalDate.of(2025, JANUARY, 1); + private static final Compte depuisCompte = new Compte("depuisCompte", date, ariary(5000)); + private static final Compte versCompte = new Compte("versCompte", date, ariary(5000)); + + @Test + void basique() { + var transfert = new TransfertArgent("transfert1", depuisCompte, versCompte, date, ariary(1000)); + var actual = subject.apply(transfert); + + var expected = + "* `transfert1`, le 1 janvier 2025 transférer 1_000Ar depuis Trésoreries:depuisCompte vers" + + " Trésoreries:versCompte"; + assertEquals(expected, actual); + } +} diff --git a/src/test/java/school/hei/patrimoine/utils/Comparator.java b/src/test/java/school/hei/patrimoine/utils/Comparator.java index 38d5132ba..5ded2e2cc 100644 --- a/src/test/java/school/hei/patrimoine/utils/Comparator.java +++ b/src/test/java/school/hei/patrimoine/utils/Comparator.java @@ -3,6 +3,7 @@ import java.util.List; import lombok.extern.slf4j.Slf4j; import school.hei.patrimoine.modele.possession.FluxArgent; +import school.hei.patrimoine.modele.possession.TransfertArgent; @Slf4j public class Comparator { @@ -47,4 +48,50 @@ public static boolean isFluxArgentEquals(List a, List b) return true; } + + public static boolean isTransfertArgentEquals(TransfertArgent a, TransfertArgent b) { + if (!a.nom().equals(b.nom())) { + return false; + } + + if (!a.getFluxMensuel().equals(b.getFluxMensuel())) { + return false; + } + + if (!a.getDebut().equals(b.getDebut())) { + return false; + } + + if (!a.getFin().equals(b.getFin())) { + return false; + } + + if (!a.getDepuisCompte().nom().equals(b.getDepuisCompte().nom())) { + return false; + } + + if (!a.getVersCompte().nom().equals(b.getVersCompte().nom())) { + return false; + } + + return a.getDateOperation() == b.getDateOperation(); + } + + public static boolean isTransfertArgentEquals(List a, List b) { + if (a.size() != b.size()) { + return false; + } + + for (int i = 0; i < a.size(); i++) { + var transfertA = a.get(i); + var transfertB = b.get(i); + + if (!isTransfertArgentEquals(transfertA, transfertB)) { + log.error("{} is not equals to {}", transfertA, transfertB); + return false; + } + } + + return true; + } } diff --git a/src/test/java/school/hei/patrimoine/visualisation/swing/ihm/google/generator/possession/FluxArgentExecutionGeneratorTest.java b/src/test/java/school/hei/patrimoine/visualisation/swing/ihm/google/generator/possession/FluxArgentExecutionGeneratorTest.java index 0f7b2c1bf..80f21e0b4 100644 --- a/src/test/java/school/hei/patrimoine/visualisation/swing/ihm/google/generator/possession/FluxArgentExecutionGeneratorTest.java +++ b/src/test/java/school/hei/patrimoine/visualisation/swing/ihm/google/generator/possession/FluxArgentExecutionGeneratorTest.java @@ -119,7 +119,7 @@ void apply_creates_flux_argent_with_formatted_name_when_prevu_exists() { "date", date, "valeur", valeur, "compte", compte, - "prevu", prevu); + "prévu", prevu); FluxArgent actual = subject.apply(args);