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