001/*
002 *  Copyright 2018 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.Comparator;
021import java.util.HashMap;
022import java.util.HashSet;
023import java.util.Map;
024import java.util.Map.Entry;
025import java.util.Optional;
026import java.util.Set;
027import java.util.TreeMap;
028import java.util.TreeSet;
029import java.util.function.Function;
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.xml.sax.SAXException;
044
045import org.ametys.cms.repository.Content;
046import org.ametys.odf.ProgramItem;
047import org.ametys.odf.orgunit.OrgUnit;
048import org.ametys.odf.program.Container;
049import org.ametys.odf.program.Program;
050import org.ametys.plugins.odfpilotage.cost.CostComputationComponent;
051import org.ametys.plugins.odfpilotage.cost.entity.CostComputationData;
052import org.ametys.plugins.odfpilotage.cost.entity.Effectives;
053import org.ametys.plugins.odfpilotage.cost.entity.EqTD;
054import org.ametys.plugins.odfpilotage.cost.entity.ProgramItemData;
055import org.ametys.plugins.odfpilotage.cost.entity.VolumesOfHours;
056import org.ametys.plugins.repository.AmetysRepositoryException;
057import org.ametys.runtime.i18n.I18nizableText;
058import org.ametys.runtime.i18n.I18nizableTextParameter;
059import org.ametys.runtime.model.ModelItem;
060
061/**
062 * Pilotage report for cost modeling synthesis
063 */
064public class SyntheseReport extends AbstractReport
065{
066    /** CalculerEffectifComponent */
067    protected CostComputationComponent _costComputationComponent;
068    
069    @Override
070    public void service(ServiceManager manager) throws ServiceException
071    {
072        super.service(manager);
073        _costComputationComponent = (CostComputationComponent) manager.lookup(CostComputationComponent.ROLE);
074    }
075    
076    @Override
077    protected String getType()
078    {
079        return "synthese";
080    }
081
082    @Override
083    protected Set<String> getSupportedOutputFormats()
084    {
085        return Set.of(OUTPUT_FORMAT_XLS);
086    }
087    
088    @Override
089    protected void _launchByOrgUnit(String uaiCode, String catalog, String lang) throws Exception
090    {
091        OrgUnit orgUnit = _odfHelper.getOrgUnitByUAICode(uaiCode);
092        CostComputationData costData = _costComputationComponent.computeCostsOnOrgUnit(orgUnit, catalog, lang);
093        
094        _writeSyntheseReport(uaiCode, catalog, lang, orgUnit, costData);
095    }
096    
097    /**
098     * Create the synthese report for one organization unit 
099     * @param uaiCode the reference to the organization unit whose formations are going to be saxed
100     * @param catalog the catalog
101     * @param lang The language code
102     * @param orgUnit the orgUnit
103     * @param costData object containing informations about the formation
104     */
105    private void _writeSyntheseReport(String uaiCode, String catalog, String lang, OrgUnit orgUnit, CostComputationData costData)
106    {
107        SAXTransformerFactory factory = (SAXTransformerFactory) TransformerFactory.newInstance();
108        String fileName = _getReportFileName(catalog, lang, _reportHelper.getAccronymOrUaiCode(uaiCode));
109        
110        // Delete old files
111        File xmlFile = new File(_tmpFolder, fileName + ".xml");
112        FileUtils.deleteQuietly(xmlFile);
113        
114        // Write XML file
115        try (FileOutputStream fos = new FileOutputStream(xmlFile))
116        {
117            TransformerHandler handler = factory.newTransformerHandler();
118            
119            // Prepare the transformation
120            Result result = new StreamResult(fos);
121            handler.setResult(result);
122            handler.startDocument();
123        
124            AttributesImpl attrs = new AttributesImpl();
125            attrs.addCDATAAttribute("type", getType());
126            attrs.addCDATAAttribute("date", _reportHelper.getReadableCurrentDate());
127            XMLUtils.startElement(handler, "report", attrs);
128
129            _reportHelper.saxNaturesEnseignement(handler, getLogger());
130            _writeLines(handler, costData, orgUnit);
131            
132            XMLUtils.endElement(handler, "report");
133            handler.endDocument();
134
135            // Convert the report to configured output format
136            convertReport(_tmpFolder, fileName, xmlFile);
137        }
138        catch (Exception e)
139        {
140            getLogger().error("An error occured while generating 'Synthese' report for orgunit '{}'", uaiCode, e);
141        }
142        finally
143        {
144            FileUtils.deleteQuietly(xmlFile);
145        }
146    }
147    
148    /**
149     * Write lines content of the report
150     * @param handler the transformer handler
151     * @param costData informations about the capacity
152     * @param orgUnit the orgUnit
153     * @throws SAXException to handle XMLUtils exceptions
154     */
155    private void _writeLines(TransformerHandler handler, CostComputationData costData, OrgUnit orgUnit) throws SAXException
156    {
157        XMLUtils.startElement(handler, "lines");
158        Map<Program, Set<Container>> stepsByProgram = _getStepsByProgram(costData);
159        for (Entry<Program, Set<Container>> entry : stepsByProgram.entrySet())
160        {
161            for (Container step : entry.getValue())
162            {
163                _writeStepLine(handler, costData, entry.getKey(), step);
164            }
165            _writeProgramLine(handler, costData, entry.getKey());
166        }
167        _writeOrgUnitLine(handler, costData, orgUnit);
168        
169        XMLUtils.endElement(handler, "lines");
170    }
171    
172    private Map<Program, Set<Container>> _getStepsByProgram(CostComputationData costData)
173    {
174        Comparator<Content> titleComparator = new TitleComparator();
175        String yearId = _pilotageHelper.getYearId().orElse(null);
176        
177        Map<Program, Set<Container>> stepsByProgram = new TreeMap<>(titleComparator);
178        
179        if (yearId == null)
180        {
181            stepsByProgram.putAll(
182                costData.getComputedPrograms()
183                    .stream()
184                    .collect(Collectors.toMap(Function.identity(), __ -> Set.<Container>of()))
185            );
186        }
187        else
188        {
189            for (Program program : costData.getComputedPrograms())
190            {
191                Set<Container> steps = new TreeSet<>(titleComparator);
192                steps.addAll(_getChildrendStepsForProgramItem(program, yearId));
193                stepsByProgram.put(program, steps);
194            }
195        }
196        
197        return stepsByProgram;
198    }
199    
200    private Set<Container> _getChildrendStepsForProgramItem(ProgramItem programItem, String yearId)
201    {
202        Set<Container> containers = new HashSet<>();
203
204        // Search if the current element is a container and is of type year
205        if (programItem instanceof Container)
206        {
207            Container container = (Container) programItem;
208            if (yearId.equals(container.getNature()))
209            {
210                containers.add(container);
211            }
212        }
213        
214        // In all other cases, search in the parent elements
215        if (containers.isEmpty())
216        {
217            for (ProgramItem child : _odfHelper.getChildProgramItems(programItem))
218            {
219                containers.addAll(_getChildrendStepsForProgramItem(child, yearId));
220            }
221        }
222        
223        return containers;
224    }
225
226    private void _writeStepLine(TransformerHandler handler, CostComputationData costData, Program program, Container step) throws AmetysRepositoryException, SAXException
227    {
228        XMLUtils.startElement(handler, "line");
229        XMLUtils.createElement(handler, "type", "container");
230        XMLUtils.createElement(handler, "diplome", ((Content) program).getTitle());
231        XMLUtils.createElement(handler, "annee", step.getTitle());
232        _writeValues(handler, costData, step, program.getName() + ModelItem.ITEM_PATH_SEPARATOR + "(.*" + ModelItem.ITEM_PATH_SEPARATOR + ")*" + step.getName());
233        XMLUtils.endElement(handler, "line");
234    }
235    
236    private void _writeProgramLine(TransformerHandler handler, CostComputationData costData, Program program) throws AmetysRepositoryException, SAXException
237    {
238        XMLUtils.startElement(handler, "line");
239        XMLUtils.createElement(handler, "type", "program");
240        Map<String, I18nizableTextParameter> i18nParams = new HashMap<>();
241        i18nParams.put("program",  new I18nizableText(((Content) program).getTitle()));
242        XMLUtils.createElement(handler, "diplome", _i18nUtils.translate(new I18nizableText("plugin.odf-pilotage", "PLUGINS_ODF_PILOTAGE_SYNTHESIS_TOTAL", i18nParams), program.getLanguage()));
243        _writeValues(handler, costData, program, program.getName());
244        XMLUtils.endElement(handler, "line");
245    }
246    
247    private void _writeOrgUnitLine(TransformerHandler handler, CostComputationData costData, OrgUnit orgUnit) throws SAXException
248    {
249        XMLUtils.startElement(handler, "line");
250        XMLUtils.createElement(handler, "type", "orgunit");
251        XMLUtils.createElement(handler, "diplome", _i18nUtils.translate(new I18nizableText("plugin.odf-pilotage", "PLUGINS_ODF_PILOTAGE_SYNTHESIS_GRAND_TOTAL"), orgUnit.getLanguage()));
252        String orgUnitPath = orgUnit.getName();
253        _writeValues(handler, costData, orgUnit, orgUnitPath);
254        XMLUtils.endElement(handler, "line");
255    }
256    
257    private void _writeValues(TransformerHandler handler, CostComputationData costData, Content item, String regexPath) throws SAXException
258    {
259        ProgramItemData itemData = Optional.of(item)
260            .map(Content::getId)
261            .map(costData::get)
262            .orElse(null);
263        
264        if (itemData != null)
265        {
266            // Volumes of hours
267            VolumesOfHours volumesOfHours = itemData.getVolumesOfHours();
268            if (volumesOfHours != null)
269            {
270                for (Entry<String, Double> volumeOfHours : volumesOfHours.getVolumes().entrySet())
271                {
272                    AttributesImpl attrs = new AttributesImpl();
273                    attrs.addCDATAAttribute("id", volumeOfHours.getKey());
274                    XMLUtils.createElement(handler, "volume", attrs, String.valueOf(volumeOfHours.getValue()));
275                }
276                XMLUtils.createElement(handler, "total", String.valueOf(volumesOfHours.getTotal()));
277            }
278            
279            // EqTD
280            EqTD eqTD = itemData.getEqTD();
281            if (eqTD != null)
282            {
283                Double localEqTD = 0D;
284                Map<String, Double> localEqTDByPath = eqTD.getLocalEqTD();
285                for (String eqTDpath : localEqTDByPath.keySet())
286                {
287                    if (eqTDpath.matches(regexPath))
288                    {
289                        localEqTD += localEqTDByPath.get(eqTDpath);
290                    }
291                }
292                XMLUtils.createElement(handler, "eqtdLocal", String.valueOf(localEqTD));
293                XMLUtils.createElement(handler, "eqtdProrated", String.valueOf(eqTD.getProratedEqTD("")));
294            }
295            
296            // Effectives
297            Effectives effectives = itemData.getEffectives();
298            if (effectives != null)
299            {
300                XMLUtils.createElement(handler, "effectif", String.valueOf(effectives.getComputedEffective().longValue()));
301            }
302            
303            // Rapport H/E
304            XMLUtils.createElement(handler, "heRatio", String.valueOf(itemData.getHeRatio()));
305        }
306    }
307    
308    /**
309     * Compare two contents by their title
310     */
311    public class TitleComparator implements Comparator<Content> 
312    {
313        public int compare(Content c1, Content c2)
314        {
315            return c1.getTitle().compareTo(c2.getTitle());
316        }
317    }
318}