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 */ 016package org.ametys.plugins.odfpilotage.report.impl; 017 018import java.io.File; 019import java.io.FileOutputStream; 020import java.util.Collections; 021import java.util.Comparator; 022import java.util.HashMap; 023import java.util.List; 024import java.util.Map; 025import java.util.Map.Entry; 026import java.util.Objects; 027import java.util.Optional; 028import java.util.Set; 029import java.util.TreeMap; 030import java.util.stream.Collectors; 031 032import javax.xml.transform.Result; 033import javax.xml.transform.TransformerFactory; 034import javax.xml.transform.sax.SAXTransformerFactory; 035import javax.xml.transform.sax.TransformerHandler; 036import javax.xml.transform.stream.StreamResult; 037 038import org.apache.avalon.framework.service.ServiceException; 039import org.apache.avalon.framework.service.ServiceManager; 040import org.apache.cocoon.xml.AttributesImpl; 041import org.apache.cocoon.xml.XMLUtils; 042import org.apache.commons.io.FileUtils; 043import org.apache.commons.lang3.StringUtils; 044import org.xml.sax.SAXException; 045 046import org.ametys.cms.data.ContentValue; 047import org.ametys.core.util.DateUtils; 048import org.ametys.odf.ProgramItem; 049import org.ametys.odf.course.Course; 050import org.ametys.odf.courselist.CourseList; 051import org.ametys.odf.courselist.CourseList.ChoiceType; 052import org.ametys.odf.coursepart.CoursePart; 053import org.ametys.odf.enumeration.OdfReferenceTableEntry; 054import org.ametys.odf.orgunit.OrgUnit; 055import org.ametys.odf.program.Container; 056import org.ametys.odf.program.Program; 057import org.ametys.odf.program.SubProgram; 058import org.ametys.plugins.odfpilotage.cost.CostComputationComponent; 059import org.ametys.plugins.odfpilotage.cost.entity.CostComputationData; 060import org.ametys.plugins.odfpilotage.cost.entity.CoursePartCostData; 061import org.ametys.plugins.odfpilotage.helper.ReportUtils; 062import org.ametys.plugins.repository.AmetysObjectIterable; 063import org.ametys.plugins.repository.AmetysObjectIterator; 064import org.ametys.plugins.repository.AmetysRepositoryException; 065import org.ametys.plugins.repository.UnknownAmetysObjectException; 066 067/** 068 * Pilotage report for cost model 069 */ 070public class CoutMaquettesReport extends AbstractReport 071{ 072 private static final String __ETAPE_PORTEUSE = "etapePorteuse"; 073 private static final String __VALUE_YEAR = "annee"; 074 075 /** CalculerEffectifComponent */ 076 protected CostComputationComponent _costComputationComponent; 077 078 private int _order; 079 080 081 @Override 082 public void service(ServiceManager manager) throws ServiceException 083 { 084 super.service(manager); 085 _costComputationComponent = (CostComputationComponent) manager.lookup(CostComputationComponent.ROLE); 086 } 087 088 @Override 089 protected String getType() 090 { 091 return "coutmaquettes"; 092 } 093 094 @Override 095 protected Set<String> getSupportedOutputFormats() 096 { 097 return Set.of(OUTPUT_FORMAT_XLS); 098 } 099 100 @Override 101 protected void _launchByOrgUnit(String uaiCode, String catalog, String lang) throws Exception 102 { 103 AmetysObjectIterable<OrgUnit> orgUnits = _reportHelper.getRootOrgUnitsByUaiCode(uaiCode); 104 AmetysObjectIterator<OrgUnit> orgUnitsIterator = orgUnits.iterator(); 105 106 List<Program> selectedPrograms = _reportHelper.filterProgramsFromOrgUnits(orgUnitsIterator.next(), lang, catalog); 107 CostComputationData costData = _costComputationComponent.computeCostsOnPrograms(selectedPrograms); 108 109 _writeCoutMaquettesReport(uaiCode, catalog, lang, costData); 110 } 111 112 /** 113 * Create the groups' report for one organization unit 114 * @param uaiCode the reference to the organization unit whose formations are going to be saxed 115 * @param catalog the catalog 116 * @param lang The language code 117 * @param costData object containing informations about the formation 118 */ 119 private void _writeCoutMaquettesReport(String uaiCode, String catalog, String lang, CostComputationData costData) 120 { 121 SAXTransformerFactory factory = (SAXTransformerFactory) TransformerFactory.newInstance(); 122 String fileName = _getReportFileName(catalog, lang, _reportHelper.getAccronymOrUaiCode(uaiCode)); 123 124 // Delete old files 125 File xmlFile = new File(_tmpFolder, fileName + ".xml"); 126 FileUtils.deleteQuietly(xmlFile); 127 128 // Write XML file 129 try (FileOutputStream fos = new FileOutputStream(xmlFile)) 130 { 131 TransformerHandler handler = factory.newTransformerHandler(); 132 133 // Prepare the transformation 134 Result result = new StreamResult(fos); 135 handler.setResult(result); 136 handler.startDocument(); 137 138 AttributesImpl attrs = new AttributesImpl(); 139 attrs.addCDATAAttribute("type", getType()); 140 attrs.addCDATAAttribute("date", _reportHelper.getReadableCurrentDate()); 141 XMLUtils.startElement(handler, "report", attrs); 142 143 _generateReport(handler, costData, uaiCode, lang, catalog); 144 145 XMLUtils.endElement(handler, "report"); 146 handler.endDocument(); 147 148 // Convert the report to configured output format 149 convertReport(_tmpFolder, fileName, xmlFile); 150 } 151 catch (Exception e) 152 { 153 getLogger().error("An error occured while generating 'Coût des maquettes' report for orgunit '{}'", uaiCode, e); 154 } 155 finally 156 { 157 FileUtils.deleteQuietly(xmlFile); 158 } 159 } 160 161 private void _generateReport(TransformerHandler handler, CostComputationData costData, String uaiCode, String lang, String catalog) 162 { 163 if (StringUtils.isEmpty(uaiCode)) 164 { 165 throw new IllegalArgumentException("Cannot process the Apogee report without the uai code.The processing of the Apogee report is aborted."); 166 } 167 168 try 169 { 170 Map<Program, Object> contentsTree = _getStructure(uaiCode, lang, catalog); 171 if (contentsTree.size() > 0) 172 { 173 _order = 1; 174 _saxTree(handler, contentsTree); 175 } 176 177 _writeColumns(handler, costData); 178 _writeLines(handler, costData); 179 } 180 catch (Exception e) 181 { 182 getLogger().error("An error occurred while processing the apogee report. Its generation has been aborted.", e); 183 } 184 } 185 186 /** 187 * Sax the information related to the courses of the tree 188 * @param handler the transformer handler 189 * @param programTree the program tree to sax 190 * @throws SAXException if an error occurs when SAXing 191 */ 192 @SuppressWarnings("unchecked") 193 private void _saxTree(TransformerHandler handler, Map<Program, Object> programTree) throws SAXException 194 { 195 for (Entry<Program, Object> programEntry : programTree.entrySet()) 196 { 197 if (programEntry.getValue() != null && programEntry.getValue() instanceof Map<?, ?>) 198 { 199 _saxCourseFromTree(handler, (Map<ProgramItem, Object>) programEntry.getValue(), programEntry.getKey()); 200 } 201 } 202 } 203 204 private void _saxCourseFromTree(TransformerHandler handler, Map<ProgramItem, Object> programTree, Program program) throws SAXException 205 { 206 _saxCourseFromTree(handler, programTree, program, null, null, null, null, null, null, 1, ""); 207 } 208 209 private void _saxCourseFromTree(TransformerHandler handler, Map<ProgramItem, Object> tree, Program program, SubProgram subprogram, Container containerYear, Container containerSemester, CourseList list, Integer listPosition, Course parentCourse, int level, String courseHierarchy) throws SAXException 210 { 211 int courseListPosition = 0; 212 for (Entry<ProgramItem, Object> entry : tree.entrySet()) 213 { 214 ProgramItem child = entry.getKey(); 215 @SuppressWarnings("unchecked") 216 Map<ProgramItem, Object> subTree = (Map<ProgramItem, Object>) entry.getValue(); 217 218 if (child instanceof Course) 219 { 220 Course childCourse = (Course) child; 221 String path = courseHierarchy + " > " + childCourse.getTitle(); 222 _saxCourse(handler, program, subprogram, containerYear, containerSemester, list, listPosition, (Course) child, parentCourse, level, subTree == null ? true : false, path); 223 224 if (subTree != null) 225 { 226 _saxCourseFromTree(handler, subTree, program, subprogram, containerYear, containerSemester, list, listPosition, childCourse, level + 1, path); 227 } 228 } 229 230 if (subTree != null) 231 { 232 if (child instanceof Program) 233 { 234 _saxCourseFromTree(handler, subTree, (Program) child, subprogram, containerYear, containerSemester, list, listPosition, parentCourse, level, courseHierarchy); 235 } 236 else if (child instanceof Container) 237 { 238 Container container = (Container) child; 239 String containerNature = _refTableHelper.getItemCode(container.getNature()); 240 241 if ("annee".equals(containerNature)) 242 { 243 _saxCourseFromTree(handler, subTree, program, subprogram, container, containerSemester, list, listPosition, parentCourse, level, courseHierarchy); 244 } 245 else if ("semestre".equals(containerNature)) 246 { 247 _saxCourseFromTree(handler, subTree, program, subprogram, containerYear, container, list, listPosition, parentCourse, level, courseHierarchy); 248 } 249 else 250 { 251 _saxCourseFromTree(handler, subTree, program, subprogram, containerYear, containerSemester, list, listPosition, parentCourse, level, courseHierarchy); 252 } 253 } 254 else if (child instanceof SubProgram) 255 { 256 _saxCourseFromTree(handler, subTree, program, (SubProgram) child, containerYear, containerSemester, list, listPosition, parentCourse, level, courseHierarchy); 257 } 258 else if (child instanceof CourseList) 259 { 260 courseListPosition++; 261 CourseList childCourseList = (CourseList) child; 262 String path = courseHierarchy.equals("") ? childCourseList.getTitle() : courseHierarchy + " > " + childCourseList.getTitle(); 263 _saxCourseFromTree(handler, subTree, program, subprogram, containerYear, containerSemester, (CourseList) child, courseListPosition, parentCourse, level, path); 264 } 265 } 266 } 267 } 268 269 private String _getHierarchy(Program program, SubProgram subprogram, Container containerYear, Container containerSemester, String courseHierarchy) 270 { 271 String hierarchy = program.getTitle(); 272 if (subprogram != null) 273 { 274 hierarchy += " > " + subprogram.getTitle(); 275 } 276 277 if (containerYear != null) 278 { 279 hierarchy += " > " + containerYear.getTitle(); 280 } 281 282 if (containerSemester != null) 283 { 284 hierarchy += " > " + containerSemester.getTitle(); 285 } 286 287 hierarchy += " > " + courseHierarchy; 288 289 return hierarchy; 290 } 291 292 private void _saxCourse(TransformerHandler handler, Program program, SubProgram subprogram, Container containerYear, Container containerSemester, CourseList list, Integer listPosition, Course course, Course parentCourse, int level, boolean lastLevel, String courseHierarchy) throws SAXException 293 { 294 if (course != null) 295 { 296 String hierarchy = _getHierarchy(program, subprogram, containerYear, containerSemester, courseHierarchy); 297 298 XMLUtils.startElement(handler, "course"); 299 300 // Ordre 301 XMLUtils.createElement(handler, "ordre", String.valueOf(_order)); 302 303 // Composante 304 _saxOrgUnits(handler, program); 305 306 // Formation 307 XMLUtils.createElement(handler, "formation", program.getTitle()); 308 XMLUtils.createElement(handler, "formationCode", program.getCode()); 309 310 _saxSubProgram(handler, subprogram); 311 312 _saxContainer(handler, containerYear, parentCourse); 313 314 _saxCourseList(handler, list, listPosition); 315 316 // A des fils 317 boolean aDesFils = course.hasCourseLists(); 318 XMLUtils.createElement(handler, "aDesFils", aDesFils ? "X" : ""); 319 320 long courseListsSize = course.getParentCourseLists().size(); 321 322 // Partagé 323 XMLUtils.createElement(handler, "partage", courseListsSize > 1 ? "X" : ""); 324 325 // Nb occurrences 326 XMLUtils.createElement(handler, "occurrences", _reportHelper.formatNumberToSax(courseListsSize)); 327 328 Container etape = _getEtapePorteuse(course, hierarchy); 329 330 String porte = _getPorte(etape, containerYear); 331 332 // Porté ("X" si l'ELP est porté par la formation (Etape porteuse=COD_ETP), vide sinon) 333 XMLUtils.createElement(handler, "porte", porte); 334 335 // Niveau 336 XMLUtils.createElement(handler, "niveau", "niv" + level); 337 338 // Date de création 339 XMLUtils.createElement(handler, "creationDate", DateUtils.dateToString(course.getCreationDate())); 340 341 // Code Apogée 342 XMLUtils.createElement(handler, "codeApogee", course.getValue("elpCode", false, StringUtils.EMPTY)); 343 344 // Nature de l'élément 345 XMLUtils.createElement(handler, "nature", _refTableHelper.getItemCode(course.getCourseType())); 346 347 // Libellé court 348 XMLUtils.createElement(handler, "libelleCourt", course.getValue("shortLabel", false, StringUtils.EMPTY)); 349 350 // Libellé 351 XMLUtils.createElement(handler, "libelle", course.getTitle()); 352 353 // Code Ametys (ELP) 354 XMLUtils.createElement(handler, "elpCode", course.getCode()); 355 356 // Lieu 357 _reportHelper.saxContentAttribute(handler, course, "campus", "campus"); 358 359 // Crédits ECTS 360 XMLUtils.createElement(handler, "ects", String.valueOf(course.getEcts())); 361 362 String teachingActivity = _refTableHelper.getItemCode(course.getTeachingActivity()); 363 String stage = teachingActivity.equals("SA") ? "X" : ""; 364 365 // Element stage 366 XMLUtils.createElement(handler, "stage", stage); 367 368 // Code semestre 369 String periode = _refTableHelper.getItemCode(course.getTeachingTerm()); 370 XMLUtils.createElement(handler, "periode", "s10".equals(periode) ? "s0" : periode); 371 372 OrgUnit orgUnit = _getOrgUnit(course, hierarchy); 373 374 // Code composante 375 String codeComposante = Optional.ofNullable(orgUnit) 376 .map(o -> o.<String>getValue("codCmp")) 377 .orElse(StringUtils.EMPTY); 378 XMLUtils.createElement(handler, "codeComposante", codeComposante); 379 380 // Code CIP 381 String codeCIP = Optional.ofNullable(orgUnit) 382 .map(o -> o.<String>getValue("codCipApogee")) 383 .orElse(StringUtils.EMPTY); 384 XMLUtils.createElement(handler, "codeCIP", codeCIP); 385 386 // Code ANU 387 long codeAnu = course.getValue("CodeAnu", false, 0L); 388 XMLUtils.createElement(handler, "CodeAnu", codeAnu > 0 ? String.valueOf(codeAnu) : StringUtils.EMPTY); 389 390 // Calcul des charges 391 XMLUtils.createElement(handler, "calculCharges", aDesFils ? "" : "X"); 392 393 // Discipline 394 String disciplineEnseignement = Optional.of("disciplineEnseignement") 395 .map(course::<ContentValue>getValue) 396 .flatMap(ContentValue::getContentIfExists) 397 .map(OdfReferenceTableEntry::new) 398 .map(entry -> { 399 String code = entry.getCode(); 400 return (StringUtils.isNotEmpty(code) ? "[" + code + "] " : StringUtils.EMPTY) + entry.getLabel(course.getLanguage()); 401 }) 402 .orElse(StringUtils.EMPTY); 403 XMLUtils.createElement(handler, "discipline", disciplineEnseignement); 404 405 // Etape porteuse 406 String etapePorteuse = lastLevel ? _getEtapeTitle(etape) : StringUtils.EMPTY; 407 XMLUtils.createElement(handler, "etapePorteuse", etapePorteuse); 408 409 if (lastLevel) 410 { 411 // Heures d'enseignement 412 XMLUtils.startElement(handler, "courseParts"); 413 for (CoursePart coursePart : course.getCourseParts()) 414 { 415 AttributesImpl attr = new AttributesImpl(); 416 attr.addCDATAAttribute("id", coursePart.getId()); 417 XMLUtils.createElement(handler, "coursePart", attr); 418 } 419 XMLUtils.endElement(handler, "courseParts"); 420 } 421 422 saxAdditionalCourseData(handler, course); 423 424 XMLUtils.endElement(handler, "course"); 425 426 _order++; 427 } 428 } 429 430 private String _getEtapeTitle(Container etape) 431 { 432 if (etape == null) 433 { 434 return StringUtils.EMPTY; 435 } 436 437 StringBuilder etapeTitle = new StringBuilder(); 438 etapeTitle.append("["); 439 etapeTitle.append(etape.getCode()); 440 etapeTitle.append("] "); 441 etapeTitle.append(etape.getTitle()); 442 443 String etpCode = etape.getValue("etpCode"); 444 if (StringUtils.isNotBlank(etpCode)) 445 { 446 etapeTitle.append(" ("); 447 etapeTitle.append(etpCode); 448 String vrsEtpCode = etape.getValue("vrsEtpCode"); 449 if (StringUtils.isNotBlank(vrsEtpCode)) 450 { 451 etapeTitle.append("-"); 452 etapeTitle.append(vrsEtpCode); 453 } 454 etapeTitle.append(")"); 455 } 456 457 return etapeTitle.toString(); 458 } 459 460 /** 461 * Sax a additional data of a {@link Course}. 462 * @param handler The handler 463 * @param course The course to SAX 464 * @throws AmetysRepositoryException if an error occurs 465 * @throws SAXException if an error occurs 466 */ 467 protected void saxAdditionalCourseData(TransformerHandler handler, Course course) throws AmetysRepositoryException, SAXException 468 { 469 // Do nothing by default 470 } 471 472 private void _saxContainer(TransformerHandler handler, Container container, Course parentCourse) throws AmetysRepositoryException, SAXException 473 { 474 if (container != null) 475 { 476 // Année 477 XMLUtils.createElement(handler, "annee", container.getTitle()); 478 // COD_ETP 479 XMLUtils.createElement(handler, "COD_ETP", container.getValue("etpCode", false, StringUtils.EMPTY)); 480 // COD_VRS_ETP 481 XMLUtils.createElement(handler, "COD_VRS_ETP", container.getValue("vrsEtpCode", false, StringUtils.EMPTY)); 482 // Code Ametys ELP père 483 XMLUtils.createElement(handler, "codeELPPere", parentCourse != null ? parentCourse.getCode() : ""); 484 } 485 } 486 487 private void _saxSubProgram(TransformerHandler handler, SubProgram subprogram) throws AmetysRepositoryException, SAXException 488 { 489 if (subprogram != null) 490 { 491 // Parcours 492 XMLUtils.createElement(handler, "parcours", subprogram.getTitle()); 493 XMLUtils.createElement(handler, "parcoursCode", subprogram.getCode()); 494 } 495 } 496 497 private void _saxOrgUnits(TransformerHandler handler, Program program) throws SAXException 498 { 499 StringBuilder sb = new StringBuilder(); 500 List<String> orgUnits = program.getOrgUnits(); 501 for (String orgUnitId : orgUnits) 502 { 503 try 504 { 505 OrgUnit orgUnit = _resolver.resolveById(orgUnitId); 506 if (sb.length() > 0) 507 { 508 sb.append(", "); 509 } 510 sb.append(orgUnit.getTitle()); 511 sb.append(" ("); 512 sb.append(orgUnit.getUAICode()); 513 sb.append(")"); 514 } 515 catch (UnknownAmetysObjectException e) 516 { 517 getLogger().info("La composante référencée par la formation {} ({}) n'a pas été trouvée.", program.getTitle(), program.getCode()); 518 } 519 } 520 XMLUtils.createElement(handler, "orgUnit", sb.toString()); 521 } 522 523 private String _getPorte(Container etape, Container containerYear) 524 { 525 String porte = ""; 526 if (etape != null && containerYear != null) 527 { 528 String etpCode = containerYear.getValue("etpCode", false, StringUtils.EMPTY); 529 if (StringUtils.isNotEmpty(etpCode)) 530 { 531 porte = etpCode.equals(etape.getValue("etpCode", false, StringUtils.EMPTY)) ? "X" : ""; 532 } 533 } 534 return porte; 535 } 536 537 private OrgUnit _getOrgUnit(Course course, String hierarchy) 538 { 539 OrgUnit orgUnit = null; 540 541 List<String> courseOrgUnits = course.getOrgUnits(); 542 if (!courseOrgUnits.isEmpty()) 543 { 544 try 545 { 546 orgUnit = _resolver.resolveById(courseOrgUnits.get(0)); 547 } 548 catch (UnknownAmetysObjectException e) 549 { 550 getLogger().info("La composante référencée par l'élément pédagogique {} ({}) n'a pas été trouvée.", hierarchy, course.getCode()); 551 } 552 553 if (courseOrgUnits.size() > 1) 554 { 555 getLogger().warn("L'élément pédagogique {} ({}) référence plus d'une composante.", hierarchy, course.getCode()); 556 } 557 } 558 559 return orgUnit; 560 } 561 562 private Container _getEtapePorteuse(Course course, String hierarchy) 563 { 564 // Use a very useful component from ODF OSE 565 Set<Container> stepsHolder = _getStepsHolder(course); 566 switch (stepsHolder.size()) 567 { 568 case 0: 569 getLogger().info("L'élément pédagogique {} ({}) n'est rattaché à aucune étape.", hierarchy, course.getCode()); 570 break; 571 case 1: 572 return stepsHolder.stream().findFirst().get(); 573 default: 574 getLogger().info("Impossible de définir une étape porteuse unique sur l'élément pédagogique {} ({}).", hierarchy, course.getCode()); 575 break; 576 } 577 578 return null; 579 } 580 581 /** 582 * Find steps holders 583 * @param programItem the item we want to explore 584 * @return a list of steps holders 585 */ 586 protected Set<Container> _getStepsHolder(ProgramItem programItem) 587 { 588 // Search if the current element is a course and has a step holder 589 if (programItem instanceof Course) 590 { 591 Course course = (Course) programItem; 592 ContentValue stepHolder = course.getValue(__ETAPE_PORTEUSE); 593 if (stepHolder != null) 594 { 595 try 596 { 597 return Collections.singleton((Container) stepHolder.getContent()); 598 } 599 catch (AmetysRepositoryException e) 600 { 601 if (getLogger().isWarnEnabled()) 602 { 603 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.", stepHolder.getContentId(), course.getTitle(), course.getId()); 604 } 605 } 606 } 607 } 608 // Search if the current element is a container and is of type year 609 else if (programItem instanceof Container) 610 { 611 Container container = (Container) programItem; 612 if (_refTableHelper.getItemCode(container.getNature()).equals(__VALUE_YEAR)) 613 { 614 return Collections.singleton(container); 615 } 616 } 617 618 // In all other cases, search in the parent elements 619 return _odfHelper.getParentProgramItems(programItem) 620 .stream() 621 .map(child -> _getStepsHolder(child)) 622 .flatMap(Set::stream) 623 .collect(Collectors.toSet()); 624 } 625 626 private void _saxCourseList(TransformerHandler handler, CourseList list, Integer position) throws SAXException 627 { 628 if (list != null) 629 { 630 XMLUtils.createElement(handler, "list", "Lst" + position); 631 _saxChoiceList(handler, list); 632 } 633 } 634 635 private void _saxChoiceList(TransformerHandler handler, CourseList list) throws SAXException 636 { 637 // Type 638 ChoiceType typeList = list.getType(); 639 if (typeList != null) 640 { 641 if (typeList.name() != null && (typeList.name().equals(ChoiceType.CHOICE.toString()) || typeList.name().equals(ChoiceType.MANDATORY.toString()) || typeList.name().equals(ChoiceType.OPTIONAL.toString()))) 642 { 643 String typeListAsString = ""; 644 if (typeList.name().equals(ChoiceType.CHOICE.toString())) 645 { 646 typeListAsString = "X"; 647 } 648 else if (typeList.name().equals(ChoiceType.MANDATORY.toString())) 649 { 650 typeListAsString = "O"; 651 } 652 else if (typeList.name().equals(ChoiceType.OPTIONAL.toString())) 653 { 654 typeListAsString = "F"; 655 } 656 657 XMLUtils.createElement(handler, "typeList", typeListAsString); 658 } 659 660 // Min-Max (Ne remplir que pour le type "CHOICE" (ne mettre que le min)) 661 if (typeList.name() != null && typeList.name().equals(ChoiceType.CHOICE.toString())) 662 { 663 XMLUtils.createElement(handler, "minmax", _reportHelper.formatNumberToSax(list.getMinNumberOfCourses())); 664 } 665 } 666 } 667 668 class ProgramTitleComparator implements Comparator<Program> 669 { 670 @Override 671 public int compare(Program p1, Program p2) 672 { 673 674 return p1.getTitle().compareTo(p2.getTitle()); 675 } 676 } 677 678 /** 679 * Generate the data structure that will be used to create the report 680 * @param uaiCode the uai code of the organization unit 681 * @param lang the lang of programs 682 * @param catalog the catalog of programs 683 * @return The structure 684 */ 685 private Map<Program, Object> _getStructure(String uaiCode, String lang, String catalog) 686 { 687 AmetysObjectIterable<OrgUnit> orgUnits = _reportHelper.getRootOrgUnitsByUaiCode(uaiCode); 688 AmetysObjectIterator<OrgUnit> orgUnitsIterator = orgUnits.iterator(); 689 if (!orgUnitsIterator.hasNext()) 690 { 691 throw new IllegalArgumentException("Unable to find any organization unit with the uai code : " + uaiCode + ".The processing of the Apogee report is aborted."); 692 } 693 694 Map<Program, Object> programTree = new TreeMap<>(new ProgramTitleComparator()); 695 696 OrgUnit rootOrgUnit = orgUnitsIterator.next(); 697 // On ne récupère que les composantes, enfant direct du root org unit, et on ignore les départements. 698 if (rootOrgUnit.getParentOrgUnit() != null && rootOrgUnit.getParentOrgUnit().getParentOrgUnit() == null) 699 { 700 List<String> orgUnitIds = _reportHelper.getSubOrgUnits(rootOrgUnit); 701 702 // Chercher les programmes concernés par la composante sélectionnée et ses enfants 703 for (String orgUnitId : orgUnitIds) 704 { 705 AmetysObjectIterable<Program> programs = _reportHelper.getProgramsByOrgUnitId(orgUnitId, lang, catalog); 706 AmetysObjectIterator<Program> programsIterator = programs.iterator(); 707 while (programsIterator.hasNext()) 708 { 709 Program program = programsIterator.next(); 710 Map<ProgramItem, Object> courses = _reportHelper.getCoursesFromContent(program); 711 if (courses != null) 712 { 713 programTree.put(program, courses); 714 } 715 } 716 } 717 } 718 719 return programTree; 720 } 721 722 /** 723 * Write lines content of the report 724 * @param handler the transformer handler 725 * @param costData informations about the capacity 726 * @throws SAXException to handle XMLUtils exceptions 727 */ 728 private void _writeLines(TransformerHandler handler, CostComputationData costData) throws SAXException 729 { 730 // Remove null values before wirting it (the null values have been stored to avoid the same operation twice) 731 costData.getCoursePartCostData().values().removeIf(Objects::isNull); 732 733 XMLUtils.startElement(handler, "courseParts"); 734 735 // Write a course part by line 736 for (Entry<CoursePart, CoursePartCostData> entry : costData.getCoursePartCostData().entrySet()) 737 { 738 AttributesImpl attr = new AttributesImpl(); 739 attr.addCDATAAttribute("id", entry.getKey().getId()); 740 XMLUtils.startElement(handler, "coursePart", attr); 741 Map<String, String> calculatedCoursePart = this.getValues(costData, entry.getKey(), entry.getValue()); 742 for (String columnName : calculatedCoursePart.keySet()) 743 { 744 XMLUtils.createElement(handler, columnName, calculatedCoursePart.get(columnName)); 745 } 746 747 // Ventilation des effectifs 748 Map<Container, Double> ventilationEffectif = costData.getEffectiveByStep(entry.getKey().getId()); 749 if (ventilationEffectif != null) 750 { 751 for (Container etape : ventilationEffectif.keySet()) 752 { 753 AttributesImpl attrs = new AttributesImpl(); 754 attrs.addCDATAAttribute("id", etape.getId()); 755 XMLUtils.createElement(handler, "effectif", attrs, ReportUtils.FORMAT_2_DIGITS.format(ventilationEffectif.get(etape))); 756 } 757 } 758 759 // Ventilation des eqTD 760 Map<Container, Double> ventilationEqTD = costData.getEqTDByStep(entry.getKey().getId()); 761 if (ventilationEqTD != null) 762 { 763 for (Container etape : ventilationEqTD.keySet()) 764 { 765 AttributesImpl attrs = new AttributesImpl(); 766 attrs.addCDATAAttribute("id", etape.getId()); 767 XMLUtils.createElement(handler, "eqTD", attrs, ReportUtils.FORMAT_2_DIGITS.format(ventilationEqTD.get(etape))); 768 } 769 } 770 771 XMLUtils.endElement(handler, "coursePart"); 772 } 773 774 XMLUtils.endElement(handler, "courseParts"); 775 } 776 777 private void _writeColumns(TransformerHandler handler, CostComputationData costData) throws SAXException 778 { 779 // Columns 780 XMLUtils.startElement(handler, "columns"); 781 782 // Ecriture des colonnes par ordre alphabétique sur le titre 783 Map<String, String> sortedYearsToDisplay = _getYearsToDisplay(costData); 784 785 for (Map.Entry<String, String> entry : sortedYearsToDisplay.entrySet()) 786 { 787 _writeColumn(handler, entry); 788 } 789 790 XMLUtils.endElement(handler, "columns"); 791 } 792 793 private void _writeColumn(TransformerHandler handler, Map.Entry<String, String> entry) throws SAXException 794 { 795 AttributesImpl attrs = new AttributesImpl(); 796 attrs.addCDATAAttribute("id", entry.getKey()); 797 XMLUtils.createElement(handler, "column", attrs, entry.getValue()); 798 } 799 800 private Map<String, String> _getYearsToDisplay(CostComputationData costData) 801 { 802 return costData.getAllEffectivesByStep() 803 .keySet() 804 .stream() 805 .collect(Collectors.toMap(Container::getId, Container::getCode)); 806 } 807 808 /** 809 * Create a map of values to sax 810 * @param costData informations about the formation 811 * @param coursePart the coursePart 812 * @param coursePartCostData the coursePart cost data 813 * @return the map of values to sax 814 */ 815 private Map<String, String> getValues(CostComputationData costData, CoursePart coursePart, CoursePartCostData coursePartCostData) 816 { 817 Map<String, String> values = new HashMap<>(); 818 Double enteredEffective = costData.getEnteredEffective(coursePart.getId()).isPresent() ? costData.getEnteredEffective(coursePart.getId()).get() : 0d; 819 Course courseHolder = coursePart.getCourseHolder(); 820 821 // *** Course part *** 822 values.put("titre", coursePart.getTitle()); 823 values.put("code", coursePart.getCode()); 824 values.put("nature", _refTableHelper.getItemCode(coursePart.getNature())); 825 values.put("volumeHoraire", ReportUtils.FORMAT_2_DIGITS.format(coursePart.getNumberOfHours())); 826 values.put("elpPorteur", courseHolder.getCode()); 827 828 // *** Norme et + *** 829 values.put("effectifCalcule", ReportUtils.FORMAT_2_DIGITS.format(costData.getGlobalComputedEffective(coursePart.getId()).get())); 830 values.put("effectifPrevisionnel", ReportUtils.FORMAT_2_DIGITS.format(enteredEffective)); 831 values.put("effectifMax", ReportUtils.FORMAT_2_DIGITS.format(coursePartCostData.getEffectiveMax())); 832 values.put("effectifMinSup", ReportUtils.FORMAT_2_DIGITS.format(coursePartCostData.getEffectiveMinSup())); 833 values.put("norme", coursePartCostData.getNormLabel()); 834 values.put("groupesCalcules", ReportUtils.FORMAT_2_DIGITS.format(coursePartCostData.getCalculatedGroups())); 835 values.put("groupesAOuvrir", ReportUtils.FORMAT_2_DIGITS.format(coursePartCostData.getGroupsToOpen())); 836 values.put("eqTDTotal", ReportUtils.FORMAT_2_DIGITS.format(costData.getEqTD(coursePart.getId()).getGlobalEqTD())); 837 838 values.values().removeIf(Objects::isNull); 839 840 return values; 841 } 842}