001// --------------------------------------------------------------------------------
002// Copyright 2002-2024 Echo Three, LLC
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// --------------------------------------------------------------------------------
016
017package com.echothree.model.control.training.server.logic;
018
019import com.echothree.model.control.core.server.control.CoreControl;
020import com.echothree.model.control.training.common.exception.UnknownPartyTrainingClassNameException;
021import com.echothree.model.control.training.server.control.TrainingControl;
022import com.echothree.model.control.workeffort.common.workeffort.TrainingConstants;
023import com.echothree.model.control.workeffort.server.logic.WorkEffortLogic;
024import com.echothree.model.control.workeffort.server.logic.WorkEffortLogic.PreparedWorkEffort;
025import com.echothree.model.control.training.common.training.PartyTrainingClassStatusConstants;
026import com.echothree.model.control.workflow.server.control.WorkflowControl;
027import com.echothree.model.control.workrequirement.server.logic.WorkRequirementLogic;
028import com.echothree.model.data.core.server.entity.EntityInstance;
029import com.echothree.model.data.party.common.pk.PartyPK;
030import com.echothree.model.data.party.server.entity.Party;
031import com.echothree.model.data.training.server.entity.PartyTrainingClass;
032import com.echothree.model.data.training.server.entity.PartyTrainingClassDetail;
033import com.echothree.model.data.training.server.entity.TrainingClass;
034import com.echothree.model.data.training.server.entity.TrainingClassDetail;
035import com.echothree.model.data.training.server.entity.TrainingClassSection;
036import com.echothree.model.data.training.server.value.PartyTrainingClassDetailValue;
037import com.echothree.model.data.workeffort.server.entity.WorkEffort;
038import com.echothree.model.data.workeffort.server.entity.WorkEffortScope;
039import com.echothree.model.data.workflow.server.entity.WorkflowEntityStatus;
040import com.echothree.util.common.message.ExecutionErrors;
041import com.echothree.util.common.persistence.BasePK;
042import com.echothree.util.server.control.BaseLogic;
043import com.echothree.util.server.message.ExecutionErrorAccumulator;
044import com.echothree.util.server.persistence.Session;
045import java.util.List;
046
047public class PartyTrainingClassLogic
048        extends BaseLogic {
049    
050    private PartyTrainingClassLogic() {
051        super();
052    }
053    
054    private static class PartyTrainingClassLogicHolder {
055        static PartyTrainingClassLogic instance = new PartyTrainingClassLogic();
056    }
057    
058    public static PartyTrainingClassLogic getInstance() {
059        return PartyTrainingClassLogicHolder.instance;
060    }
061    
062    public PartyTrainingClass getPartyTrainingClassByName(final ExecutionErrorAccumulator eea, final String partyTrainingClassName) {
063        var trainingControl = Session.getModelController(TrainingControl.class);
064        PartyTrainingClass partyTrainingClass = trainingControl.getPartyTrainingClassByName(partyTrainingClassName);
065
066        if(partyTrainingClass == null) {
067            handleExecutionError(UnknownPartyTrainingClassNameException.class, eea, ExecutionErrors.UnknownPartyTrainingClassName.name(), partyTrainingClassName);
068        }
069
070        return partyTrainingClass;
071    }
072    
073    private void insertPartyTrainingClassIntoWorkflow(final EntityInstance entityInstance, final Long completedTime, final Long validUntilTime,
074            final BasePK partyPK) {
075        var workflowControl = Session.getModelController(WorkflowControl.class);
076        String workflowEntranceName = completedTime == null ? PartyTrainingClassStatusConstants.WorkflowEntrance_NEW_ASSIGNED : PartyTrainingClassStatusConstants.WorkflowEntrance_NEW_PASSED;
077        
078        workflowControl.addEntityToWorkflowUsingNames(null, PartyTrainingClassStatusConstants.Workflow_PARTY_TRAINING_CLASS_STATUS, workflowEntranceName,
079                entityInstance, null, validUntilTime, partyPK);
080    }
081
082    public static class PreparedPartyTrainingClass {
083        
084        private Party party;
085        private TrainingClass trainingClass;
086        private Long completedTime;
087        private Long validUntilTime;
088        private PreparedWorkEffort preparedWorkEffort;
089
090        public Party getParty() {
091            return party;
092        }
093
094        public void setParty(Party party) {
095            this.party = party;
096        }
097
098        public TrainingClass getTrainingClass() {
099            return trainingClass;
100        }
101
102        public void setTrainingClass(TrainingClass trainingClass) {
103            this.trainingClass = trainingClass;
104        }
105
106        public Long getCompletedTime() {
107            return completedTime;
108        }
109
110        public void setCompletedTime(Long completedTime) {
111            this.completedTime = completedTime;
112        }
113
114        public Long getValidUntilTime() {
115            return validUntilTime;
116        }
117
118        public void setValidUntilTime(Long validUntilTime) {
119            this.validUntilTime = validUntilTime;
120        }
121
122        public PreparedWorkEffort getPreparedWorkEffort() {
123            return preparedWorkEffort;
124        }
125
126        public void setPreparedWorkEffort(PreparedWorkEffort preparedWorkEffort) {
127            this.preparedWorkEffort = preparedWorkEffort;
128        }
129
130    }
131
132    public PreparedPartyTrainingClass preparePartyTrainingClass(final ExecutionErrorAccumulator eea, final Party party, final TrainingClass trainingClass,
133            final Long completedTime, final Long validUntilTime) {
134        PreparedPartyTrainingClass preparedPartyTrainingClass = new PreparedPartyTrainingClass();
135
136        preparedPartyTrainingClass.setParty(party);
137        preparedPartyTrainingClass.setTrainingClass(trainingClass);
138        preparedPartyTrainingClass.setCompletedTime(completedTime);
139        preparedPartyTrainingClass.setValidUntilTime(validUntilTime);
140
141        if(completedTime == null) {
142            WorkEffortScope workEffortScope = trainingClass.getLastDetail().getWorkEffortScope();
143
144            if(workEffortScope != null) {
145                TrainingClassDetail trainingClassDetail = trainingClass.getLastDetail();
146                Long estimatedReadingTime = trainingClassDetail.getEstimatedReadingTime();
147                Long readingTimeAllowed = trainingClassDetail.getReadingTimeAllowed();
148                Long estimatedTestingTime = trainingClassDetail.getEstimatedTestingTime();
149                Long testingTimeAllowed = trainingClassDetail.getTestingTimeAllowed();
150                Long requiredCompletionTime = trainingClassDetail.getRequiredCompletionTime();
151
152                if(estimatedReadingTime == null) {
153                    eea.addExecutionError(ExecutionErrors.MissingEstimatedReadingTime.name(), trainingClassDetail.getTrainingClassName());
154                }
155                if(readingTimeAllowed == null) {
156                    eea.addExecutionError(ExecutionErrors.MissingReadingTimeAllowed.name(), trainingClassDetail.getTrainingClassName());
157                }
158                if(estimatedTestingTime == null) {
159                    eea.addExecutionError(ExecutionErrors.MissingEstimatedTestingTime.name(), trainingClassDetail.getTrainingClassName());
160                }
161                if(testingTimeAllowed == null) {
162                    eea.addExecutionError(ExecutionErrors.MissingTestingTimeAllowed.name(), trainingClassDetail.getTrainingClassName());
163                }
164                if(requiredCompletionTime == null) {
165                    eea.addExecutionError(ExecutionErrors.MissingRequiredCompletionTime.name(), trainingClassDetail.getTrainingClassName());
166                }
167
168                if(eea == null || !eea.hasExecutionErrors()) {
169                    Long estimatedTimeAllowed = estimatedReadingTime + estimatedTestingTime;
170                    Long maximumTimeAllowed = readingTimeAllowed + testingTimeAllowed;
171
172                    preparedPartyTrainingClass.setPreparedWorkEffort(WorkEffortLogic.getInstance().prepareForWorkEffort(eea, workEffortScope,
173                            requiredCompletionTime, estimatedTimeAllowed, maximumTimeAllowed));
174                }
175            }
176        }
177
178        return preparedPartyTrainingClass;
179    }
180
181    public PartyTrainingClass createPartyTrainingClass(final Session session, final PreparedPartyTrainingClass preparedPartyTrainingClass,
182            final BasePK createdBy) {
183        var coreControl = Session.getModelController(CoreControl.class);
184        var trainingControl = Session.getModelController(TrainingControl.class);
185        Party party = preparedPartyTrainingClass.getParty();
186        TrainingClass trainingClass = preparedPartyTrainingClass.getTrainingClass();
187        Long completedTime = preparedPartyTrainingClass.getCompletedTime();
188        WorkEffort workEffort = null;
189
190        PartyTrainingClass partyTrainingClass = trainingControl.createPartyTrainingClass(preparedPartyTrainingClass.getParty(), trainingClass,
191                preparedPartyTrainingClass.completedTime, preparedPartyTrainingClass.getValidUntilTime(), createdBy);
192        EntityInstance entityInstance = coreControl.getEntityInstanceByBasePK(partyTrainingClass.getPrimaryKey());
193
194        if(completedTime == null) {
195            PreparedWorkEffort preparedWorkEffort = preparedPartyTrainingClass.getPreparedWorkEffort();
196
197            if(preparedWorkEffort != null) {
198                Long requiredCompletionTime = trainingClass.getLastDetail().getRequiredCompletionTime();
199                Long requiredTime = requiredCompletionTime == null ? null : session.START_TIME + requiredCompletionTime;
200
201                workEffort = WorkEffortLogic.getInstance().createWorkEffort(preparedWorkEffort, entityInstance, createdBy);
202
203                WorkRequirementLogic.getInstance().createWorkRequirementUsingNames(session, workEffort, TrainingConstants.WorkRequirementType_TRAINING,
204                    party, null, requiredTime, createdBy);
205            }
206        }
207
208        insertPartyTrainingClassIntoWorkflow(entityInstance, preparedPartyTrainingClass.getCompletedTime(), preparedPartyTrainingClass.getValidUntilTime(),
209                createdBy);
210
211        if(completedTime == null) {
212            List<TrainingClassSection> trainingClassSections = trainingControl.getTrainingClassSections(trainingClass);
213
214            // If there are Pages to read, or Questions to answer, then setup a PartyTrainingClassSession for them.
215            if(trainingControl.countTrainingClassPages(trainingClassSections) != 0 || trainingControl.countTrainingClassQuestions(trainingClassSections) != 0) {
216                PartyTrainingClassSessionLogic.getInstance().createPartyTrainingClassSession(partyTrainingClass, createdBy);
217            }
218        }
219
220        return partyTrainingClass;
221    }
222
223    /**
224     * Check to make sure that the Party requesting the Party Training Class is the one that its assigned to, and verify that its in a valid
225     * status for requesting the information.
226     */
227    public void checkPartyTrainingClassStatus(final ExecutionErrorAccumulator eea, final PartyTrainingClass partyTrainingClass, final PartyPK modifiedBy) {
228        PartyTrainingClassDetail partyTrainingClassDetail = partyTrainingClass.getLastDetail();
229        boolean invalidPartyTrainingClass = false;
230
231        if(modifiedBy.equals(partyTrainingClassDetail.getPartyPK())) {
232            var coreControl = Session.getModelController(CoreControl.class);
233            EntityInstance entityInstance = coreControl.getEntityInstanceByBasePK(partyTrainingClass.getPrimaryKey());
234            var workflowControl = Session.getModelController(WorkflowControl.class);
235            WorkflowEntityStatus workflowEntityStatus = workflowControl.getWorkflowEntityStatusByEntityInstanceForUpdateUsingNames(PartyTrainingClassStatusConstants.Workflow_PARTY_TRAINING_CLASS_STATUS,
236                    entityInstance);
237            String workflowStepName = workflowEntityStatus.getWorkflowStep().getLastDetail().getWorkflowStepName();
238
239            // Check to see if its ASSIGNED. If it is, move it to TRAINING.
240            if(workflowStepName.equals(PartyTrainingClassStatusConstants.WorkflowStep_ASSIGNED)) {
241                workflowControl.transitionEntityInWorkflowUsingNames(null, workflowEntityStatus, PartyTrainingClassStatusConstants.WorkflowDestination_ASSIGNED_TO_TRAINING,
242                        workflowControl.getWorkflowTriggerTime(workflowEntityStatus), modifiedBy);
243            } else if(!workflowStepName.equals(PartyTrainingClassStatusConstants.WorkflowStep_TRAINING)) {
244                invalidPartyTrainingClass = true;
245            }
246        } else {
247            invalidPartyTrainingClass = true;
248        }
249
250        if(invalidPartyTrainingClass) {
251            eea.addExecutionError(ExecutionErrors.InvalidPartyTrainingClass.name(), partyTrainingClassDetail.getPartyTrainingClassName());
252        }
253    }
254
255    public void updatePartyTrainingClassFromValue(final PartyTrainingClassDetailValue partyTrainingClassDetailValue, final BasePK updatedBy) {
256        var trainingControl = Session.getModelController(TrainingControl.class);
257        
258        // TODO: adjust Status if necessary
259        // TODO: delete PartyTrainingClassStatus if necessary
260        
261        trainingControl.updatePartyTrainingClassFromValue(partyTrainingClassDetailValue, updatedBy);
262    }
263    
264    public void deletePartyTrainingClass(PartyTrainingClass partyTrainingClass, final BasePK deleteBy) {
265        var trainingControl = Session.getModelController(TrainingControl.class);
266        
267        trainingControl.deletePartyTrainingClass(partyTrainingClass, deleteBy);
268    }
269    
270}