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}