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