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.workrequirement.server.logic; 018 019import com.echothree.model.control.core.server.control.EntityInstanceControl; 020import com.echothree.model.control.sequence.common.SequenceTypes; 021import com.echothree.model.control.sequence.server.control.SequenceControl; 022import com.echothree.model.control.sequence.server.logic.SequenceGeneratorLogic; 023import com.echothree.model.control.workflow.server.control.WorkflowControl; 024import com.echothree.model.control.workflow.server.logic.WorkflowDestinationLogic; 025import com.echothree.model.control.workrequirement.common.workflow.WorkAssignmentStatusConstants; 026import com.echothree.model.control.workrequirement.common.workflow.WorkRequirementStatusConstants; 027import com.echothree.model.control.workrequirement.common.workflow.WorkTimeStatusConstants; 028import com.echothree.model.control.workrequirement.server.control.WorkRequirementControl; 029import com.echothree.model.data.party.common.pk.PartyPK; 030import com.echothree.model.data.party.server.entity.Party; 031import com.echothree.model.data.user.server.entity.UserVisit; 032import com.echothree.model.data.workeffort.server.entity.WorkEffort; 033import com.echothree.model.data.workrequirement.server.entity.WorkAssignment; 034import com.echothree.model.data.workrequirement.server.entity.WorkRequirement; 035import com.echothree.model.data.workrequirement.server.entity.WorkRequirementScope; 036import com.echothree.model.data.workrequirement.server.entity.WorkTime; 037import com.echothree.util.common.persistence.BasePK; 038import com.echothree.util.server.persistence.Session; 039import javax.enterprise.context.ApplicationScoped; 040import javax.enterprise.inject.spi.CDI; 041import javax.inject.Inject; 042 043@ApplicationScoped 044public class WorkRequirementLogic { 045 046 @Inject 047 EntityInstanceControl entityInstanceControl; 048 049 @Inject 050 SequenceControl sequenceControl; 051 052 @Inject 053 WorkRequirementControl workRequirementControl; 054 055 @Inject 056 WorkflowControl workflowControl; 057 058 @Inject 059 SequenceGeneratorLogic sequenceGeneratorLogic; 060 061 @Inject 062 WorkflowDestinationLogic workflowDestinationLogic; 063 064 protected WorkRequirementLogic() { 065 super(); 066 } 067 068 public static WorkRequirementLogic getInstance() { 069 return CDI.current().select(WorkRequirementLogic.class).get(); 070 } 071 072 public WorkRequirement createWorkRequirementUsingNames(final Session session, final WorkEffort workEffort, final String workRequirementTypeName, 073 final Party assignedParty, final Long assignedEndTime, final Long requiredTime, final BasePK createdBy) { 074 var workEffortScope = workEffort.getLastDetail().getWorkEffortScope(); 075 var workEffortType = workEffortScope.getLastDetail().getWorkEffortType(); 076 var workRequirementType = workRequirementControl.getWorkRequirementTypeByName(workEffortType, workRequirementTypeName); 077 var workRequirementScope = workRequirementControl.getWorkRequirementScope(workEffortScope, workRequirementType); 078 079 return createWorkRequirement(session, workEffort, workRequirementScope, assignedParty, assignedEndTime, requiredTime, createdBy); 080 } 081 082 public WorkRequirement createWorkRequirement(final Session session, final WorkEffort workEffort, final WorkRequirementScope workRequirementScope, 083 final Party assignedParty, final Long assignedEndTime, Long requiredTime, final BasePK createdBy) { 084 var workRequirementScopeDetail = workRequirementScope.getLastDetail(); 085 var workRequirementTypeDetail = workRequirementScopeDetail.getWorkRequirementType().getLastDetail(); 086 var workRequirementSequence = workRequirementScopeDetail.getWorkRequirementSequence(); 087 088 if(workRequirementSequence == null) { 089 workRequirementSequence = workRequirementTypeDetail.getWorkRequirementSequence(); 090 091 if(workRequirementSequence == null) { 092 workRequirementSequence = sequenceControl.getDefaultSequenceUsingNames(SequenceTypes.WORK_REQUIREMENT.name()); 093 } 094 } 095 096 var workRequirementName = sequenceGeneratorLogic.getNextSequenceValue(workRequirementSequence); 097 var startTime = session.getStartTime(); 098 var estimatedTimeAllowed = workRequirementScopeDetail.getEstimatedTimeAllowed(); 099 100 if(estimatedTimeAllowed == null) { 101 estimatedTimeAllowed = workRequirementTypeDetail.getEstimatedTimeAllowed(); 102 } 103 104 // If a requiredTime wasn't supplied, and there's an estimatedTimeAllowed available, then take the current 105 // time + the esimtatedTimeAllowed and use that as the requiredTime. 106 if(requiredTime == null && estimatedTimeAllowed != null) { 107 requiredTime = session.getStartTime() + estimatedTimeAllowed; 108 } 109 110 var workRequirement = workRequirementControl.createWorkRequirement(workRequirementName, workEffort, workRequirementScope, startTime, 111 requiredTime, createdBy); 112 var entityInstance = entityInstanceControl.getEntityInstanceByBasePK(workRequirement.getPrimaryKey()); 113 114 // TODO: should requiredTime map into triggerTime? 115 workflowControl.addEntityToWorkflowUsingNames(null, WorkRequirementStatusConstants.Workflow_WORK_REQUIREMENT_STATUS, 116 assignedParty == null ? WorkRequirementStatusConstants.WorkflowEntrance_NEW_UNASSIGNED : WorkRequirementStatusConstants.WorkflowEntrance_NEW_ASSIGNED, 117 entityInstance, null, null, createdBy); 118 119 if(assignedParty != null) { 120 // If an assignedParty is specified, then create a WorkAssignment and do not give that Party a choice on acceptance. 121 createWorkAssignment(workRequirement, assignedParty, session.getStartTime(), assignedEndTime, 122 WorkAssignmentStatusConstants.WorkflowEntrance_NEW_ACCEPTED, createdBy); 123 } 124 125 return workRequirement; 126 } 127 128 public WorkAssignment createWorkAssignment(final WorkRequirement workRequirement, final Party party, final Long startTime, final Long endTime, 129 final String workflowEntranceName, final BasePK createdBy) { 130 var workAssignment = workRequirementControl.createWorkAssignment(workRequirement, party, startTime, endTime, createdBy); 131 var workRequirementStatus = workRequirementControl.getWorkRequirementStatusForUpdate(workRequirement); 132 var entityInstance = entityInstanceControl.getEntityInstanceByBasePK(workAssignment.getPrimaryKey()); 133 134 workRequirementStatus.setLastWorkAssignment(workAssignment); 135 136 // TODO: endTime should be used as a triggerTime for the Workflow, and dump it into a difference status. Only if the workflowEntranceName 137 // isn't 'NEW_ACCEPTED' perhaps? This could trigger automatic reassignment if it hasn't been forced to a particular party. 138 workflowControl.addEntityToWorkflowUsingNames(null, WorkAssignmentStatusConstants.Workflow_WORK_ASSIGNMENT_STATUS, workflowEntranceName, 139 entityInstance, null, null, createdBy); 140 141 return workAssignment; 142 } 143 144 public WorkTime createWorkTime(final UserVisit userVisit, final WorkRequirement workRequirement, final Party party, final Long startTime, 145 final Long endTime, final boolean complete, final BasePK createdBy) { 146 var workTime = workRequirementControl.createWorkTime(workRequirement, party, startTime, endTime, createdBy); 147 var workRequirementStatus = workRequirementControl.getWorkRequirementStatusForUpdate(workRequirement); 148 var entityInstance = entityInstanceControl.getEntityInstanceByBasePK(workTime.getPrimaryKey()); 149 150 workRequirementStatus.setLastWorkTime(workTime); 151 152 workflowControl.addEntityToWorkflowUsingNames(null, WorkTimeStatusConstants.Workflow_WORK_TIME_STATUS, 153 endTime == null ? WorkTimeStatusConstants.WorkflowEntrance_NEW_IN_PROGRESS : complete ? WorkTimeStatusConstants.WorkflowEntrance_NEW_COMPLETE : WorkTimeStatusConstants.WorkflowEntrance_NEW_INCOMPLETE, 154 entityInstance, null, null, createdBy); 155 156 // If there's a UserVisit and it is "IN_PROGRESS," then set it up so that it'll be ended as "INCOMPLETE" if the 157 // UserVisit is abandoned. 158 if(userVisit != null && endTime != null) { 159 workRequirementControl.createWorkTimeUserVisit(workTime, userVisit); 160 } 161 162 return workTime; 163 } 164 165 public void endWorkTime(final WorkTime workTime, final Long endTime, final boolean complete, final PartyPK endedBy) { 166 var entityInstance = entityInstanceControl.getEntityInstanceByBasePK(workTime.getPrimaryKey()); 167 var workflowEntityStatus = workflowControl.getWorkflowEntityStatusByEntityInstanceForUpdateUsingNames(WorkTimeStatusConstants.Workflow_WORK_TIME_STATUS, entityInstance); 168 var workflowDestination = workflowDestinationLogic.getWorkflowDestinationByName(null, workflowEntityStatus.getWorkflowStep(), 169 complete ? WorkTimeStatusConstants.WorkflowDestination_IN_PROGRESS_TO_COMPLETE : WorkTimeStatusConstants.WorkflowDestination_IN_PROGRESS_TO_INCOMPLETE); 170 var workTimeDetailValue = workRequirementControl.getWorkTimeDetailValueForUpdate(workTime); 171 172 workTimeDetailValue.setEndTime(endTime); 173 workRequirementControl.updateWorkTimeFromValue(workTimeDetailValue, endedBy); 174 175 workflowControl.transitionEntityInWorkflow(null, workflowEntityStatus, workflowDestination, null, endedBy); 176 } 177 178 public void endWorkTimesByUserVisit(final UserVisit userVisit, final Long endTime, final PartyPK updatedBy) { 179 var workTimeUserVisits = workRequirementControl.getWorkTimeUserVisitsByUserVisitForUpdate(userVisit); 180 181 workTimeUserVisits.stream().map((workTimeUserVisit) -> { 182 endWorkTime(workTimeUserVisit.getWorkTime(), endTime == null ? null : userVisit.getLastCommandTime(), false, updatedBy); 183 return workTimeUserVisit; 184 }).forEach((workTimeUserVisit) -> { 185 workRequirementControl.deleteWorkTimeUserVisit(workTimeUserVisit); 186 }); 187 } 188 189}