001/*
002 *  Copyright 2019 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.odfsync.apogee.ws.structure;
017
018import java.rmi.RemoteException;
019import java.util.ArrayList;
020import java.util.List;
021import java.util.Map;
022import java.util.stream.Collectors;
023
024import org.apache.avalon.framework.service.ServiceException;
025import org.apache.avalon.framework.service.ServiceManager;
026import org.apache.commons.lang3.StringUtils;
027
028import org.ametys.cms.data.ContentValue;
029import org.ametys.cms.data.type.ModelItemTypeConstants;
030import org.ametys.cms.repository.Content;
031import org.ametys.odf.course.Course;
032import org.ametys.odf.courselist.CourseList;
033import org.ametys.odf.orgunit.OrgUnit;
034import org.ametys.odf.program.AbstractProgram;
035import org.ametys.odf.program.Container;
036import org.ametys.odf.program.ProgramPart;
037import org.ametys.odf.program.SubProgram;
038import org.ametys.plugins.odfsync.apogee.ws.ApogeeStructureComponent;
039import org.ametys.plugins.odfsync.apogee.ws.ApogeeWS;
040import org.ametys.plugins.odfsync.export.AbstractExportStructure;
041import org.ametys.plugins.odfsync.export.ExportReport;
042import org.ametys.plugins.odfsync.export.ExportReport.ExportStatus;
043import org.ametys.runtime.i18n.I18nizableText;
044
045import gouv.education.apogee.commun.client.ws.creationse.CreationSEMetierServiceInterface;
046
047/**
048 * The abstract class to handle an export in Apogee
049 */
050public abstract class AbstractApogeeStructure extends AbstractExportStructure
051{
052    /** Avalon Role */
053    public static final String ROLE = ApogeeStructureComponent.class.getName();
054    
055    /** The attribute name for the code Apogee */
056    public static final String CODE_APOGEE_ATTRIBUTE_NAME = "codeApogee";
057    
058    /** The false attribute name for the version Apogee */
059    public static final String VERSION_APOGEE_ATTRIBUTE_NAME = "versionApogee";
060    
061    /** The separator between the code and the version */
062    public static final String CODE_APOGEE_SEPARATOR = "-";
063    
064    /** The apogee WS */
065    protected ApogeeWS _apogeeWS;
066    
067    @Override
068    public void service(ServiceManager manager) throws ServiceException
069    {
070        super.service(manager);
071        _apogeeWS = (ApogeeWS) manager.lookup(ApogeeWS.ROLE);
072    }
073    
074    /**
075     * Mandatory data to export a content in a DIP in Apogee
076     * @param content the content to export
077     * @return the list of mandatory data
078     */
079    public List<String> getDIPMandatoryData(Content content)
080    {
081        List<String> mandatoryData = new ArrayList<>();
082        mandatoryData.add(CODE_APOGEE_ATTRIBUTE_NAME);
083        mandatoryData.add(AbstractProgram.EDUCATION_KIND);
084        mandatoryData.add("cycleApogee");
085        mandatoryData.add(AbstractProgram.DEGREE);
086        
087        return mandatoryData;
088    }
089    
090    /**
091     * Mandatory data to export a content in a VDI in Apogee
092     * @param content the content to export
093     * @return the list of mandatory data
094     */
095    public List<String> getVDIMandatoryData(Content content)
096    {
097        // The orgunit Apogee Code is mandatory too
098        
099        List<String> mandatoryData = new ArrayList<>();
100        mandatoryData.add(VERSION_APOGEE_ATTRIBUTE_NAME);
101        mandatoryData.add("start-date-recruitment");
102        mandatoryData.add("end-date-recruitment");
103        mandatoryData.add("start-date-validation");
104        mandatoryData.add("end-date-validation");
105        
106        return mandatoryData;
107    }
108    
109    /**
110     * Mandatory data to export a content in a ETP in Apogee
111     * @param content the content to export
112     * @return the list of mandatory data
113     */
114    public List<String> getETPMandatoryData(Content content)
115    {
116        // The orgunit Apogee Code is mandatory too
117        
118        List<String> mandatoryData = new ArrayList<>();
119        mandatoryData.add(CODE_APOGEE_ATTRIBUTE_NAME); 
120        mandatoryData.add("cycleApogee");
121        
122        return mandatoryData;
123    }
124    
125    /**
126     * Mandatory data to export a content in a VET in Apogee
127     * @param content the content to export
128     * @return the list of mandatory data
129     */
130    public List<String> getVETMandatoryData(Content content)
131    {
132        // The orgunit Apogee Code is mandatory too
133        
134        List<String> mandatoryData = new ArrayList<>();
135        mandatoryData.add(VERSION_APOGEE_ATTRIBUTE_NAME);
136        mandatoryData.add("duration-apogee");
137        mandatoryData.add("inscription-types"); 
138        mandatoryData.add("cips"); 
139        
140        return mandatoryData;
141    }
142    
143    /**
144     * Mandatory data to export a content in a LSE in Apogee
145     * @param content the content to export
146     * @return the list of mandatory data
147     */
148    public List<String> getLSEMandatoryData(Content content)
149    {
150        List<String> mandatoryData = new ArrayList<>();
151        mandatoryData.add(CODE_APOGEE_ATTRIBUTE_NAME); 
152        mandatoryData.add("choiceType"); 
153        
154        String choiceTypeAmetys = content.getValue("choiceType");
155        if (choiceTypeAmetys.equals("CHOICE"))
156        {
157            mandatoryData.add("min"); 
158        }
159        
160        return mandatoryData;
161    }
162    
163    /**
164     * Mandatory data to export a content in a ELP in Apogee
165     * @param content the content to export
166     * @return the list of mandatory data
167     */
168    public List<String> getELPMandatoryData(Content content)
169    {
170        // The orgunit Apogee Code is mandatory too
171        
172        List<String> mandatoryData = new ArrayList<>();
173        mandatoryData.add(CODE_APOGEE_ATTRIBUTE_NAME); 
174        mandatoryData.add("cips"); 
175        mandatoryData.add("orgUnit"); 
176        if (content instanceof Container)
177        {
178            mandatoryData.add("nature"); 
179        }
180        else if (content instanceof Course)
181        {
182            mandatoryData.add("courseType"); 
183        }
184        
185        return mandatoryData;
186    }
187    
188    /**
189     * Mandatory data to export an orgunit for a DIP in Apogee
190     * @return the list of mandatory data
191     */
192    public List<String> getOrgUnitMandatoryDataForDIP()
193    {
194        List<String> mandatoryData = new ArrayList<>();
195        mandatoryData.add(CODE_APOGEE_ATTRIBUTE_NAME); 
196        
197        return mandatoryData;
198    }
199    
200    /**
201     * Mandatory data to export an orgunit for a ETP in Apogee
202     * @return the list of mandatory data
203     */
204    public List<String> getOrgUnitMandatoryDataForETP()
205    {
206        List<String> mandatoryData = new ArrayList<>();
207        mandatoryData.add(CODE_APOGEE_ATTRIBUTE_NAME); 
208        mandatoryData.add("codeCGE"); 
209        
210        return mandatoryData;
211    }
212    
213    /**
214     * Mandatory data to export an orgunit for a ELP in Apogee
215     * @return the list of mandatory data
216     */
217    public List<String> getOrgUnitMandatoryDataForELP()
218    {
219        List<String> mandatoryData = new ArrayList<>();
220        mandatoryData.add(CODE_APOGEE_ATTRIBUTE_NAME); 
221        
222        return mandatoryData;
223    }
224    
225    /**
226     * Get the code Apogee of the content
227     * @param content the content
228     * @return the code Apogee
229     */
230    public String getCodeApogee(Content content)
231    {
232        String codeApogee = content.getValue(CODE_APOGEE_ATTRIBUTE_NAME);
233        if (StringUtils.isNotBlank(codeApogee) && codeApogee.contains(CODE_APOGEE_SEPARATOR))
234        {
235            return StringUtils.substringBefore(codeApogee, CODE_APOGEE_SEPARATOR);
236        }
237        else
238        {
239            return codeApogee;
240        }
241    }
242    
243    /**
244     * Get the version, Apogee of the content
245     * @param content the content
246     * @return the version Apogee
247     */
248    public Long getVersionApogee(Content content)
249    {
250        String codeApogee = content.getValue(CODE_APOGEE_ATTRIBUTE_NAME);
251        if (StringUtils.isNotBlank(codeApogee) && codeApogee.contains(CODE_APOGEE_SEPARATOR))
252        {
253            return Long.parseLong(StringUtils.substringAfterLast(codeApogee, CODE_APOGEE_SEPARATOR));
254        }
255        else
256        {
257            return null;
258        }
259    }
260    
261    /**
262     * Check if the subProgram has the good data and structure to be export in Apogee
263     * @param subProgram the subProgram to check
264     * @param report the Apogee export report
265     */
266    public abstract void checkSubProgram(SubProgram subProgram, ExportReport report);
267    
268    /**
269     * Check if the container as year has the good data and structure to be export in Apogee
270     * @param container the container to check
271     * @param report the Apogee export report
272     * @param containerNatureCode The container nature code
273     */
274    public void checkContainerAsYear(Container container, ExportReport report, String containerNatureCode)
275    {
276        if (checkContainerYear(containerNatureCode, report))
277        {
278            // The container is a semester so check the data as a semester (ELP in Apogee)
279            checkMandatoryDataForContent(container, getETPMandatoryData(container), report);
280            checkMandatoryDataForContent(container, getVETMandatoryData(container), report);
281            
282            List<String> orgUnits = container.getOrgUnits();
283            checkMandatoryDataForOrgunits(container, orgUnits, getOrgUnitMandatoryDataForETP(), report);
284            
285            // Check the container structure
286            List<ProgramPart> programPartChildren = container.getProgramPartChildren();
287            for (ProgramPart childProgramPart : programPartChildren)
288            {
289                if (childProgramPart instanceof Container)
290                {
291                    Container containerChildProgramPart = (Container) childProgramPart;
292                    String childContainerNatureCode = getContainerNatureCode(containerChildProgramPart);
293                    
294                    checkContainerAsSemester(containerChildProgramPart, report, childContainerNatureCode);
295                }
296                else
297                {
298                    report.setStatus(ExportStatus.CONTENT_STRUCTURE_INVALID);
299                    break;
300                }
301            }
302            
303            if (programPartChildren.isEmpty())
304            {
305                // The structure is not handled by this export
306                report.setStatus(ExportStatus.CONTENT_STRUCTURE_INVALID);
307            }
308        }
309        else
310        {
311            // The structure is not handled by this export
312            report.setStatus(ExportStatus.CONTENT_STRUCTURE_INVALID);
313        }
314    }
315    
316    /**
317     * Check if the container as semester has the good data and structure to be export in Apogee
318     * @param container the container to check
319     * @param report the Apogee export report
320     * @param containerNatureCode The container nature code
321     */
322    public void checkContainerAsSemester(Container container, ExportReport report, String containerNatureCode)
323    {
324        if (checkContainerSemester(containerNatureCode, report))
325        {
326            // The container is a semester so check the data as a semester (ELP in Apogee)
327            checkMandatoryDataForContent(container, getELPMandatoryData(container), report);
328            
329            // Check the container structure
330            for (ProgramPart childProgramPart : container.getProgramPartChildren())
331            {
332                if (childProgramPart instanceof CourseList)
333                {
334                    checkCourseList((CourseList) childProgramPart, report);
335                }
336                else
337                {
338                    report.setStatus(ExportStatus.CONTENT_STRUCTURE_INVALID);
339                    break;
340                }
341            }
342        }
343        else
344        {
345            // The structure is not handled by this export
346            report.setStatus(ExportStatus.CONTENT_STRUCTURE_INVALID);
347        }
348    }
349    
350    /**
351     * Check if the course list has the good data and structure to be export in Apogee
352     * @param courseList the course list to check
353     * @param report the Apogee export report
354     */
355    public void checkCourseList(CourseList courseList, ExportReport report)
356    {
357        // Check mandatory data for course list
358        checkMandatoryDataForContent(courseList, getLSEMandatoryData(courseList), report);
359        
360        // Check the course list structure
361        for (Course course : courseList.getCourses())
362        {
363            checkCourse(course, report);
364        }
365    }
366    
367    /**
368     * Check if the course has the good data and structure to be export in Apogee
369     * @param course the course to check
370     * @param report the Apogee export report
371     */
372    public void checkCourse(Course course, ExportReport report)
373    {
374        // Check mandatory data for course
375        checkMandatoryDataForContent(course, getELPMandatoryData(course), report);
376        
377        // Check mandatory data for course orgUnits
378        checkMandatoryDataForOrgunits(course, course.getOrgUnits(), getOrgUnitMandatoryDataForELP(), report);
379        
380         // Check the course structure
381        for (CourseList courseList : course.getCourseLists())
382        {
383            checkCourseList(courseList, report);
384        }
385    }
386    
387    
388    /**
389     * Check if the content has a value for all mandatory data
390     * @param content the content to check
391     * @param mandatoryData the list of mandatory data path
392     * @param report the Apogee export report
393     */
394    public void checkMandatoryDataForContent(Content content, List<String> mandatoryData, ExportReport report)
395    {
396        for (String dataPath : mandatoryData)
397        {
398            if (VERSION_APOGEE_ATTRIBUTE_NAME.equals(dataPath))
399            {
400                _checkVersionApogee(content, report);
401            }
402            else if (content.hasValue(dataPath))
403            {
404                if (org.ametys.runtime.model.type.ModelItemTypeConstants.STRING_TYPE_ID.equals(content.getType(dataPath).getId()))
405                {
406                    _checkSimpleData(content, dataPath, report);    
407                }
408                else if (ModelItemTypeConstants.CONTENT_ELEMENT_TYPE_ID.equals(content.getType(dataPath).getId()))
409                {
410                    _checkTableRef(content, dataPath, report);
411                }
412            }
413            else
414            {
415                _addMandatoryDataPathAndReport(content, dataPath, report);
416            }
417        }
418    }
419
420    /**
421     * Check if the content has a value for the simple data
422     * @param content the content to check
423     * @param dataPath the data path
424     * @param report the Apogee export report
425     */
426    protected void _checkSimpleData(Content content, String dataPath, ExportReport report)
427    {
428        if (content.isMultiple(dataPath))
429        {
430            String[] values = content.getValue(dataPath);
431            if (values.length == 0 || StringUtils.isBlank(values[0]))
432            {
433                _addMandatoryDataPathAndReport(content, dataPath, report);
434            }
435        }
436        else
437        {
438            String value = content.getValue(dataPath);
439            if (StringUtils.isBlank(value))
440            {
441                _addMandatoryDataPathAndReport(content, dataPath, report);
442            }
443        }
444    }
445
446    /**
447     * Check if the content has a value for the simple table ref data
448     * @param content the content to check
449     * @param dataPath the data path
450     * @param report the Apogee export report
451     */
452    protected void _checkTableRef(Content content, String dataPath, ExportReport report)
453    {
454        // We handle only simple table ref
455        if (!content.isMultiple(dataPath))
456        {
457            ContentValue value = content.getValue(dataPath);
458            Content refContent = value.getContent();
459            if (!value.hasValue(CODE_APOGEE_ATTRIBUTE_NAME))
460            {
461                _addMandatoryDataPathAndReport(refContent, CODE_APOGEE_ATTRIBUTE_NAME, report);
462            }
463        }
464    }
465
466    /**
467     * Check if the version Apogee exist in the Apogee code
468     * @param content the content
469     * @param report the Apogee export report
470     */
471    protected void _checkVersionApogee(Content content, ExportReport report)
472    {
473        if (!content.hasValue(CODE_APOGEE_ATTRIBUTE_NAME))
474        {
475            report.addInvalidDataPath(content, new I18nizableText("plugin.odf-sync", "PLUGINS_ODF_SYNC_EXPORT_APOGEE_MANDATORY_VERSION"));
476        }
477        else
478        {
479            String codeApogee = content.getValue(CODE_APOGEE_ATTRIBUTE_NAME);
480            if (!codeApogee.contains("-") || StringUtils.isBlank(StringUtils.substringAfterLast(codeApogee, "-")))
481            {
482                report.addInvalidDataPath(content, new I18nizableText("plugin.odf-sync", "PLUGINS_ODF_SYNC_EXPORT_APOGEE_MANDATORY_VERSION"));
483            }
484        }
485    }
486    
487    /**
488     * Check if the orgUnits has a value for all mandatory data
489     * @param content the content to check
490     * @param orgUnits the list of orgUnit to check
491     * @param mandatoryData the list of mandatory data path
492     * @param report the Apogee export report
493     */
494    public void checkMandatoryDataForOrgunits(Content content, List<String> orgUnits, List<String> mandatoryData, ExportReport report)
495    {
496        if (orgUnits.size() == 0)
497        {
498            // No orgUnits, course data is invalid
499            _addMandatoryDataPathAndReport(content, "orgUnit", report);
500        }
501        else
502        {
503            for (String orgUnitId : orgUnits)
504            {
505                OrgUnit orgUnit = _resolver.resolveById(orgUnitId);
506                checkMandatoryDataForContent(orgUnit, mandatoryData, report);
507            }
508        }
509    }
510    
511    /**
512     * Create a course list in Apogee
513     * @param courseList the course list to create
514     * @param parentApogee the parent in Apogee
515     * @param creationService the service to create element in Apogee
516     * @param report the Apogee export report
517     * @throws RemoteException if an export error occurred
518     */
519    protected void _createCourseList(CourseList courseList, Content parentApogee, CreationSEMetierServiceInterface creationService, ExportReport report) throws RemoteException
520    {
521        // Create ELPs before the list
522        for (Course course : courseList.getCourses()) 
523        {
524            _createCourse(course, courseList, creationService, report);
525        }
526        
527        String codELP = getCodeApogee(parentApogee);
528        String codLSE = getCodeApogee(courseList);
529
530        _apogeeWS.createLSE(courseList, null, codLSE, creationService);
531        
532        Long nbELP = null;
533        Double ectsMin = 0D;
534        Double ectsMax = 0D;
535        String choiceTypeAmetys = courseList.getValue("choiceType");
536        if (choiceTypeAmetys.equals("CHOICE"))
537        {
538            nbELP = courseList.getValue("min");
539            
540            List<Double> ectsList = courseList.getCourses()
541                .stream()
542                .map(Course::getEcts)
543                .sorted()
544                .collect(Collectors.toList());
545            
546            for (int i = 0; i < nbELP; i++)
547            {
548                ectsMin += ectsList.get(0);
549            }
550            
551            ectsMin = (Math.floor(ectsMin) == 0D) ? 1D : Math.floor(ectsMin);
552            
553            for (int i = 1; i <= nbELP; i++)
554            {
555                ectsMax += ectsList.get(ectsList.size() - i);
556            }
557            
558            ectsMax = (Math.ceil(ectsMax) == 0D || ectsMin > ectsMax) ? 1D : Math.ceil(ectsMax);
559        }
560        
561        _apogeeWS.createLinkETPELPLSE(null, null, codLSE, codELP, nbELP, ectsMin, ectsMax, creationService);
562    }
563    
564    /**
565     * Create a course in Apogee
566     * @param course the course to create
567     * @param parentApogee the parent in Apogee
568     * @param creationService the service to create element in Apogee
569     * @param report the Apogee export report
570     * @throws RemoteException if an export error occurred
571     */
572    protected void _createCourse(Course course, Content parentApogee, CreationSEMetierServiceInterface creationService, ExportReport report) throws RemoteException
573    {
574        String codELP = getCodeApogee(course);
575        _apogeeWS.createELP(course, null, codELP, creationService);
576        
577        for (CourseList courseList : course.getCourseLists())
578        {
579            _createCourseList(courseList, course, creationService, report);
580        }
581    }
582    
583    /**
584     * Check if the container is a semester and set the exportStatus to CONTENT_STRUCTURE_INVALID if the container nature code cannot be found
585     * @param containerNatureCode The container nature code
586     * @param report The ExportReport that contains the exportStatus
587     * @return true if the container is a semester, false if it is not, and null if the container nature code could not be retrieved
588     */
589    protected boolean checkContainerSemester(String containerNatureCode, ExportReport report)
590    {
591        if (containerNatureCode.isBlank())
592        {
593            //The structure is not handled by this export because the container has no type
594            report.setStatus(ExportStatus.CONTENT_STRUCTURE_INVALID);
595            return false;
596        }
597        
598        return "semestre".equals(containerNatureCode);
599    }
600    
601    /**
602     * Check if the container is a year and set the exportStatus to CONTENT_STRUCTURE_INVALID if the container nature code cannot be found
603     * @param containerNatureCode The container nature code
604     * @param report The ExportReport that contains the exportStatus
605     * @return true if the container is a year, false if it is not, and null if the container nature code could not be retrieved
606     */
607    protected boolean checkContainerYear(String containerNatureCode, ExportReport report)
608    {
609        if (containerNatureCode.isBlank())
610        {
611            //The structure is not handled by this export because the container has no type
612            report.setStatus(ExportStatus.CONTENT_STRUCTURE_INVALID);
613            return false;
614        }
615        
616        return "annee".equals(containerNatureCode);
617    }
618    
619    private void _addMandatoryDataPathAndReport(Content content, String dataPath, ExportReport report)
620    {
621        I18nizableText invalidMessage = new I18nizableText(
622            "plugin.odf-sync",
623            "PLUGINS_ODF_SYNC_EXPORT_APOGEE_MANDATORY_FIELD",
624            Map.of("fieldName", content.getDefinition(dataPath).getLabel())
625        );
626        report.addInvalidDataPath(content, invalidMessage);
627    }
628}