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