--- /dev/null
+/*
+ * Copyright © FYLAB and the Conseil Régional d'Île-de-France, 2009
+ * This file is part of L'Interface Libre et Interactive de l'Enseignement (Lilie).
+ *
+ * Lilie is free software. You can redistribute it and/or modify since
+ * you respect the terms of either (at least one of the both license) :
+ * - under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ * - the CeCILL-C as published by CeCILL-C; either version 1 of the
+ * License, or any later version
+ *
+ * There are special exceptions to the terms and conditions of the
+ * licenses as they are applied to this software. View the full text of
+ * the exception in file LICENSE.txt in the directory of this software
+ * distribution.
+ *
+ * Lilie is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Licenses for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * and the CeCILL-C along with Lilie. If not, see :
+ * <http://www.gnu.org/licenses/> and
+ * <http://www.cecill.info/licences.fr.html>.
+ */
+
+package org.lilie.services.eliot.applications.notes.resultat
+
+import org.lilie.services.eliot.scolarite.StructureEnseignement
+import org.lilie.services.eliot.scolarite.Periode
+import org.hibernate.criterion.CriteriaSpecification
+import org.lilie.services.eliot.notes.CalcMoyennesInfo
+import org.lilie.services.eliot.notes.DirtyMoyenne
+import org.lilie.services.eliot.notes.ResultatEleveServicePeriode
+import org.lilie.services.eliot.notes.TypeMoyenneEnum
+import org.lilie.services.eliot.constantes.ConstFonct
+
+class DirtyMoyenneService {
+
+ static transactional = true
+
+
+ /**
+ * Vérifie si les moyennes pour cette structureEnseignement sont à jour
+ * @param structureEnseignement (classe, groupe local, regroupement)
+ * @author bper
+ */
+ CalcMoyennesInfo internalVerifieMoyennesAjour(StructureEnseignement structureEnseignement,
+ List<Periode> periodes = null) {
+
+ // La requete retourne la classe dont les moyenne ne sont pas à jour.
+ // Cela peut être la structureEnseignement elle même si elle de type CLASSE
+ // ou une de ses classes si elle est de type GROUPE.
+ // Dans le cas où toutes les moyennes sont à jour le requête retourne null.
+ StructureEnseignement classe = (StructureEnseignement) DirtyMoyenne.createCriteria().get {
+ createAlias('periode.classe', 'cls')
+ createAlias('cls.infoCalculMoyennesClasseSet', 'infoCalcul', CriteriaSpecification.LEFT_JOIN)
+
+ or {
+ or {
+ isNull('infoCalcul.dateDebutCalcul')
+ geProperty('dateChangement', 'infoCalcul.dateDebutCalcul')
+ }
+ isEmpty('cls.infoCalculMoyennesClasseSet')
+ }
+
+ periode {
+ if (structureEnseignement.isClasse()) {
+ eq('classe', structureEnseignement)
+ } else {
+ 'in'('classe', structureEnseignement.classes)
+ }
+
+ if (periodes) {
+ 'in'('id',periodes*.id)
+ }
+
+ projections {
+ property('classe')
+ }
+ }
+
+ order('infoCalcul.dateFinCalcul')
+
+ maxResults(1)
+ }
+
+ CalcMoyennesInfo calcInfoGlobal = new CalcMoyennesInfo(
+ moyennesAjour: classe ? false : true,
+ dateDernierCalculMoyennes: classe?.getInfoCalculMoyennesClasse()?.dateFinCalcul
+ )
+
+ return calcInfoGlobal
+ }
+
+
+ /**
+ * Marque que les données source d'une moyenne ont chagé.
+ */
+ protected void marqueChangement(DirtyMoyenneParams params) {
+ DirtyMoyenne dirtyMoyenne = internalCreeOrFindDirtyMoyenne(params)
+ dirtyMoyenne.dateChangement = new Date()
+ dirtyMoyenne.save(failOnError: true, flush: true)
+ }
+
+
+ /**
+ * Cherche ou crée une DirtyMoyenne
+ * @author msan
+ */
+ DirtyMoyenne internalCreeOrFindDirtyMoyenne(DirtyMoyenneParams params) {
+
+ DirtyMoyenne dirtyMoyenne = null
+ List<DirtyMoyenne> dirtyMoyennes = internalFindAllDirtyMoyenne(params)
+
+ if (dirtyMoyennes.isEmpty()) {
+ // DM n'existe pas encore - crée la
+ dirtyMoyenne = internalCreeDirtyMoyenne(params)
+ } else {
+ // un seul enregistrement - c'est ce qu'on veut
+ if (dirtyMoyennes.size()==1) {
+ dirtyMoyenne = dirtyMoyennes.first()
+ } else {
+ // plusieurs enregistrements - on prend le plus récent
+ // - l'autre va être supprimé en tout cas par le processus qui l'a créé
+ dirtyMoyenne = dirtyMoyennes.sort { DirtyMoyenne dm ->
+ dm.dateChangement
+ }.last()
+ }
+ }
+
+ return dirtyMoyenne
+ }
+
+
+ /**
+ * Liste des DirtyMoyenne
+ * @param params parametres des DMs a chercher
+ * @param trieParDateChangement si true, les DMs sont tries par dateChangement (la plus recente est la premiere)
+ * @author msan
+ */
+ List<DirtyMoyenne> internalFindAllDirtyMoyenne(DirtyMoyenneParams params, Boolean trieParDateChangement = false) {
+ return DirtyMoyenne.withCriteria {
+ if (params.eleve) {
+ eq('eleve', params.eleve)
+ }
+ if (params.classe) {
+ eq('classe', params.classe)
+ }
+ if (params.periode) {
+ eq('periode', params.periode)
+ }
+ if(params.service) {
+ eq(ConstFonct.SERVICE, params.service)
+ }
+ if(params.sousService){
+ eq('sousService', params.sousService)
+ }
+ if (params.typeMoyenne) {
+ eq('typeMoyenne', params.typeMoyenne)
+ }
+ if (trieParDateChangement) {
+ order("dateChangement", "desc")
+ }
+ }
+ }
+
+
+ /**
+ * Crée DirtyMoyenne avec la gestion de création concurrente
+ * @author msan
+ */
+ DirtyMoyenne internalCreeDirtyMoyenne(DirtyMoyenneParams params) {
+ DirtyMoyenne dirtyMoyenne = new DirtyMoyenne(dateChangement : new Date())
+
+ if (params.eleve) {
+ dirtyMoyenne.eleve = params.eleve
+ }
+ if (params.classe) {
+ dirtyMoyenne.classe = params.classe
+ }
+ if (params.periode) {
+ dirtyMoyenne.periode = params.periode
+ }
+ if(params.service) {
+ dirtyMoyenne.service = params.service
+ }
+ if(params.sousService){
+ dirtyMoyenne.sousService = params.sousService
+ }
+ if(params.typeMoyenne) {
+ dirtyMoyenne.typeMoyenne = params.typeMoyenne
+ }
+ dirtyMoyenne.save(failOnError: true, flush: true)
+
+ // vérifie que DM est la seule
+ // sinon supprime la si elle n'est pas la plus récente
+ List<DirtyMoyenne> dmExistnates = internalFindAllDirtyMoyenne(params)
+ if (dmExistnates.size()>1) {
+ dmExistnates.sort { it.dateChangement }
+ DirtyMoyenne dmPlusRecente = dmExistnates.last()
+ if ((dmPlusRecente) != (dirtyMoyenne)) {
+ // on n'est pas le dernière à crée le DM
+ dirtyMoyenne.delete()
+ dirtyMoyenne = dmPlusRecente
+ }
+ }
+ return dirtyMoyenne
+ }
+
+
+ /**
+ * Marque les moyennes ELEVE_SERVICE_PERIODE et ELEVE_SOUS_SERVICE_PERIODE
+ * comme dirty pour les services/sous-services et périodes pour lesquels
+ * les rangs et les nombres de notes n'ont pas été calculés.
+ * N.B. Les rangs ne sont pas calculés pour les sous-services
+ * @author bper
+ * @author msan
+ */
+ void marqueDirtyIfNotUptodate(List<ResultatEleveServicePeriode> resultats) {
+
+ List<DirtyMoyenneParams> resultatsServicesARecalculer = []
+ List<DirtyMoyenneParams> resultatsSousServicesARecalculer = []
+
+ resultats.each { ResultatEleveServicePeriode resultatEleveServicePeriode ->
+
+ if (resultatEleveServicePeriode.moyenne != null &&
+ ( resultatEleveServicePeriode.rang == null ||
+ resultatEleveServicePeriode.nbEleves == null ||
+ (resultatEleveServicePeriode.nbNotes == null &&
+ !resultatEleveServicePeriode.resultatsEleveSousServicePeriode)
+ )) {
+ resultatsServicesARecalculer.add(
+ new DirtyMoyenneParams(
+ eleve: resultatEleveServicePeriode.eleve,
+ service: resultatEleveServicePeriode.service,
+ periode: resultatEleveServicePeriode.periode,
+ typeMoyenne: TypeMoyenneEnum.ELEVE_SERVICE_PERIODE
+ )
+ )
+ }
+
+ resultatEleveServicePeriode.resultatsEleveSousServicePeriode?.each {
+
+ if (it.moyenne != null && it.nbNotes == null) {
+
+ resultatsSousServicesARecalculer.add(new DirtyMoyenneParams(
+ eleve: it.resultatEleveServicePeriode.eleve,
+ sousService: it.sousService,
+ periode: resultatEleveServicePeriode.periode,
+ typeMoyenne: TypeMoyenneEnum.ELEVE_SOUS_SERVICE_PERIODE
+ ))
+
+ }
+
+ }
+ }
+
+ resultatsServicesARecalculer.each { marqueChangement(it)}
+ resultatsSousServicesARecalculer.each { marqueChangement(it) }
+ }
+
+}