001// -------------------------------------------------------------------------------- 002// Copyright 2002-2024 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.CoreControl; 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.core.server.entity.EntityInstance; 030import com.echothree.model.data.party.common.pk.PartyPK; 031import com.echothree.model.data.party.server.entity.Party; 032import com.echothree.model.data.sequence.server.entity.Sequence; 033import com.echothree.model.data.user.server.entity.UserVisit; 034import com.echothree.model.data.workeffort.server.entity.WorkEffort; 035import com.echothree.model.data.workeffort.server.entity.WorkEffortScope; 036import com.echothree.model.data.workeffort.server.entity.WorkEffortType; 037import com.echothree.model.data.workflow.server.entity.WorkflowDestination; 038import com.echothree.model.data.workflow.server.entity.WorkflowEntityStatus; 039import com.echothree.model.data.workrequirement.server.entity.WorkAssignment; 040import com.echothree.model.data.workrequirement.server.entity.WorkRequirement; 041import com.echothree.model.data.workrequirement.server.entity.WorkRequirementScope; 042import com.echothree.model.data.workrequirement.server.entity.WorkRequirementScopeDetail; 043import com.echothree.model.data.workrequirement.server.entity.WorkRequirementStatus; 044import com.echothree.model.data.workrequirement.server.entity.WorkRequirementType; 045import com.echothree.model.data.workrequirement.server.entity.WorkRequirementTypeDetail; 046import com.echothree.model.data.workrequirement.server.entity.WorkTime; 047import com.echothree.model.data.workrequirement.server.entity.WorkTimeUserVisit; 048import com.echothree.model.data.workrequirement.server.value.WorkTimeDetailValue; 049import com.echothree.util.common.persistence.BasePK; 050import com.echothree.util.server.persistence.Session; 051import java.util.List; 052 053public class WorkRequirementLogic { 054 055 private WorkRequirementLogic() { 056 super(); 057 } 058 059 private static class WorkRequirementLogicHolder { 060 static WorkRequirementLogic instance = new WorkRequirementLogic(); 061 } 062 063 public static WorkRequirementLogic getInstance() { 064 return WorkRequirementLogicHolder.instance; 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 workRequirementControl = Session.getModelController(WorkRequirementControl.class); 070 WorkEffortScope workEffortScope = workEffort.getLastDetail().getWorkEffortScope(); 071 WorkEffortType workEffortType = workEffortScope.getLastDetail().getWorkEffortType(); 072 WorkRequirementType workRequirementType = workRequirementControl.getWorkRequirementTypeByName(workEffortType, workRequirementTypeName); 073 WorkRequirementScope workRequirementScope = workRequirementControl.getWorkRequirementScope(workEffortScope, workRequirementType); 074 075 return createWorkRequirement(session, workEffort, workRequirementScope, assignedParty, assignedEndTime, requiredTime, createdBy); 076 } 077 078 public WorkRequirement createWorkRequirement(final Session session, final WorkEffort workEffort, final WorkRequirementScope workRequirementScope, 079 final Party assignedParty, final Long assignedEndTime, Long requiredTime, final BasePK createdBy) { 080 var coreControl = Session.getModelController(CoreControl.class); 081 var workflowControl = Session.getModelController(WorkflowControl.class); 082 var workRequirementControl = Session.getModelController(WorkRequirementControl.class); 083 var sequenceControl = Session.getModelController(SequenceControl.class); 084 WorkRequirementScopeDetail workRequirementScopeDetail = workRequirementScope.getLastDetail(); 085 WorkRequirementTypeDetail workRequirementTypeDetail = workRequirementScopeDetail.getWorkRequirementType().getLastDetail(); 086 Sequence 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 String workRequirementName = SequenceGeneratorLogic.getInstance().getNextSequenceValue(workRequirementSequence); 097 Long startTime = session.START_TIME_LONG; 098 Long 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.START_TIME + estimatedTimeAllowed; 108 } 109 110 WorkRequirement workRequirement = workRequirementControl.createWorkRequirement(workRequirementName, workEffort, workRequirementScope, startTime, 111 requiredTime, createdBy); 112 EntityInstance entityInstance = coreControl.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.START_TIME_LONG, 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 coreControl = Session.getModelController(CoreControl.class); 131 var workflowControl = Session.getModelController(WorkflowControl.class); 132 var workRequirementControl = Session.getModelController(WorkRequirementControl.class); 133 WorkAssignment workAssignment = workRequirementControl.createWorkAssignment(workRequirement, party, startTime, endTime, createdBy); 134 WorkRequirementStatus workRequirementStatus = workRequirementControl.getWorkRequirementStatusForUpdate(workRequirement); 135 EntityInstance entityInstance = coreControl.getEntityInstanceByBasePK(workAssignment.getPrimaryKey()); 136 137 workRequirementStatus.setLastWorkAssignment(workAssignment); 138 139 // TODO: endTime should be used as a triggerTime for the Workflow, and dump it into a difference status. Only if the workflowEntranceName 140 // isn't 'NEW_ACCEPTED' perhaps? This could trigger automatic reassignment if it hasn't been forced to a particular party. 141 workflowControl.addEntityToWorkflowUsingNames(null, WorkAssignmentStatusConstants.Workflow_WORK_ASSIGNMENT_STATUS, workflowEntranceName, 142 entityInstance, null, null, createdBy); 143 144 return workAssignment; 145 } 146 147 public WorkTime createWorkTime(final UserVisit userVisit, final WorkRequirement workRequirement, final Party party, final Long startTime, 148 final Long endTime, final boolean complete, final BasePK createdBy) { 149 var coreControl = Session.getModelController(CoreControl.class); 150 var workflowControl = Session.getModelController(WorkflowControl.class); 151 var workRequirementControl = Session.getModelController(WorkRequirementControl.class); 152 WorkTime workTime = workRequirementControl.createWorkTime(workRequirement, party, startTime, endTime, createdBy); 153 WorkRequirementStatus workRequirementStatus = workRequirementControl.getWorkRequirementStatusForUpdate(workRequirement); 154 EntityInstance entityInstance = coreControl.getEntityInstanceByBasePK(workTime.getPrimaryKey()); 155 156 workRequirementStatus.setLastWorkTime(workTime); 157 158 workflowControl.addEntityToWorkflowUsingNames(null, WorkTimeStatusConstants.Workflow_WORK_TIME_STATUS, 159 endTime == null ? WorkTimeStatusConstants.WorkflowEntrance_NEW_IN_PROGRESS : complete ? WorkTimeStatusConstants.WorkflowEntrance_NEW_COMPLETE : WorkTimeStatusConstants.WorkflowEntrance_NEW_INCOMPLETE, 160 entityInstance, null, null, createdBy); 161 162 // If there's a UserVisit and it is "IN_PROGRESS," then set it up so that it'll be ended as "INCOMPLETE" if the 163 // UserVisit is abandoned. 164 if(userVisit != null && endTime != null) { 165 workRequirementControl.createWorkTimeUserVisit(workTime, userVisit); 166 } 167 168 return workTime; 169 } 170 171 public void endWorkTime(final WorkTime workTime, final Long endTime, final boolean complete, final PartyPK endedBy) { 172 var workflowControl = Session.getModelController(WorkflowControl.class); 173 var workRequirementControl = Session.getModelController(WorkRequirementControl.class); 174 var coreControl = Session.getModelController(CoreControl.class); 175 EntityInstance entityInstance = coreControl.getEntityInstanceByBasePK(workTime.getPrimaryKey()); 176 WorkflowEntityStatus workflowEntityStatus = workflowControl.getWorkflowEntityStatusByEntityInstanceForUpdateUsingNames(WorkTimeStatusConstants.Workflow_WORK_TIME_STATUS, entityInstance); 177 WorkflowDestination workflowDestination = WorkflowDestinationLogic.getInstance().getWorkflowDestinationByName(null, workflowEntityStatus.getWorkflowStep(), 178 complete ? WorkTimeStatusConstants.WorkflowDestination_IN_PROGRESS_TO_COMPLETE : WorkTimeStatusConstants.WorkflowDestination_IN_PROGRESS_TO_INCOMPLETE); 179 WorkTimeDetailValue workTimeDetailValue = workRequirementControl.getWorkTimeDetailValueForUpdate(workTime); 180 181 workTimeDetailValue.setEndTime(endTime); 182 workRequirementControl.updateWorkTimeFromValue(workTimeDetailValue, endedBy); 183 184 workflowControl.transitionEntityInWorkflow(null, workflowEntityStatus, workflowDestination, null, endedBy); 185 } 186 187 public void endWorkTimesByUserVisit(final UserVisit userVisit, final Long endTime, final PartyPK updatedBy) { 188 var workRequirementControl = Session.getModelController(WorkRequirementControl.class); 189 List<WorkTimeUserVisit> workTimeUserVisits = workRequirementControl.getWorkTimeUserVisitsByUserVisitForUpdate(userVisit); 190 191 workTimeUserVisits.stream().map((workTimeUserVisit) -> { 192 endWorkTime(workTimeUserVisit.getWorkTime(), endTime == null ? null : userVisit.getLastCommandTime(), false, updatedBy); 193 return workTimeUserVisit; 194 }).forEach((workTimeUserVisit) -> { 195 workRequirementControl.deleteWorkTimeUserVisit(workTimeUserVisit); 196 }); 197 } 198 199}