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