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