001/* 002 * Copyright 2020 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.ametys.plugins.odfpilotage.cost; 018 019import java.util.Collections; 020import java.util.HashMap; 021import java.util.List; 022import java.util.Map; 023import java.util.Objects; 024import java.util.Optional; 025import java.util.Set; 026import java.util.stream.Collectors; 027 028import org.apache.avalon.framework.component.Component; 029import org.apache.avalon.framework.service.ServiceException; 030import org.apache.avalon.framework.service.ServiceManager; 031import org.apache.avalon.framework.service.Serviceable; 032import org.apache.commons.lang3.StringUtils; 033 034import org.ametys.cms.data.ContentValue; 035import org.ametys.cms.repository.Content; 036import org.ametys.odf.ODFHelper; 037import org.ametys.odf.ProgramItem; 038import org.ametys.odf.course.Course; 039import org.ametys.odf.courselist.CourseList; 040import org.ametys.odf.courselist.CourseList.ChoiceType; 041import org.ametys.odf.coursepart.CoursePart; 042import org.ametys.odf.enumeration.OdfReferenceTableHelper; 043import org.ametys.odf.orgunit.OrgUnit; 044import org.ametys.odf.program.Container; 045import org.ametys.odf.program.Program; 046import org.ametys.odf.program.SubProgram; 047import org.ametys.plugins.odfpilotage.cost.entity.CostComputationData; 048import org.ametys.plugins.odfpilotage.cost.entity.CostComputationDataCache; 049import org.ametys.plugins.odfpilotage.cost.entity.CoursePartCostData; 050import org.ametys.plugins.odfpilotage.cost.entity.Effectives; 051import org.ametys.plugins.odfpilotage.cost.entity.EqTD; 052import org.ametys.plugins.odfpilotage.cost.entity.NormDetails; 053import org.ametys.plugins.odfpilotage.cost.entity.OverriddenData; 054import org.ametys.plugins.odfpilotage.cost.entity.VolumesOfHours; 055import org.ametys.plugins.odfpilotage.helper.PilotageHelper; 056import org.ametys.plugins.odfpilotage.helper.ReportHelper; 057import org.ametys.plugins.odfpilotage.helper.ReportUtils; 058import org.ametys.plugins.repository.AmetysObjectResolver; 059import org.ametys.plugins.repository.AmetysRepositoryException; 060import org.ametys.plugins.repository.UnknownAmetysObjectException; 061import org.ametys.runtime.model.ModelItem; 062import org.ametys.runtime.plugin.component.AbstractLogEnabled; 063 064/** 065 * This component computes values used by the cost modeling tool 066 */ 067public class CostComputationComponent extends AbstractLogEnabled implements Component, Serviceable 068{ 069 /** The Avalon role name */ 070 public static final String ROLE = CostComputationComponent.class.getName(); 071 072 private static final String __VALUE_YEAR = "annee"; 073 private static final String __ETAPE_PORTEUSE = "etapePorteuse"; 074 private static final String __EFFECTIF_ESTIMATED = "numberOfStudentsEstimated"; 075 076 /** The ODF enumeration helper */ 077 protected OdfReferenceTableHelper _refTableHelper; 078 079 /** The report helper */ 080 protected ReportHelper _reportHelper; 081 082 /** The ODF helper */ 083 protected ODFHelper _odfHelper; 084 085 /** The ametys object resolver */ 086 protected AmetysObjectResolver _resolver; 087 088 @Override 089 public void service(ServiceManager manager) throws ServiceException 090 { 091 _refTableHelper = (OdfReferenceTableHelper) manager.lookup(OdfReferenceTableHelper.ROLE); 092 _reportHelper = (ReportHelper) manager.lookup(ReportHelper.ROLE); 093 _odfHelper = (ODFHelper) manager.lookup(ODFHelper.ROLE); 094 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 095 } 096 097 /** 098 * Call methods to initialize data and compute groups distribution from an orgUnit 099 * @param orgUnit the orgUnit 100 * @param catalog the catalog 101 * @param lang the lang 102 * @return CostData object containing informations about the formation 103 */ 104 public CostComputationData computeCostsOnOrgUnits(OrgUnit orgUnit, String catalog, String lang) 105 { 106 OverriddenData overriddenData = new OverriddenData(); 107 return computeCostsOnOrgUnits(orgUnit, catalog, lang, overriddenData); 108 } 109 110 /** 111 * Call methods to initialize data and compute groups distribution from an orgUnit 112 * @param orgUnit the orgUnit 113 * @param catalog the catalog 114 * @param lang the lang 115 * @param overriddenData overridden data by the user 116 * @return CostData object containing informations about the formation 117 */ 118 public CostComputationData computeCostsOnOrgUnits(OrgUnit orgUnit, String catalog, String lang, OverriddenData overriddenData) 119 { 120 CostComputationDataCache cache = new CostComputationDataCache(_refTableHelper.getItems(PilotageHelper.NORME), _refTableHelper.getItems(OdfReferenceTableHelper.ENSEIGNEMENT_NATURE), overriddenData); 121 122 CostComputationData costData = new CostComputationData(); 123 _populateOrgUnits(costData, orgUnit, cache, catalog, lang); 124 125 return costData; 126 } 127 128 /** 129 * Call methods to initialize data and compute groups distribution from a catalog 130 * @param programs the catalog 131 * @return CostData object containing informations about the formation 132 */ 133 public CostComputationData computeCostsOnPrograms(List<Program> programs) 134 { 135 OverriddenData overriddenData = new OverriddenData(); 136 return computeCostsOnPrograms(programs, overriddenData); 137 } 138 139 /** 140 * Call methods to initialize data and compute groups distribution from a catalog 141 * @param programs the catalog 142 * @param overriddenData overridden data by the user 143 * @return CostData object containing informations about the formation 144 */ 145 public CostComputationData computeCostsOnPrograms(List<Program> programs, OverriddenData overriddenData) 146 { 147 CostComputationDataCache cache = new CostComputationDataCache(_refTableHelper.getItems(PilotageHelper.NORME), _refTableHelper.getItems(OdfReferenceTableHelper.ENSEIGNEMENT_NATURE), overriddenData); 148 149 CostComputationData costData = new CostComputationData(); 150 151 for (Program program : programs) 152 { 153 _setProgramItemStructure(program, costData, cache); 154 _populateProgram(costData, program, program.getName(), cache); 155 } 156 return costData; 157 } 158 159 /** 160 * Call methods to initialize data and compute groups distribution from a program 161 * @param program the program 162 * @return CostData object containing informations about the formation 163 */ 164 public CostComputationData computeCostsOnProgram(Program program) 165 { 166 List<Program> programs = List.of(program); 167 return computeCostsOnPrograms(programs); 168 } 169 170 /** 171 * Call methods to initialize data and compute groups distribution from a program 172 * @param program the program 173 * @param overriddenData overridden data by the user 174 * @return CostData object containing informations about the formation 175 */ 176 public CostComputationData computeCostsOnProgram(Program program, OverriddenData overriddenData) 177 { 178 List<Program> programs = List.of(program); 179 return computeCostsOnPrograms(programs, overriddenData); 180 } 181 182 /** 183 * Iterate the program item structure and compute the global effective for each course part 184 * @param programItem the program item 185 * @param costData object containing informations about the formation 186 * @param cache the cache values 187 */ 188 protected void _setProgramItemStructure(ProgramItem programItem, CostComputationData costData, CostComputationDataCache cache) 189 { 190 // On ajout le programItem courant à la structure 191 List<ProgramItem> childs = _odfHelper.getChildProgramItems(programItem); 192 for (ProgramItem child : childs) 193 { 194 if (child instanceof Course && !((Course) child).hasCourseLists()) 195 { 196 for (CoursePart coursePart : ((Course) child).getCourseParts()) 197 { 198 // On calcule l'effectif global et saisi pour chaque coursePart 199 _getComputedGlobalAndEnteredEffectives(costData, coursePart, cache); 200 } 201 } 202 // Appel récursif pour continuer de parcourir la structure 203 _setProgramItemStructure(child, costData, cache); 204 } 205 } 206 207 /** 208 * Iterate and explore each orgUnit of the structure 209 * @param orgUnit the orgUnit 210 * @param costData object containing informations about the formation 211 * @param cache the cache values 212 * @param catalog the catalog 213 * @param lang the lang 214 */ 215 protected void _populateOrgUnits(CostComputationData costData, OrgUnit orgUnit, CostComputationDataCache cache, String catalog, String lang) 216 { 217 List<String> orgUnitsIds = orgUnit.getSubOrgUnits(); 218 String orgUnitPath = orgUnit.getName(); 219 if (!orgUnitsIds.isEmpty() && orgUnit.getParentOrgUnit() == null) 220 { 221 VolumesOfHours volumeOfHours = new VolumesOfHours(orgUnit.getId()); 222 EqTD eqTD = new EqTD(orgUnit.getId()); 223 Effectives effectives = new Effectives(orgUnit.getId()); 224 225 for (String orgUnitId : orgUnitsIds) 226 { 227 OrgUnit childOrgUnit = _resolver.resolveById(orgUnitId); 228 String childOrgUnitPath = orgUnitPath + ModelItem.ITEM_PATH_SEPARATOR + childOrgUnit.getName(); 229 _populateOrgUnit(costData, childOrgUnit, cache, childOrgUnitPath, catalog, lang); 230 231 Effectives childEffective = costData.getEffective(childOrgUnit.getId()); 232 effectives.sum(childEffective, childOrgUnitPath, orgUnitPath); 233 234 // Calcule les volumes horaires 235 VolumesOfHours childVolumeOfHour = costData.getVolumesOfHours(childOrgUnit.getId()); 236 volumeOfHours.sum(childVolumeOfHour, 1d); 237 238 // Calcule les heures eqTD 239 EqTD childEqTD = costData.getEqTD(childOrgUnit.getId()); 240 eqTD.sum(childEqTD, 1d, childOrgUnitPath, orgUnitPath); 241 } 242 // Stock les informations 243 costData.addEqTD(orgUnit.getId(), eqTD); 244 costData.addVolumeOfHours(orgUnit.getId(), volumeOfHours); 245 costData.setEffective(orgUnit.getId(), effectives); 246 Optional<Double> localEffective = costData.getComputedLocalEffective(orgUnit.getId(), orgUnitPath); 247 Double heReport = localEffective.isPresent() && localEffective.get() != 0 ? eqTD.getProRatedEqTD(orgUnitPath) / localEffective.get() : 0; 248 costData.addHeReport(orgUnitPath, heReport); 249 250 } 251 else 252 { 253 _populateOrgUnit(costData, orgUnit, cache, orgUnitPath, catalog, lang); 254 } 255 } 256 257 /** 258 * Iterate and explore each program of an orgUnit 259 * @param orgUnit the orgUnit 260 * @param costData object containing informations about the formation 261 * @param cache the cache values 262 * @param orgUnitPath the current path 263 * @param catalog the catalog 264 * @param lang the lang 265 */ 266 protected void _populateOrgUnit(CostComputationData costData, OrgUnit orgUnit, CostComputationDataCache cache, String orgUnitPath, String catalog, String lang) 267 { 268 List<Program> programs = _odfHelper.getProgramsFromOrgUnit(orgUnit, catalog, lang); 269 270 VolumesOfHours volumeOfHours = new VolumesOfHours(orgUnit.getId()); 271 EqTD eqTD = new EqTD(orgUnit.getId()); 272 Effectives effectives = new Effectives(orgUnit.getId()); 273 274 for (Program program : programs) 275 { 276 String programPath = orgUnitPath + ModelItem.ITEM_PATH_SEPARATOR + program.getName(); 277 _setProgramItemStructure(program, costData, cache); 278 _populateProgram(costData, program, programPath, cache); 279 280 // Somme les effectifs de chaque étape pour calculer les effectifs totaux de la formation 281 Effectives childEffective = costData.getEffective(program.getId()); 282 effectives.sum(childEffective, programPath, orgUnitPath); 283 284 // Calcule les volumes horaires 285 VolumesOfHours childVolumeOfHour = costData.getVolumesOfHours(program.getId()); 286 volumeOfHours.sum(childVolumeOfHour, 1d); 287 288 // Calcule les heures eqTD 289 EqTD childEqTD = costData.getEqTD(program.getId()); 290 eqTD.sum(childEqTD, 1d, programPath, orgUnitPath); 291 } 292 293 // Stock les informations 294 costData.addEqTD(orgUnit.getId(), eqTD); 295 costData.addVolumeOfHours(orgUnit.getId(), volumeOfHours); 296 costData.setEffective(orgUnit.getId(), effectives); 297 Optional<Double> localEffective = costData.getComputedLocalEffective(orgUnit.getId(), orgUnitPath); 298 Double heReport = localEffective.isPresent() && localEffective.get() != 0 ? eqTD.getProRatedEqTD(orgUnitPath) / localEffective.get() : 0; 299 costData.addHeReport(orgUnitPath, heReport); 300 } 301 302 /** 303 * Iterate and explore each container of the catalog 304 * @param program the catalog 305 * @param costData object containing informations about the formation 306 * @param programPath the current path 307 * @param cache the cache values 308 */ 309 protected void _populateProgram(CostComputationData costData, Program program, String programPath, CostComputationDataCache cache) 310 { 311 Map<String, Container> containers = _getSteps(costData, program, programPath); 312 313 costData.addSteps(containers, program); 314 315 if (containers.isEmpty()) 316 { 317 getLogger().warn("[{}] La formation n'a pas de conteneur compatible pour le calcul de pilotage.", programPath); 318 } 319 else 320 { 321 getLogger().info("[{}] Traitement des conteneurs...", programPath); 322 VolumesOfHours volumeOfHours = new VolumesOfHours(program.getId()); 323 EqTD eqTD = new EqTD(program.getId()); 324 Effectives effectives = new Effectives(program.getId()); 325 326 for (String containerPath : containers.keySet()) 327 { 328 Container container = containers.get(containerPath); 329 // Met en mémoire l'étape actuelle 330 cache.setCurrentStep(container); 331 332 // Parcours les étapes de la formation 333 _populateStep(costData, container, containerPath, cache); 334 335 // Somme les effectifs de chaque étape pour calculer les effectifs totaux de la formation 336 Effectives childEffective = costData.getEffective(container.getId()); 337 effectives.sum(childEffective, containerPath, programPath); 338 339 // Calcule les volumes horaires 340 VolumesOfHours childVolumeOfHour = costData.getVolumesOfHours(container.getId()); 341 volumeOfHours.sum(childVolumeOfHour, 1d); 342 343 // Calcule les heures eqTD 344 EqTD childEqTD = costData.getEqTD(container.getId()); 345 eqTD.sum(childEqTD, 1d, containerPath, programPath); 346 } 347 348 // Stock les informations 349 costData.addEqTD(program.getId(), eqTD); 350 costData.addVolumeOfHours(program.getId(), volumeOfHours); 351 costData.setEffective(program.getId(), effectives); 352 Optional<Double> localEffective = costData.getComputedLocalEffective(program.getId(), programPath); 353 Double heReport = localEffective.isPresent() && localEffective.get() != 0 ? eqTD.getProRatedEqTD(programPath) / localEffective.get() : 0; 354 costData.addHeReport(programPath, heReport); 355 } 356 // Lorsque toutes les valeurs des containers sont calculées, on ventile au niveau des subProgram 357 _populateSubProgram(costData, program, programPath, cache); 358 } 359 360 361 /** 362 * Iterate and explore each sub program of the catalog 363 * @param program the catalog 364 * @param costData object containing informations about the formation 365 * @param programPath the current path 366 * @param cache the cache values 367 */ 368 protected void _populateSubProgram(CostComputationData costData, Program program, String programPath, CostComputationDataCache cache) 369 { 370 Set<SubProgram> subPrograms = _odfHelper.getChildSubPrograms(program); 371 for (SubProgram subProgram : subPrograms) 372 { 373 String programItemPath = programPath + ModelItem.ITEM_PATH_SEPARATOR + subProgram.getName(); 374 Map<String, Container> containers = _getSteps(costData, subProgram, programItemPath); 375 376 VolumesOfHours volumeOfHours = new VolumesOfHours(subProgram.getId()); 377 EqTD eqTD = new EqTD(subProgram.getId()); 378 Effectives effectives = new Effectives(subProgram.getId()); 379 380 for (String containerPath : containers.keySet()) 381 { 382 Container container = containers.get(containerPath); 383 384 // Somme les effectifs de chaque étape pour calculer les effectifs totaux de la formation 385 Effectives childEffective = costData.getEffective(container.getId()); 386 effectives.sum(childEffective, containerPath, programItemPath); 387 388 // Calcule les heures eqTD 389 EqTD childEqTD = costData.getEqTD(container.getId()); 390 eqTD.sum(childEqTD, 1d, containerPath, programItemPath); 391 } 392 393 // Stock les informations 394 costData.addEqTD(subProgram.getId(), eqTD); 395 costData.addVolumeOfHours(subProgram.getId(), volumeOfHours); 396 costData.setEffective(subProgram.getId(), effectives); 397 Optional<Double> localEffective = costData.getComputedLocalEffective(subProgram.getId(), programItemPath); 398 Double heReport = localEffective.isPresent() && localEffective.get() != 0 ? eqTD.getProRatedEqTD(programItemPath) / localEffective.get() : 0; 399 costData.addHeReport(programItemPath, heReport); 400 } 401 } 402 403 /** 404 * Iterate recursively each child of the current programItem to find the lowest course level 405 * @param costData object containing informations about the formation 406 * @param objectPath the current path 407 * @param programItem the current node 408 * @param cache the cache values 409 */ 410 protected void _populateStep(CostComputationData costData, ProgramItem programItem, String objectPath, CostComputationDataCache cache) 411 { 412 getLogger().info("[{}] Exploration de l'arborescence...", objectPath); 413 414 // On parcourt récursivement l'arbre de données jusqu'au cours de plus bas niveau 415 VolumesOfHours volumeOfHours = new VolumesOfHours(programItem.getId()); 416 EqTD eqTD = costData.getEqTD(programItem.getId()) != null ? costData.getEqTD(programItem.getId()) : new EqTD(programItem.getId()); 417 for (ProgramItem child : _odfHelper.getChildProgramItems(programItem)) 418 { 419 Double weight = 1D; 420 String childPath = objectPath + ModelItem.ITEM_PATH_SEPARATOR + child.getName(); 421 422 if (child instanceof Course && !((Course) child).hasCourseLists()) 423 { 424 // Parcourt les cours de plus bas niveau 425 _populateCourse(costData, (Course) child, childPath, cache); 426 } 427 else 428 { 429 // Si le programItem n'est pas un cours sans enfant, on continue recursivement 430 _populateStep(costData, child, childPath, cache); 431 } 432 433 EqTD childEqTD = costData.getEqTD(child.getId()); 434 // Calcule les heures eqTD 435 if (child instanceof Course) 436 { 437 // Calcul du poids 438 weight = Optional.of(cache).map(c -> c.getWeight(childPath)).orElse(1d); 439 440 Container currentStepholder = cache.getCurrentStep(); 441 Optional<ContentValue> stepHolder = Optional.of(child) // Pour l'ELP courant 442 .map(Course.class::cast) // Cast en Course 443 .map(c -> c.<ContentValue>getValue(__ETAPE_PORTEUSE)); // Récupérer l'étape porteuse 444 445 boolean isHolded = stepHolder.isEmpty() 446 || stepHolder 447 .flatMap(ContentValue::getContentIfExists) // Récupérer le contenu s'il existe 448 .map(Container.class::cast) // Cast en Container 449 .map(currentStepholder::equals) // Est-ce que l'étape courante = étape porteuse ? 450 .orElse(false); // Sinon non porté 451 452 // Si l'étape porteuse du cours n'est pas l'étape courante, la valeur de ses eqTD portés est de 0 453 if (!isHolded) 454 { 455 childEqTD.addLocalEqTD(childPath, 0d); 456 } 457 } 458 eqTD.sum(childEqTD, weight, childPath, objectPath); 459 460 // Calcule les volumes horaires 461 VolumesOfHours childVolumeOfHours = costData.getVolumesOfHours(child.getId()); 462 volumeOfHours.sum(childVolumeOfHours, weight); 463 } 464 465 // Stock les informations 466 costData.addEqTD(programItem.getId(), eqTD); 467 Optional<Double> localEffective = costData.getComputedLocalEffective(programItem.getId(), objectPath); 468 Double heReport = localEffective.isPresent() && localEffective.get() != 0 ? eqTD.getProRatedEqTD(objectPath) / localEffective.get() : 0; 469 costData.addHeReport(objectPath, heReport); 470 costData.addVolumeOfHours(programItem.getId(), volumeOfHours); 471 } 472 473 /** 474 * Explore the course and iterate its course part to populate them 475 * @param costData object containing informations about the formation 476 * @param coursePath the current path 477 * @param course the course 478 * @param cache the cache values 479 */ 480 protected void _populateCourse(CostComputationData costData, Course course, String coursePath, CostComputationDataCache cache) 481 { 482 getLogger().info("[{} - {}] Lecture de l'ELP...", coursePath, course.getCode()); 483 484 VolumesOfHours volumeOfHour = new VolumesOfHours(course.getId()); 485 EqTD eqTD = costData.getEqTD(course.getId()) != null ? costData.getEqTD(course.getId()) : new EqTD(course.getId()); 486 for (CoursePart coursePart : course.getCourseParts()) 487 { 488 String coursePartPath = coursePath + ModelItem.ITEM_PATH_SEPARATOR + coursePart.getName(); 489 490 getLogger().info("[{} - {}] Lecture des heures d'enseignement...", coursePartPath, coursePart.getCode()); 491 492 _populateCoursePart(costData, coursePart, course, coursePartPath, cache); 493 494 // Calcule les volumes horaires 495 VolumesOfHours childVolumeOfHours = costData.getVolumesOfHours(coursePart.getId()); 496 volumeOfHour.sum(childVolumeOfHours, 1d); 497 498 // Calcule les heures eqTD 499 EqTD childEqTD = costData.getEqTD(coursePart.getId()); 500 eqTD.sum(childEqTD, 1d, coursePartPath, coursePath); 501 } 502 // Stock les informations 503 costData.addEqTD(course.getId(), eqTD); 504 Optional<Double> localEffective = costData.getComputedLocalEffective(course.getId(), coursePath); 505 Double heReport = localEffective.isPresent() && localEffective.get() != 0 ? eqTD.getProRatedEqTD(coursePath) / localEffective.get() : 0; 506 costData.addHeReport(coursePath, heReport); 507 costData.addVolumeOfHours(course.getId(), volumeOfHour); 508 } 509 510 /** 511 * Fill a costData object with informations about the course and the course part 512 * @param costData object containing informations about the formation 513 * @param coursePart the object to explore 514 * @param courseParent The parent of the course part 515 * @param coursePartPath the current path 516 * @param cache the cache values 517 */ 518 protected void _populateCoursePart(CostComputationData costData, CoursePart coursePart, Course courseParent, String coursePartPath, CostComputationDataCache cache) 519 { 520 // Récupère les volumes horaires du coursePart et les ajoutes dans costData pour pouvoir les ventiler dans les autres programItem 521 VolumesOfHours volumeOfHours = new VolumesOfHours(_refTableHelper.getItemCode(coursePart.getNature())); 522 volumeOfHours.addVolumes(_refTableHelper.getItemCode(coursePart.getNature()), coursePart.getNumberOfHours()); 523 costData.addVolumeOfHours(coursePart.getId(), volumeOfHours); 524 525 // Récupération de l'ELP porteur 526 Course courseHolder = coursePart.getCourseHolder(); 527 if (courseHolder == null) 528 { 529 getLogger().warn("[{}] L'ELP porteur est soit vide, soit invalide.", coursePartPath); 530 } 531 532 // Récupération des détails de la norme 533 Container stepHolder = Optional.ofNullable(courseHolder) 534 .map(ch -> _getStepHolder(ch, coursePartPath)) 535 .orElse(null); 536 Optional<NormDetails> normDetails = _getNormDetails(stepHolder, coursePart, coursePartPath, cache); 537 if (normDetails.isEmpty()) 538 { 539 getLogger().warn("[{}] Les effectifs maximum et minimum supplémentaires ne peuvent pas être récupérés.", coursePartPath); 540 return; 541 } 542 543 // Calcule toutes les données nécessaires et les stock dans l'objet CoursePartInformation 544 _computeEffectiveAndVentilation(costData, coursePart, courseParent, normDetails.get(), coursePartPath, cache); 545 } 546 547 /** 548 * Handle all effectives computations from a course part 549 * @param costData object containing informations about the formation 550 * @param coursePart the object to explore 551 * @param courseParent the parent of the course part 552 * @param normDetails informations about the norm of the course part 553 * @param coursePartPath the course part path 554 * @param cache the cache values 555 */ 556 protected void _computeEffectiveAndVentilation(CostComputationData costData, CoursePart coursePart, Course courseParent, NormDetails normDetails, String coursePartPath, CostComputationDataCache cache) 557 558 { 559 CoursePartCostData coursePartCostData = new CoursePartCostData(); 560 561 // Récupère les volumes horaires du coursePart et les ajoutes dans costData pour pouvoir les ventiler dans les autres programItem 562 String natureId = _refTableHelper.getItemCode(coursePart.getNature()); 563 Double numberOfHours = cache.getOverriddenVolumeOfHours(coursePart.getId(), natureId).isPresent() ? cache.getOverriddenVolumeOfHours(coursePart.getId(), natureId).get() : coursePart.getNumberOfHours(); 564 VolumesOfHours volumeOfHours = new VolumesOfHours(natureId); 565 volumeOfHours.addVolumes(natureId, numberOfHours); 566 costData.addVolumeOfHours(coursePart.getId(), volumeOfHours); 567 568 // Prend en compte les effectifs saisis 569 Double enteredEffective = costData.getEnteredEffective(coursePart.getId()).isPresent() ? costData.getEnteredEffective(coursePart.getId()).get() : 0d; 570 571 // Effectifs provenant uniquement des effectifs saisis au niveau des années auquel un poids est appliqué selon le chemin emprunté 572 Effectives computedEffective = _getComputedLocalEffective(costData, coursePart, courseParent, coursePartPath, cache); 573 574 Long groupsToOpen = cache.getOverriddenGroups(coursePart.getId()).isPresent() ? cache.getOverriddenGroups(coursePart.getId()).get().longValue() : coursePart.getValue("groupsToOpen", false, 0L); 575 Long computedGroups = _computeGroups(enteredEffective, computedEffective.getComputedGlobalEffective(), normDetails); 576 Double eqTDCoef = ReportUtils.transformEqTD2Double(coursePart.getValue("eqTD", false, StringUtils.EMPTY)); 577 if (eqTDCoef == null) 578 { 579 eqTDCoef = cache.getEqTDByNature(coursePart.getNature()); 580 } 581 double eqTDTotal = _computeEqTDLocal(numberOfHours, groupsToOpen > 0 ? groupsToOpen : computedGroups, eqTDCoef); 582 583 coursePartCostData.setNormDetails(normDetails); 584 coursePartCostData.setComputedGroups(computedGroups); 585 coursePartCostData.setGroupsToOpen(groupsToOpen); 586 587 EqTD eqTD = costData.getEqTD(coursePart.getId()) != null ? costData.getEqTD(coursePart.getId()) : new EqTD(coursePart.getId()); 588 eqTD.setGlobalEqTD(eqTDTotal); 589 590 // Calcul de l'EQTD proratisé en calculant un ratio entre l'effectif local et l'effectif local. Si l'effectif est surchargé alors on le prend à la place de l'effectif global 591 Double globalEffective = costData.getEnteredEffective(coursePart.getId()).isPresent() ? costData.getEnteredEffective(coursePart.getId()).get() : computedEffective.getComputedGlobalEffective(); 592 double ratio = computedEffective.getComputedGlobalEffective() == 0 ? 0 : computedEffective.getComputedLocalEffective(coursePartPath) / globalEffective; 593 double eqTDTotalProRated = eqTDTotal * ratio; 594 eqTD.addProRatedEqTD(coursePartPath, eqTDTotalProRated); 595 596 // Heures eqTD portées 597 Course courseHolder = coursePart.getCourseHolder(); 598 Double eqTDLocal = eqTDTotal; 599 // Si un cours mutualisé n'est pas porté par l'étape courante, on ne lui 600 // affecte pas les heures eqTD de ce cours 601 if (!Objects.equals(courseHolder, courseParent)) 602 { 603 eqTDLocal = 0d; 604 } 605 eqTD.addLocalEqTD(coursePartPath, eqTDLocal); 606 costData.addEqTD(coursePart.getId(), eqTD); 607 Double heReport = costData.getComputedLocalEffective(coursePart.getId(), coursePartPath).get() != 0 ? eqTDTotalProRated / costData.getComputedLocalEffective(coursePart.getId(), coursePartPath).get() : 0; 608 costData.addHeReport(coursePartPath, heReport); 609 costData.addCoursePartCostData(coursePart, coursePartCostData); 610 _computeEqTDVentilation(costData, coursePart, enteredEffective, globalEffective, eqTDTotal); 611 } 612 613 /** 614 * Compute the effective and the eqTD ventilation by step 615 * @param costData object containing informations about the formation 616 * @param coursePart the object to explore 617 * @param enteredEffective the entered effective of the coursePart 618 * @param globalEffective the global effective of the coursePart 619 * @param eqTDTotal the total eqTD value of the coursePart 620 */ 621 protected void _computeEqTDVentilation(CostComputationData costData, CoursePart coursePart, Double enteredEffective, Double globalEffective, Double eqTDTotal) 622 { 623 Map<Container, Double> ventilationEffective = costData.getEffectiveByStep(coursePart.getId()); 624 625 // Ventilation eqTD 626 Map<Container, Double> ventilationEqTD = new HashMap<>(); 627 for (Map.Entry<Container, Double> effectifsForStep : ventilationEffective.entrySet()) 628 { 629 Double effectif = effectifsForStep.getValue(); 630 double effectifTotal = effectif > enteredEffective ? globalEffective : enteredEffective; 631 double eqTDVentile = _calculEqTD(effectif, effectifTotal, eqTDTotal); 632 ventilationEqTD.put(effectifsForStep.getKey(), eqTDVentile); 633 } 634 ventilationEqTD.values().removeIf(Objects::isNull); 635 costData.addEqTDByStep(coursePart.getId(), ventilationEqTD); 636 } 637 638 /** 639 * Divide the effective of a program item by the total effective of a course list to retrieve the weight 640 * @param programItem the program item 641 * @param programItemParent the parent of the program item 642 * @param costData object containing informations about the formation 643 * @return the weight of the program item 644 */ 645 protected Double _getCourseWeight(ProgramItem programItem, ProgramItem programItemParent, CostComputationData costData) 646 { 647 Double totalEffective = 0d; 648 Double weight = 1d; 649 if (programItemParent instanceof CourseList && ((CourseList) programItemParent).getType() == ChoiceType.CHOICE) 650 { 651 CourseList courseList = (CourseList) programItemParent; 652 List<Course> children = courseList.getCourses(); 653 for (Course course : children) 654 { 655 Optional<Double> enteredEffective = costData.getEnteredEffective(course.getId()); 656 Optional<Double> globalComputedEffective = costData.getGlobalComputedEffective(course.getId()); 657 totalEffective += enteredEffective.isPresent() ? enteredEffective.get() : globalComputedEffective.isPresent() ? globalComputedEffective.get() : 0d; 658 } 659 // Si un effectif est saisi, on calcule le poids à partir de ce dernier 660 if (costData.getEnteredEffective(programItem.getId()).isPresent() && totalEffective > 0) 661 { 662 weight = costData.getEnteredEffective(programItem.getId()).get() / totalEffective; 663 } 664 // Sinon on utilise l'effectif global 665 else if (costData.getGlobalComputedEffective(programItem.getId()).isPresent() && totalEffective > 0) 666 { 667 weight = costData.getGlobalComputedEffective(programItem.getId()).get() / totalEffective; 668 } 669 } 670 return weight; 671 } 672 673 /** 674 * Get the step holder associated to the course 675 * @param courseHolder the course holder of a course part 676 * @param coursePartPrefix the current prefix 677 * @return the step holder 678 */ 679 protected Container _getStepHolder(Course courseHolder, String coursePartPrefix) 680 { 681 Set<Container> stepsHolder = _getStepsHolder(courseHolder, coursePartPrefix); 682 Container stepHolder = null; 683 if (stepsHolder.size() == 1) 684 { 685 stepHolder = stepsHolder.iterator().next(); 686 } 687 else if (stepsHolder.isEmpty()) 688 { 689 getLogger().warn("Aucune étape n'est rattachée à l'ELP '{}' directement ou indirectement", courseHolder.getTitle()); 690 } 691 else 692 { 693 getLogger().warn("Plusieurs étapes sont rattachées à l'ELP '{}', impossible de déterminer laquelle est porteuse.", courseHolder.getTitle()); 694 } 695 return stepHolder; 696 697 } 698 699 /** 700 * Retrieve the value of the norm 701 * @param coursePart informations about the teaching hours of a course 702 * @param coursePartPrefix the current prefix 703 * @param stepHolder the step holder of the course 704 * @return the norm value 705 * @param cache the cache values 706 */ 707 protected String _getNorm(CoursePart coursePart, String coursePartPrefix, Container stepHolder, CostComputationDataCache cache) 708 { 709 String norm = _getNorm(coursePart, coursePart.getNature(), coursePartPrefix, false, cache); 710 if (norm == null && stepHolder != null) 711 { 712 norm = _getNorm(stepHolder, coursePart.getNature(), coursePartPrefix, true, cache); 713 } 714 return norm; 715 } 716 717 /** 718 * Get all informations about the norm 719 * @param stepHolder the step holder 720 * @param coursePart informations about the teaching hours of a course 721 * @param coursePartPrefix the current prefix 722 * @return norm details 723 * @param cache the cache values 724 */ 725 protected Optional<NormDetails> _getNormDetails(Container stepHolder, CoursePart coursePart, String coursePartPrefix, CostComputationDataCache cache) 726 { 727 // Récupération de la norme 728 String norm = _getNorm(coursePart, coursePartPrefix, stepHolder, cache); 729 String nature = coursePart.getNature(); 730 731 return Optional.ofNullable(norm) 732 .map(n -> cache.getNorms(n, nature)) 733 .or(() -> _getNormFromNature(nature, cache)); 734 } 735 736 private Optional<NormDetails> _getNormFromNature(String nature, CostComputationDataCache cache) 737 { 738 return Optional.of(nature) 739 .map(cache::getEffectiveMinMaxByNature) 740 .map(eff -> 741 new NormDetails( 742 eff.get("effectifMinSup"), 743 eff.get("effectifMax"), 744 StringUtils.EMPTY 745 ) 746 ); 747 } 748 749 /** 750 * Get the computed global and entered effectives 751 * @param costData object containing informations about the formation 752 * @param coursePart the course part 753 * @param cache the cached values 754 * @return the computed global and entered effectives 755 */ 756 protected Effectives _getComputedGlobalAndEnteredEffectives(CostComputationData costData, CoursePart coursePart, CostComputationDataCache cache) 757 { 758 // Consulte le cache pour vérifier si un effectif a déjà été calculé pour ce coursePart 759 Effectives computedEffective = cache.getComputedEffectiveCache(coursePart.getId()); 760 if (computedEffective == null) 761 { 762 computedEffective = new Effectives(coursePart.getId()); 763 764 for (Course course : coursePart.getCourses()) 765 { 766 computedEffective.sumGlobalEffective(_computeGlobalAndEnteredEffectives(costData, coursePart, course, 1d, cache).getComputedGlobalEffective()); 767 costData.add(course.getId(), coursePart.getId()); 768 } 769 770 cache.putComputedEffectiveCache(coursePart.getId(), computedEffective); 771 costData.setEffective(coursePart.getId(), computedEffective); 772 773 } 774 return computedEffective; 775 } 776 777 /** 778 * Compute the global and entered effectives 779 * @param costData object containing informations about the formation 780 * @param coursePart the course part 781 * @param programItem the item we want to evaluate the capacity 782 * @param initialWeight the weight 783 * @param cache the cached values 784 * @return the computed effective 785 */ 786 protected Effectives _computeGlobalAndEnteredEffectives(CostComputationData costData, CoursePart coursePart, ProgramItem programItem, Double initialWeight, CostComputationDataCache cache) 787 { 788 Effectives computedEffective = cache.getComputedEffectiveCache(programItem.getId()); 789 if (computedEffective == null) 790 { 791 computedEffective = new Effectives(programItem.getId()); 792 Double enteredEffective = programItem instanceof CourseList || ((Content) programItem).getValue(__EFFECTIF_ESTIMATED, false, 0L).doubleValue() == 0d ? null : ((Content) programItem).getValue(__EFFECTIF_ESTIMATED, false, 0L).doubleValue(); 793 cache.putEnteredEffectiveCache(programItem.getId(), enteredEffective); 794 computedEffective.setEnteredEffective(Optional.ofNullable(enteredEffective)); 795 if (programItem instanceof Container && _refTableHelper.getItemCode(((Container) programItem).getNature()).equals(__VALUE_YEAR)) 796 { 797 // Add the etape to course part information 798 Double effectiveEstimated = cache.getOverriddenEffective(programItem.getId()).isPresent() ? cache.getOverriddenEffective(programItem.getId()).get() : ((Content) programItem).getValue(__EFFECTIF_ESTIMATED, false, 0L).doubleValue(); 799 computedEffective.sumGlobalEffective(effectiveEstimated); 800 costData.add(effectiveEstimated, (Container) programItem, programItem); 801 802 if (effectiveEstimated == 0) 803 { 804 getLogger().warn("[{}] Aucun effectif n'a été saisi sur cette étape.", ((Container) programItem).getTitle()); 805 } 806 } 807 else 808 { 809 double weight = _getWeight(programItem); 810 for (ProgramItem parent : _odfHelper.getParentProgramItems(programItem)) 811 { 812 computedEffective.sumGlobalEffective(_computeGlobalAndEnteredEffectives(costData, coursePart, parent, weight, cache).getComputedGlobalEffective()); 813 costData.add(parent.getId(), programItem.getId()); 814 } 815 } 816 costData.addGlobalComputedEffective(programItem.getId(), computedEffective.getComputedGlobalEffective()); 817 costData.addEnteredEffective(programItem.getId(), computedEffective.getEnteredEffective()); 818 cache.putComputedEffectiveCache(programItem.getId(), computedEffective); 819 } 820 computedEffective.globalhWeight(initialWeight); 821 costData.cloneWithWeight(initialWeight, programItem); 822 return computedEffective; 823 } 824 825 /** 826 * Get the computed local effective 827 * @param costData object containing informations about the formation 828 * @param coursePart the course part 829 * @param courseParent the course parent of the course part 830 * @param coursePartPath the course part path 831 * @param cache the cache values 832 * @return the computed effective 833 */ 834 protected Effectives _getComputedLocalEffective(CostComputationData costData, CoursePart coursePart, Course courseParent, String coursePartPath, CostComputationDataCache cache) 835 { 836 // Consulte le cache pour vérifier si un effectif a déjà été calculé pour ce coursePart 837 Effectives computedEffective = cache.getComputedEffectiveCache(coursePart.getId()) == null ? new Effectives(coursePart.getId()) : cache.getComputedEffectiveCache(coursePart.getId()); 838 839 String coursePath = StringUtils.substringBeforeLast(coursePartPath, ModelItem.ITEM_PATH_SEPARATOR); 840 841 Effectives currentEffectif = _computeLocalEffective(costData, coursePart, courseParent, coursePath, cache); 842 843 // Somme la valeur des effectifs du cours parent à son coursePart 844 computedEffective.sumLocalEffective(currentEffectif, coursePath, coursePartPath); 845 cache.putComputedEffectiveCache(coursePart.getId(), computedEffective); 846 costData.addLocalComputedEffective(coursePart.getId(), coursePartPath, computedEffective.getComputedLocalEffective(coursePartPath)); 847 return computedEffective; 848 } 849 850 /** 851 * Compute the local effective 852 * @param costData object containing informations about the formation 853 * @param coursePart the course part 854 * @param programItem the item we want to evaluate the capacity 855 * @param programItemPath the current path 856 * @param cache the cache values 857 * @return the computed effective 858 */ 859 protected Effectives _computeLocalEffective(CostComputationData costData, CoursePart coursePart, ProgramItem programItem, String programItemPath, CostComputationDataCache cache) 860 { 861 Effectives computedEffective = cache.getComputedEffectiveCache(programItem.getId()) != null ? cache.getComputedEffectiveCache(programItem.getId()) : new Effectives(programItem.getId()); 862 if (!computedEffective.containsComputedLocalEffective(programItemPath)) 863 { 864 if (programItem instanceof Container && _refTableHelper.getItemCode(((Container) programItem).getNature()).equals(__VALUE_YEAR)) 865 { 866 // numberOfStudentsEstimated est de type Long mais le getDouble sur une propriété Long en JCR fonctionne très bien et fait un #doubleValue() sur le Long 867 Double effectiveEstimated = cache.getOverriddenEffective(programItem.getId()).isPresent() ? cache.getOverriddenEffective(programItem.getId()).get() : ((Content) programItem).getValue(__EFFECTIF_ESTIMATED, false, 0L).doubleValue(); 868 computedEffective.addComputedLocalEffective(programItemPath, effectiveEstimated); 869 costData.addLocalComputedEffective(programItem.getId(), programItemPath, computedEffective.getComputedLocalEffective(programItemPath)); 870 cache.putComputedEffectiveCache(programItem.getId(), computedEffective); 871 if (effectiveEstimated == 0) 872 { 873 getLogger().warn("[{}] Aucun effectif n'a été saisi sur cette étape.", ((Container) programItem).getTitle()); 874 } 875 } 876 else 877 { 878 // Parcourt l'intégralité des parents du programItem (globaux et locaux) pour gérer la mutualisation 879 for (ProgramItem parent : _odfHelper.getParentProgramItems(programItem)) 880 { 881 // Détermine le path du parent à partir du path enfant 882 String parentPath = StringUtils.substringBeforeLast(programItemPath, ModelItem.ITEM_PATH_SEPARATOR); 883 // Détermine le nom du parent à partir du path 884 String parentName = StringUtils.substringAfterLast(parentPath, ModelItem.ITEM_PATH_SEPARATOR); 885 // Si le nom du parent contenu dans le path courant est le même que le nom du parent retrouvé par le helper, nous sommes dans la structure locale 886 if (parentName.equals(parent.getName())) 887 { 888 Effectives effective = _computeLocalEffective(costData, coursePart, parent, parentPath, cache); 889 890 // Ajoute la valeur de l'effectif local du parent à celle du programItem 891 computedEffective.sumLocalEffective(effective, parentPath, programItemPath); 892 // Détermine le poids du programItem et l'applique à l'effectif local 893 _computeLocalEffectiveByWeight(costData, programItem, parent, computedEffective, programItemPath, cache); 894 } 895 } 896 } 897 } 898 return computedEffective; 899 } 900 901 /** 902 * Compute the local effective of a programItem 903 * @param costData object containing informations about the formation 904 * @param programItem the current programItem 905 * @param programItemParent the parent of the programItem 906 * @param computedEffective the computed effective 907 * @param programItemPath the current path 908 * @param cache the values cache 909 */ 910 protected void _computeLocalEffectiveByWeight(CostComputationData costData, ProgramItem programItem, ProgramItem programItemParent, Effectives computedEffective, String programItemPath, CostComputationDataCache cache) 911 { 912 Double weight = _getCourseWeight(programItem, programItemParent, costData); 913 cache.putWeight(programItemPath, weight); 914 computedEffective.localWeight(weight, programItemPath); 915 costData.addLocalComputedEffective(programItem.getId(), programItemPath, computedEffective.getComputedLocalEffective(programItemPath)); 916 cache.putComputedEffectiveCache(programItem.getId(), computedEffective); 917 } 918 919 /** 920 * Get all steps of a program item 921 * @param costData object containing informations about the formation 922 * @param programItem the current node 923 * @param programItemPath the current path 924 * @return all steps of a program item 925 */ 926 protected Map<String, Container> _getSteps(CostComputationData costData, ProgramItem programItem, String programItemPath) 927 { 928 Map<String, Container> steps = new HashMap<>(); 929 for (ProgramItem child : _odfHelper.getChildProgramItems(programItem)) 930 { 931 if (child instanceof Container && _refTableHelper.getItemCode(((Container) child).getNature()).equals(__VALUE_YEAR)) 932 { 933 Container container = (Container) child; 934 String containerPath = programItemPath + ModelItem.ITEM_PATH_SEPARATOR + container.getName(); 935 steps.put(containerPath, container); 936 } 937 else if (child instanceof Container || child instanceof SubProgram) 938 { 939 steps.putAll(_getSteps(costData, child, programItemPath + ModelItem.ITEM_PATH_SEPARATOR + child.getName())); 940 } 941 } 942 return steps; 943 } 944 945 /** 946 * Define the weight of an item 947 * @param programItem the item to evaluate 948 * @return the weight 949 */ 950 protected double _getWeight(ProgramItem programItem) 951 { 952 double weight = 1.0; 953 if (programItem instanceof CourseList) 954 { 955 CourseList courseList = (CourseList) programItem; 956 ChoiceType type = courseList.getType(); 957 958 switch (type) 959 { 960 case OPTIONAL: 961 // Si c'est une liste optionnelle, les effectifs et groupes ne sont pas calculés 962 weight = 0.0; 963 break; 964 case CHOICE: 965 // Si c'est une liste à choix, le poids est calculé 966 long min = courseList.getMinNumberOfCourses(); 967 long max = courseList.getMaxNumberOfCourses(); 968 long size = courseList.getCourses().size(); 969 970 if (min != max) 971 { 972 getLogger().warn("[{}] La liste contient min={} et max={}. Retenu {}", courseList.getTitle(), min, max, min); 973 } 974 975 if (min > size) 976 { 977 getLogger().warn("[{}] La liste contient min={} mais n'a que {} éléments.", courseList.getTitle(), min, size); 978 } 979 else 980 { 981 weight = (double) min / (double) size; 982 } 983 break; 984 default: 985 // Si c'est une liste obligatoire (ou de type null), le poids reste à 1 986 break; 987 } 988 } 989 return weight; 990 } 991 992 /** 993 * Compute the TD equivalent 994 * @param effectif the capacity 995 * @param effectifTotal the total capacity 996 * @param eqTDTotal the total TD equivalent 997 * @return the computed TD equivalent 998 */ 999 protected double _calculEqTD(double effectif, double effectifTotal, double eqTDTotal) 1000 { 1001 if (effectifTotal == 0.0) 1002 { 1003 return 0.0; 1004 } 1005 return eqTDTotal * effectif / effectifTotal; 1006 } 1007 1008 /** 1009 * Compute the total TD equivalent 1010 * @param duration the duration of the TD 1011 * @param groupes the group repartition 1012 * @param eqTD the TD equivalent 1013 * @return the computed TD equivalent 1014 */ 1015 protected double _computeEqTDLocal(double duration, long groupes, Double eqTD) 1016 { 1017 if (eqTD != null) 1018 { 1019 return duration * groupes * eqTD; 1020 } 1021 return 0; 1022 } 1023 1024 /** 1025 * compute the number of groups needed 1026 * @param enteredEffective the forecast capacity 1027 * @param computedEffective the computed capacity 1028 * @param normDetails all informations needed to compute the norm 1029 * @return the number of groups to open 1030 */ 1031 protected long _computeGroups(double enteredEffective, double computedEffective, NormDetails normDetails) 1032 { 1033 double effective = enteredEffective > 0 ? enteredEffective : computedEffective; 1034 long effectiveMax = normDetails.getEffectiveMax(); 1035 long effectiveMinSup = normDetails.getEffectiveMinSup(); 1036 if (effective == 0) 1037 { 1038 return 0; 1039 } 1040 1041 if (effective < effectiveMax) 1042 { 1043 return 1; 1044 } 1045 1046 long groups = (long) effective / effectiveMax; 1047 if (effective - groups * effectiveMax >= effectiveMinSup) 1048 { 1049 groups++; 1050 } 1051 1052 return groups; 1053 } 1054 1055 /** 1056 * Find steps holders 1057 * @param programItem the item we want to explore 1058 * @param coursePartPrefix the current prefix 1059 * @return a list of steps holders 1060 */ 1061 protected Set<Container> _getStepsHolder(ProgramItem programItem, String coursePartPrefix) 1062 { 1063 // Search if the current element is a course and has a step holder 1064 if (programItem instanceof Course) 1065 { 1066 Course course = (Course) programItem; 1067 ContentValue stepHolder = course.getValue(__ETAPE_PORTEUSE); 1068 if (stepHolder != null) 1069 { 1070 getLogger().info("[{}] L'ELP {} ({}) contient une étape porteuse.", coursePartPrefix, course.getTitle(), course.getId()); 1071 1072 try 1073 { 1074 return Collections.singleton((Container) stepHolder.getContent()); 1075 } 1076 catch (AmetysRepositoryException e) 1077 { 1078 getLogger().warn("[{}] L'étape porteuse {} référencée par l'ELP {} ({}) n'a pas été trouvée. Vérifiez qu'elle n'a pas été supprimée.", coursePartPrefix, stepHolder.getContentId(), course.getTitle(), course.getId()); 1079 } 1080 } 1081 } 1082 // Search if the current element is a container and is of type year 1083 else if (programItem instanceof Container) 1084 { 1085 Container container = (Container) programItem; 1086 if (_refTableHelper.getItemCode(container.getNature()).equals(__VALUE_YEAR)) 1087 { 1088 return Collections.singleton(container); 1089 } 1090 } 1091 1092 // In all other cases, search in the parent elements 1093 return _odfHelper.getParentProgramItems(programItem) 1094 .stream() 1095 .map(child -> _getStepsHolder(child, coursePartPrefix)) 1096 .flatMap(Set::stream) 1097 .collect(Collectors.toSet()); 1098 } 1099 1100 /** 1101 * Retrieve the norm value of a content 1102 * @param content the content 1103 * @param nature the nature of the content 1104 * @param prefix the current prefix 1105 * @param isStepholder define if the content is a step holder or not 1106 * @param cache the cache values 1107 * @return the norm of the content 1108 */ 1109 protected String _getNorm(Content content, String nature, String prefix, boolean isStepholder, CostComputationDataCache cache) 1110 { 1111 String logName = isStepholder ? "étape porteuse" : "heure d'enseignement"; 1112 1113 ContentValue normValue = content.getValue("norme"); 1114 String normId = Optional.ofNullable(normValue) 1115 .map(ContentValue::getContentId) 1116 .orElse(StringUtils.EMPTY); 1117 1118 if (StringUtils.isNotEmpty(normId)) 1119 { 1120 if (!cache.normsContainsNormIdKey(normId)) 1121 { 1122 getLogger().warn("[{}] L'{} '{}' possède une norme invalide.", prefix, logName, content.getTitle()); 1123 } 1124 else if (!cache.normsContainsNatureIdKey(normId, nature)) 1125 { 1126 if (getLogger().isWarnEnabled()) 1127 { 1128 Content norm = normValue.getContent(); 1129 getLogger().warn("[{}] La norme '{}' n'est pas définie pour la nature d'enseignement {}.", prefix, norm.getTitle(), _refTableHelper.getItemCode(nature)); 1130 } 1131 } 1132 else 1133 { 1134 return normId; 1135 } 1136 } 1137 else 1138 { 1139 getLogger().info("[{}] L'{} '{}' n'a pas de norme associée.", prefix, logName, content.getTitle()); 1140 } 1141 return null; 1142 } 1143 1144 /** 1145 * Retrieve the orgUnit 1146 * @param courseHolder the course 1147 * @return the orgUnit 1148 */ 1149 protected String _getOrgUnits(Course courseHolder) 1150 { 1151 String orgUnits = StringUtils.EMPTY; 1152 for (String orgUnitId : courseHolder.getOrgUnits()) 1153 { 1154 if (StringUtils.isNotEmpty(orgUnitId)) 1155 { 1156 if (!orgUnits.isEmpty()) 1157 { 1158 orgUnits += ", "; 1159 } 1160 try 1161 { 1162 OrgUnit orgUnit = _resolver.resolveById(orgUnitId); 1163 orgUnits += orgUnit.getTitle(); 1164 } 1165 catch (UnknownAmetysObjectException e) 1166 { 1167 // Ignore orgUnit 1168 getLogger().error("Impossible de retrouver la composante : {}", orgUnitId, e); 1169 } 1170 } 1171 } 1172 1173 return orgUnits; 1174 } 1175}