diff --git a/charts/pcs-api/values.ccd.preview.template.yaml b/charts/pcs-api/values.ccd.preview.template.yaml index 77a6ca3dc3..f106f488a0 100644 --- a/charts/pcs-api/values.ccd.preview.template.yaml +++ b/charts/pcs-api/values.ccd.preview.template.yaml @@ -177,7 +177,7 @@ xui-webapp: nodejs: imagePullPolicy: Always releaseNameOverride: ${SERVICE_NAME}-xui-webapp - image: hmctsprod.azurecr.io/xui/webapp:latest + image: hmctsprod.azurecr.io/xui/webapp:pr-5239 ingressHost: xui-${SERVICE_FQDN} devmemoryRequests: 2Gi devcpuRequests: 500m diff --git a/charts/pcs-api/values.wa.preview.template.yaml b/charts/pcs-api/values.wa.preview.template.yaml index e30b0a1e88..7ea6a267ef 100644 --- a/charts/pcs-api/values.wa.preview.template.yaml +++ b/charts/pcs-api/values.wa.preview.template.yaml @@ -1,30 +1,6 @@ java: - secrets: - AZURE_SERVICE_BUS_CONNECTION_STRING: - secretRef: pcs-sb-preview - key: connectionString - AZURE_SERVICE_BUS_PREVIEW_CONNECTION_STRING: - secretRef: pcs-sb-preview - key: connectionString environment: - SPRING_JMS_SERVICEBUS_ENABLED: true - AZURE_SERVICE_BUS_TOPIC_NAME: ${SERVICE_NAME}-asb-ccd-case-events - AZURE_SERVICE_BUS_SUBSCRIPTION_NAME: ${SERVICE_NAME}-asb-ccd-case-events - -servicebus: - enabled: true - releaseNameOverride: ${SERVICE_NAME}-asb - resourceGroup: pcs-aso-preview-rg - sbNamespace: pcs-servicebus-preview - setup: - topics: - - name: crd-topic - subscriptionNeeded: yes - - name: jrd-topic - subscriptionNeeded: yes - - name: ccd-case-events - subscriptionNeeded: yes - requiresSession: true + CAMUNDA_BASE_URL: http://pcs-api-pr-${CHANGE_ID}-camunda/engine-rest postgresql: enabled: true @@ -52,7 +28,7 @@ wa: camunda-bpm: java: - image: hmctsprod.azurecr.io/camunda/bpm:latest + image: hmctsprod.azurecr.io/camunda/bpm:pr-1025 wa-case-event-handler: java: @@ -64,6 +40,7 @@ wa: AZURE_SERVICE_BUS_TOPIC_NAME: ${SERVICE_NAME}-asb-ccd-case-events AZURE_SERVICE_BUS_SUBSCRIPTION_NAME: ${SERVICE_NAME}-asb-ccd-case-events AZURE_SERVICE_BUS_CCD_CASE_EVENTS_SUBSCRIPTION_NAME: ${SERVICE_NAME}-asb-ccd-case-events + ROLE_ASSIGNMENT_URL: http://am-role-assignment-service-aat.service.core-compute-aat.internal keyVaults: wa: secrets: @@ -80,8 +57,9 @@ wa: java: ingressHost: wa-task-management-api-${SERVICE_FQDN} environment: - ALLOWED_CASE_TYPES: pcs-${CHANGE_ID} + ALLOWED_CASE_TYPES: pcs ALLOWED_JURISDICTIONS: pcs + ROLE_ASSIGNMENT_URL: http://am-role-assignment-service-aat.service.core-compute-aat.internal keyVaults: wa: secrets: @@ -136,6 +114,8 @@ wa: wa-initiation-batch: job: schedule: "*/1 * * * *" + disableActiveClusterCheck: true + suspend: false wa-reconfiguration-batch: job: @@ -144,6 +124,8 @@ wa: wa-termination-batch: job: schedule: "*/1 * * * *" + disableActiveClusterCheck: true + suspend: false #disable wa db as we are using the postgres flexible server wa: diff --git a/charts/pcs-api/values.yaml b/charts/pcs-api/values.yaml index 1da5034c53..ac53b2d4c3 100644 --- a/charts/pcs-api/values.yaml +++ b/charts/pcs-api/values.yaml @@ -54,6 +54,7 @@ java: CCD_DATA_STORE_URL: "http://ccd-data-store-api-{{ .Values.global.environment }}.service.core-compute-{{ .Values.global.environment }}.internal" CASE_DOCUMENT_AM_URL: "http://ccd-case-document-am-api-{{ .Values.global.environment }}.service.core-compute-{{ .Values.global.environment }}.internal" RD_PROFESSIONAL_API_URL: "http://rd-professional-api-{{ .Values.global.environment }}.service.core-compute-{{ .Values.global.environment }}.internal" + CAMUNDA_BASE_URL: "http://camunda-api-{{ .Values.global.environment }}.service.core-compute-{{ .Values.global.environment }}.internal" servicebus: enabled: false ccd: diff --git a/featureFlags/flags.json b/featureFlags/flags.json index d10cfbd063..39b143347f 100644 --- a/featureFlags/flags.json +++ b/featureFlags/flags.json @@ -3,6 +3,7 @@ "bulk-print-enabled": false, "caseworker-events-enabled": true, "cui-respond-to-claim-lr-enabled": true, - "release-1.2-enabled": true + "release-1.2-enabled": true, + "caseworker-wa-enabled": false } } diff --git a/src/cftlib/java/uk/gov/hmcts/reform/pcs/CftlibConfig.java b/src/cftlib/java/uk/gov/hmcts/reform/pcs/CftlibConfig.java index 9b12decc83..42521e443b 100644 --- a/src/cftlib/java/uk/gov/hmcts/reform/pcs/CftlibConfig.java +++ b/src/cftlib/java/uk/gov/hmcts/reform/pcs/CftlibConfig.java @@ -73,6 +73,7 @@ private void createAccessProfiles(CFTLib lib) { roleNames.add("caseworker"); roleNames.add("caseworker-ras-validation"); roleNames.add("GS_profile"); + roleNames.add("caseworker-wa-task-configuration"); lib.createRoles(roleNames.toArray(new String[0])); } diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/Application.java b/src/main/java/uk/gov/hmcts/reform/pcs/Application.java index 2ab8a1f048..4a16ebb549 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/Application.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/Application.java @@ -7,6 +7,7 @@ import uk.gov.hmcts.reform.ccd.document.am.feign.CaseDocumentClientApi; import uk.gov.hmcts.reform.fees.client.FeesApi; import uk.gov.hmcts.reform.idam.client.IdamApi; +import uk.gov.hmcts.reform.pcs.camunda.CamundaApi; import uk.gov.hmcts.reform.pcs.hearings.service.api.HmcHearingApi; import uk.gov.hmcts.reform.pcs.idam.IdamUserInfoApi; import uk.gov.hmcts.reform.pcs.location.service.api.LocationReferenceApi; @@ -31,7 +32,8 @@ RdProfessionalApi.class, FeesApi.class, - CaseDocumentClientApi.class + CaseDocumentClientApi.class, + CamundaApi.class } ) diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/camunda/CamundaApi.java b/src/main/java/uk/gov/hmcts/reform/pcs/camunda/CamundaApi.java new file mode 100644 index 0000000000..0b59066b61 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/pcs/camunda/CamundaApi.java @@ -0,0 +1,19 @@ +package uk.gov.hmcts.reform.pcs.camunda; + +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestHeader; + +@FeignClient(name = "camunda", url = "${camunda.url}") +public interface CamundaApi { + String SERVICE_AUTHORIZATION = "ServiceAuthorization"; + + @PostMapping( + value = "/message", + consumes = MediaType.APPLICATION_JSON_VALUE, + produces = MediaType.APPLICATION_JSON_VALUE) + void sendMessage(@RequestHeader(SERVICE_AUTHORIZATION) String serviceAuthorisation, + SendMessageRequest sendMessageRequest); + +} diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/camunda/CamundaRequestTaskComponent.java b/src/main/java/uk/gov/hmcts/reform/pcs/camunda/CamundaRequestTaskComponent.java new file mode 100644 index 0000000000..ad0eba8ae1 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/pcs/camunda/CamundaRequestTaskComponent.java @@ -0,0 +1,63 @@ +package uk.gov.hmcts.reform.pcs.camunda; + +import com.github.kagkarlsson.scheduler.task.CompletionHandler; +import com.github.kagkarlsson.scheduler.task.FailureHandler; +import com.github.kagkarlsson.scheduler.task.TaskDescriptor; +import com.github.kagkarlsson.scheduler.task.helper.CustomTask; +import com.github.kagkarlsson.scheduler.task.helper.Tasks; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Component; + +import java.time.Duration; + +@Slf4j +@Component +public class CamundaRequestTaskComponent { + + private static final String CAMUNDA_REQUEST_TASK_NAME = "camunda-request-task"; + + public static final TaskDescriptor CAMUNDA_REQUEST_TASK_DESCRIPTOR = + TaskDescriptor.of(CAMUNDA_REQUEST_TASK_NAME, CamundaRequestTaskData.class); + + private final CamundaService camundaService; + private final int maxRetries; + private final Duration backoffDelay; + + public CamundaRequestTaskComponent(CamundaService camundaService, + @Value("${camunda.request.max-retries}") int maxRetries, + @Value("${camunda.request.backoff-delay-duration}") Duration backoffDelay) { + + this.camundaService = camundaService; + this.maxRetries = maxRetries; + this.backoffDelay = backoffDelay; + } + + @Bean + public CustomTask camundaRequestTask() { + return Tasks.custom(CAMUNDA_REQUEST_TASK_DESCRIPTOR) + .onFailure( + FailureHandler.maxRetries(maxRetries) + .withBackoff(backoffDelay) + .thenRemove(complete -> + log.warn( + "Execution has failed {} times. Giving up.", + complete.getExecution().consecutiveFailures + ) + )) + .execute((taskInstance, executionContext) -> { + CamundaRequestTaskData taskData = taskInstance.getData(); + log.debug("Executing Camunda request task for {}", taskData); + try { + camundaService.handleRequest(taskData); + return new CompletionHandler.OnCompleteRemove<>(); + } catch (Exception e) { + log.error("Failed to create Camunda request for: {}. Attempt {}/{}", + taskData, executionContext.getExecution().consecutiveFailures + 1, maxRetries, e); + throw e; + } + }); + } + +} diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/camunda/CamundaRequestTaskData.java b/src/main/java/uk/gov/hmcts/reform/pcs/camunda/CamundaRequestTaskData.java new file mode 100644 index 0000000000..bf3e41f0dc --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/pcs/camunda/CamundaRequestTaskData.java @@ -0,0 +1,23 @@ +package uk.gov.hmcts.reform.pcs.camunda; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +@AllArgsConstructor +public class CamundaRequestTaskData { + + enum Action { + CREATE, + CANCEL + } + + private final Action action; + + private final long caseReference; + + private final TaskType taskType; + +} diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/camunda/CamundaService.java b/src/main/java/uk/gov/hmcts/reform/pcs/camunda/CamundaService.java new file mode 100644 index 0000000000..c2ae9dd2bd --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/pcs/camunda/CamundaService.java @@ -0,0 +1,152 @@ +package uk.gov.hmcts.reform.pcs.camunda; + +import com.github.kagkarlsson.scheduler.SchedulerClient; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import uk.gov.hmcts.reform.authorisation.generators.AuthTokenGenerator; +import uk.gov.hmcts.reform.pcs.camunda.CamundaRequestTaskData.Action; +import uk.gov.hmcts.reform.pcs.ccd.CaseType; +import uk.gov.hmcts.reform.pcs.service.FeatureFlag; +import uk.gov.hmcts.reform.pcs.service.FeatureToggleService; + +import java.time.Clock; +import java.time.Instant; +import java.time.LocalDateTime; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME; +import static uk.gov.hmcts.reform.pcs.camunda.CamundaRequestTaskComponent.CAMUNDA_REQUEST_TASK_DESCRIPTOR; + +@Slf4j +@AllArgsConstructor +@Service +public class CamundaService { + + private final CamundaApi camundaApi; + private final AuthTokenGenerator authTokenGenerator; + private final SchedulerClient schedulerClient; + private final FeatureToggleService featureToggleService; + + private static final String CREATE = "createTaskMessage"; + private static final String CANCEL = "cancelTasks"; + private static final String UNCONFIGURED = "unconfigured"; + private static final String EMPTY_WARNINGS_LIST = "[]"; + private static final String CANCELLATION_PROCESS = "CASE_EVENT_CANCELLATION"; + private final Clock utcClock; + + public void createTask(long caseId, TaskType taskType) { + scheduleCamundaRequest(Action.CREATE, caseId, taskType); + } + + public void cancelTask(long caseId, TaskType taskType) { + scheduleCamundaRequest(Action.CANCEL, caseId, taskType); + } + + void handleRequest(CamundaRequestTaskData taskData) { + switch (taskData.getAction()) { + case CREATE -> requestTaskCreation(taskData.getCaseReference(), taskData.getTaskType()); + case CANCEL -> requestTaskCancellation(taskData.getCaseReference(), taskData.getTaskType()); + } + } + + private void scheduleCamundaRequest(Action action, long caseId, TaskType taskType) { + if (!featureToggleService.isEnabled(FeatureFlag.CASEWORKER_WA)) { + log.info("Skipped scheduling Camunda request for {}", caseId); + return; + } + + CamundaRequestTaskData taskData = CamundaRequestTaskData.builder() + .action(action) + .caseReference(caseId) + .taskType(taskType) + .build(); + + schedulerClient.scheduleIfNotExists( + CAMUNDA_REQUEST_TASK_DESCRIPTOR + .instance(UUID.randomUUID().toString()) + .data(taskData) + .scheduledTo(Instant.now(utcClock))); + } + + private void requestTaskCreation(Long caseId, TaskType taskType) { + if (!featureToggleService.isEnabled(FeatureFlag.CASEWORKER_WA)) { + log.info("Skipped creating task for {}", caseId); + return; + } + + log.info("Creating task for {}", caseId); + Map> processVariables = new ConcurrentHashMap<>(); + + LocalDateTime delayUntil = LocalDateTime.now(utcClock); + LocalDateTime dueDate = delayUntil.plusDays(taskType.getWorkingDays()); + + processVariables.put("taskState", dmnStringValue(UNCONFIGURED)); + processVariables.put("caseTypeId", dmnStringValue(CaseType.getCaseType())); + processVariables.put("dueDate", dmnStringValue(dueDate.format(ISO_LOCAL_DATE_TIME))); + processVariables.put("workingDaysAllowed", dmnIntegerValue(taskType.getWorkingDays())); + processVariables.put("jurisdiction", dmnStringValue(CaseType.getJurisdictionId())); + processVariables.put("name", dmnStringValue(taskType.getName())); + processVariables.put("taskId", dmnStringValue(taskType.getId())); + processVariables.put("caseId", dmnStringValue(caseId.toString())); + processVariables.put("delayUntil", dmnStringValue(delayUntil.format(ISO_LOCAL_DATE_TIME))); + processVariables.put("hasWarnings", dmnBooleanValue(false)); + processVariables.put("warningList", dmnStringValue(EMPTY_WARNINGS_LIST)); + processVariables.put("__processCategory__" + taskType.getId(), dmnBooleanValue(true)); + + SendMessageRequest request = SendMessageRequest.builder() + .messageName(CREATE) + .processVariables(processVariables) + .build(); + + sendCamundaRequest(request, caseId); + } + + private void requestTaskCancellation(Long caseId, TaskType taskType) { + if (!featureToggleService.isEnabled(FeatureFlag.CASEWORKER_WA)) { + log.info("Skipped cancelling task for {}", caseId); + return; + } + + Map> correlationKeys = new ConcurrentHashMap<>(); + correlationKeys.put("caseId", dmnStringValue(caseId.toString())); + correlationKeys.put("__processCategory__" + taskType.getId(), dmnBooleanValue(true)); + + Map> processVariables = new ConcurrentHashMap<>(); + processVariables.put("cancellationProcess", dmnStringValue(CANCELLATION_PROCESS)); + + SendMessageRequest request = SendMessageRequest.builder() + .messageName(CANCEL) + .processVariables(processVariables) + .correlationKeys(correlationKeys) + .build(); + + sendCamundaRequest(request, caseId); + } + + private void sendCamundaRequest(SendMessageRequest request, long caseId) { + String s2sToken = authTokenGenerator.generate(); + + try { + log.info("Camunda request for case id {}: {}", caseId, request); + camundaApi.sendMessage(s2sToken, request); + } catch (Exception e) { + log.error("Failed to send Camunda request for caseId {}", caseId, e); + } + } + + private DmnValue dmnStringValue(String value) { + return DmnValue.builder().value(value).type("String").build(); + } + + private DmnValue dmnIntegerValue(Integer value) { + return DmnValue.builder().value(value).type("Integer").build(); + } + + private DmnValue dmnBooleanValue(Boolean value) { + return DmnValue.builder().value(value).type("Boolean").build(); + } + +} diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/camunda/DmnValue.java b/src/main/java/uk/gov/hmcts/reform/pcs/camunda/DmnValue.java new file mode 100644 index 0000000000..eff870c43e --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/pcs/camunda/DmnValue.java @@ -0,0 +1,13 @@ +package uk.gov.hmcts.reform.pcs.camunda; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +@Builder +@Data +@AllArgsConstructor +public class DmnValue { + private T value; + private String type; +} diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/camunda/SendMessageRequest.java b/src/main/java/uk/gov/hmcts/reform/pcs/camunda/SendMessageRequest.java new file mode 100644 index 0000000000..47260fd6a8 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/pcs/camunda/SendMessageRequest.java @@ -0,0 +1,19 @@ +package uk.gov.hmcts.reform.pcs.camunda; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +import java.util.Map; + +@Builder +@Data +@AllArgsConstructor +public class SendMessageRequest { + + private String messageName; + + private Map> processVariables; + + private final Map> correlationKeys; +} diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/camunda/TaskType.java b/src/main/java/uk/gov/hmcts/reform/pcs/camunda/TaskType.java new file mode 100644 index 0000000000..d1556fc251 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/pcs/camunda/TaskType.java @@ -0,0 +1,14 @@ +package uk.gov.hmcts.reform.pcs.camunda; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum TaskType { + NEW_CLAIM_CREATE_NEW_HEARING("NewClaimCreateNewHearing", "New Claim – Create new hearing", 5); + + private final String id; + private final String name; + private final Integer workingDays; +} diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/accesscontrol/AccessProfile.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/accesscontrol/AccessProfile.java index f2fb204fb9..287f91d7d7 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/accesscontrol/AccessProfile.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/accesscontrol/AccessProfile.java @@ -33,7 +33,8 @@ public enum AccessProfile implements HasRole { WLU_TEAM_LEADER("wlu-team-leader", Permission.CRU), WLU_ADMIN("wlu-admin", Permission.CRU), GS_PROFILE("GS_profile", Set.of(R)), - SYSTEM_USER("pcs-system-update", CRU); + SYSTEM_USER("pcs-system-update", CRU), + WA_SYSTEM_USER("caseworker-wa-task-configuration", CRU); @JsonValue diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/accesscontrol/UserRole.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/accesscontrol/UserRole.java index d2220d1733..0e012b1cba 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/accesscontrol/UserRole.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/accesscontrol/UserRole.java @@ -45,7 +45,8 @@ public enum UserRole implements HasRole { LEADERSHIP_JUDGE("leadership-judge", Set.of(R), RAS), CIRCUIT_JUDGE("circuit-judge", Set.of(R), RAS), JUDGE("judge", Set.of(R), RAS), - SYSTEM_USER("pcs-system-update", Permission.CRU, IDAM); + SYSTEM_USER("pcs-system-update", Permission.CRU, IDAM), + WA_SYSTEM_USER("caseworker-wa-task-configuration", Permission.CRU, IDAM); @JsonValue diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/accesscontrol/WAAccess.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/accesscontrol/WAAccess.java new file mode 100644 index 0000000000..fc0caae731 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/accesscontrol/WAAccess.java @@ -0,0 +1,18 @@ +package uk.gov.hmcts.reform.pcs.ccd.accesscontrol; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.SetMultimap; +import uk.gov.hmcts.ccd.sdk.api.HasAccessControl; +import uk.gov.hmcts.ccd.sdk.api.HasRole; +import uk.gov.hmcts.ccd.sdk.api.Permission; + +import static uk.gov.hmcts.reform.pcs.ccd.accesscontrol.UserRole.WA_SYSTEM_USER; + +public class WAAccess implements HasAccessControl { + @Override + public SetMultimap getGrants() { + SetMultimap grants = HashMultimap.create(); + grants.put(WA_SYSTEM_USER, Permission.R); + return grants; + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/config/HighLevelDataSetupApp.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/config/HighLevelDataSetupApp.java index 146f7fda76..0daa3f3480 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/config/HighLevelDataSetupApp.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/config/HighLevelDataSetupApp.java @@ -36,7 +36,8 @@ public class HighLevelDataSetupApp extends DataLoaderToDefinitionStore { new CcdRoleConfig("fee-paid-judge", "PUBLIC"), new CcdRoleConfig("circuit-judge", "PUBLIC"), new CcdRoleConfig("leadership-judge", "PUBLIC"), - new CcdRoleConfig("pcs-system-update", "PUBLIC") + new CcdRoleConfig("pcs-system-update", "PUBLIC"), + new CcdRoleConfig("caseworker-wa-task-configuration", "PUBLIC") }; diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/PCSCase.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/PCSCase.java index 074bb4ec46..500b08f3cc 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/PCSCase.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/PCSCase.java @@ -31,6 +31,7 @@ import uk.gov.hmcts.reform.pcs.ccd.accesscontrol.InternalTabAccess; import uk.gov.hmcts.reform.pcs.ccd.accesscontrol.PartyVisibleTabAccess; import uk.gov.hmcts.reform.pcs.ccd.accesscontrol.RasValidationAccess; +import uk.gov.hmcts.reform.pcs.ccd.accesscontrol.WAAccess; import uk.gov.hmcts.reform.pcs.ccd.domain.dashboard.DashboardData; import uk.gov.hmcts.reform.pcs.ccd.domain.documentupload.DocumentUploadDetails; import uk.gov.hmcts.reform.pcs.ccd.domain.documentamend.DocumentAmendDetails; @@ -643,25 +644,25 @@ public class PCSCase { @CCD( label = "CaseNameHmctsInternal", - access = {GlobalSearchAccess.class} + access = {GlobalSearchAccess.class, WAAccess.class} ) private String caseNameHmctsInternal; @CCD( label = "CaseNamePublic", - access = {GlobalSearchAccess.class} + access = {GlobalSearchAccess.class, WAAccess.class} ) private String caseNamePublic; @CCD( label = "CaseManagementLocation", - access = {GlobalSearchAccess.class, RasValidationAccess.class} + access = {GlobalSearchAccess.class, RasValidationAccess.class, WAAccess.class} ) private CaseLocation caseManagementLocation; @CCD( label = "CaseManagementCategory", - access = {GlobalSearchAccess.class} + access = {GlobalSearchAccess.class, WAAccess.class} ) private DynamicList caseManagementCategory; diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/State.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/State.java index 0e2a2ed6f5..6949693d7b 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/State.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/State.java @@ -10,6 +10,7 @@ import uk.gov.hmcts.reform.pcs.ccd.accesscontrol.GlobalSearchAccess; import uk.gov.hmcts.reform.pcs.ccd.accesscontrol.InternalCaseFlagAccess; import uk.gov.hmcts.reform.pcs.ccd.accesscontrol.RasValidationAccess; +import uk.gov.hmcts.reform.pcs.ccd.accesscontrol.WAAccess; /** * All possible PCS case states. @@ -21,7 +22,7 @@ public enum State { @CCD( label = "Awaiting Submission to HMCTS", - access = {ClaimantAccess.class, CitizenAccess.class, RasValidationAccess.class}, + access = {ClaimantAccess.class, CitizenAccess.class, RasValidationAccess.class, WAAccess.class}, hint = "${caseTitleMarkdown}" ) AWAITING_SUBMISSION_TO_HMCTS, @@ -29,7 +30,7 @@ public enum State { @CCD( label = "Pending Case Issued", access = {ClaimantAccess.class, CitizenAccess.class, RasValidationAccess.class, - InternalCaseFlagAccess.class, GlobalSearchAccess.class}, + InternalCaseFlagAccess.class, GlobalSearchAccess.class, WAAccess.class}, hint = "${caseTitleMarkdown}" ) PENDING_CASE_ISSUED, @@ -37,7 +38,7 @@ public enum State { @CCD( label = "Case Issued", access = {CaseworkerReadAccess.class, ClaimantAccess.class, DefendantAccess.class, RasValidationAccess.class, - GlobalSearchAccess.class}, + GlobalSearchAccess.class, WAAccess.class}, hint = "${caseTitleMarkdown}" ) CASE_ISSUED, @@ -45,7 +46,7 @@ public enum State { @CCD( label = "Judicial Referral", access = {CaseworkerReadAccess.class, ClaimantAccess.class, RasValidationAccess.class, - GlobalSearchAccess.class}, + GlobalSearchAccess.class, WAAccess.class}, hint = "${caseTitleMarkdown}" ) JUDICIAL_REFERRAL, @@ -53,7 +54,7 @@ public enum State { @CCD( label = "Hearing Readiness", access = {CaseworkerReadAccess.class, ClaimantAccess.class, RasValidationAccess.class, - GlobalSearchAccess.class}, + GlobalSearchAccess.class, WAAccess.class}, hint = "${caseTitleMarkdown}" ) HEARING_READINESS, @@ -61,7 +62,7 @@ public enum State { @CCD( label = "Prepare For Hearing Conduct Hearing", access = {CaseworkerReadAccess.class, ClaimantAccess.class, RasValidationAccess.class, - GlobalSearchAccess.class}, + GlobalSearchAccess.class, WAAccess.class}, hint = "${caseTitleMarkdown}" ) PREPARE_FOR_HEARING_CONDUCT_HEARING, @@ -69,7 +70,7 @@ public enum State { @CCD( label = "Decision Outcome", access = {CaseworkerReadAccess.class, ClaimantAccess.class, RasValidationAccess.class, - GlobalSearchAccess.class}, + GlobalSearchAccess.class, WAAccess.class}, hint = "${caseTitleMarkdown}" ) DECISION_OUTCOME, @@ -77,7 +78,7 @@ public enum State { @CCD( label = "Case Progression", access = {CaseworkerReadAccess.class, ClaimantAccess.class, RasValidationAccess.class, - GlobalSearchAccess.class}, + GlobalSearchAccess.class, WAAccess.class}, hint = "${caseTitleMarkdown}" ) CASE_PROGRESSION, @@ -85,7 +86,7 @@ public enum State { @CCD( label = "All Final Orders", access = {CaseworkerReadAccess.class, ClaimantAccess.class, RasValidationAccess.class, - GlobalSearchAccess.class}, + GlobalSearchAccess.class, WAAccess.class}, hint = "${caseTitleMarkdown}" ) ALL_FINAL_ORDERS_ISSUED, @@ -93,7 +94,7 @@ public enum State { @CCD( label = "Case Stayed", access = {CaseworkerReadAccess.class, ClaimantAccess.class, RasValidationAccess.class, - GlobalSearchAccess.class}, + GlobalSearchAccess.class, WAAccess.class}, hint = "${caseTitleMarkdown}" ) CASE_STAYED, @@ -101,7 +102,7 @@ public enum State { @CCD( label = "Breathing Space", access = {CaseworkerReadAccess.class, ClaimantAccess.class, RasValidationAccess.class, - GlobalSearchAccess.class}, + GlobalSearchAccess.class, WAAccess.class}, hint = "${caseTitleMarkdown}" ) BREATHING_SPACE diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/changecasestate/ChangeCaseState.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/changecasestate/ChangeCaseState.java index 5cb3ec676d..0457b3ca58 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/changecasestate/ChangeCaseState.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/changecasestate/ChangeCaseState.java @@ -8,6 +8,8 @@ import uk.gov.hmcts.ccd.sdk.api.EventPayload; import uk.gov.hmcts.ccd.sdk.api.Permission; import uk.gov.hmcts.ccd.sdk.api.callback.SubmitResponse; +import uk.gov.hmcts.reform.pcs.camunda.CamundaService; +import uk.gov.hmcts.reform.pcs.camunda.TaskType; import uk.gov.hmcts.reform.pcs.ccd.ShowConditions; import uk.gov.hmcts.reform.pcs.ccd.accesscontrol.UserRole; import uk.gov.hmcts.reform.pcs.ccd.common.PageBuilder; @@ -28,6 +30,7 @@ public class ChangeCaseState implements CCDConfig { private final AddressFormatter addressFormatter; + private final CamundaService camundaService; @Override public void configureDecentralised(DecentralisedConfigBuilder configBuilder) { @@ -58,6 +61,11 @@ public void configureDecentralised(DecentralisedConfigBuilder submit(EventPayload eventPayload) { PCSCase pcsCase = eventPayload.caseData(); State targetState = pcsCase.getTargetState().toState(); + + if (targetState == State.CASE_STAYED || targetState == State.CASE_ISSUED) { + camundaService.cancelTask(eventPayload.caseReference(), TaskType.NEW_CLAIM_CREATE_NEW_HEARING); + } + return SubmitResponse.builder() .state(targetState) .confirmationBody(buildConfirmationMarkdown(pcsCase, eventPayload.caseReference())) diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/notify/service/FeePaymentNotificationService.java b/src/main/java/uk/gov/hmcts/reform/pcs/notify/service/FeePaymentNotificationService.java index 1a6e266139..d1500746fe 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/notify/service/FeePaymentNotificationService.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/notify/service/FeePaymentNotificationService.java @@ -4,6 +4,10 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import uk.gov.hmcts.reform.pcs.camunda.CamundaService; +import uk.gov.hmcts.reform.pcs.camunda.TaskType; +import uk.gov.hmcts.reform.pcs.ccd.entity.ClaimEntity; +import uk.gov.hmcts.reform.pcs.ccd.entity.PcsCaseEntity; import uk.gov.hmcts.reform.pcs.ccd.entity.feesandpay.FeePaymentEntity; import uk.gov.hmcts.reform.pcs.ccd.repository.feeandpay.FeePaymentRepository; import uk.gov.hmcts.reform.pcs.exception.FeePaymentNotFoundException; @@ -15,6 +19,7 @@ public class FeePaymentNotificationService { private final NotificationService notificationService; private final FeePaymentRepository feePaymentRepository; + private final CamundaService camundaService; @Transactional public void sendClaimantPaidCaseIssuedNotification(Integer feePaymentId) { @@ -23,6 +28,10 @@ public void sendClaimantPaidCaseIssuedNotification(Integer feePaymentId) { log.info("Sending claimant paid case issued notification for fee payment: {}", feePaymentId); - notificationService.sendClaimantClaimIssuedEmailNotification(feePayment.getClaim()); + ClaimEntity claimEntity = feePayment.getClaim(); + notificationService.sendClaimantClaimIssuedEmailNotification(claimEntity); + + PcsCaseEntity pcsCaseEntity = claimEntity.getPcsCase(); + camundaService.createTask(pcsCaseEntity.getCaseReference(), TaskType.NEW_CLAIM_CREATE_NEW_HEARING); } } diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/service/FeatureFlag.java b/src/main/java/uk/gov/hmcts/reform/pcs/service/FeatureFlag.java index 14651c685c..017fd1b1d3 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/service/FeatureFlag.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/service/FeatureFlag.java @@ -6,7 +6,8 @@ public enum FeatureFlag { BULK_PRINT("bulk-print-enabled", false), CASEWORKER_EVENTS("caseworker-events-enabled", false), CUI_RESPOND_TO_CLAIM_LR("cui-respond-to-claim-lr-enabled", false), - RELEASE_1_DOT_2("release-1.2-enabled", false); + RELEASE_1_DOT_2("release-1.2-enabled", false), + CASEWORKER_WA("caseworker-wa-enabled", false); private final String key; private final boolean defaultValue; diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index f7ea09ef9b..da596dc496 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -334,5 +334,11 @@ core_case_data: caseApi: url: ${CASE_API_URL:http://localhost:3206} +camunda: + url: ${CAMUNDA_BASE_URL:http://camunda-local-bpm/engine-rest} + request: + max-retries: ${CAMUNDA_REQUEST_TASK_MAX_RETRIES:3} + backoff-delay-duration: ${CAMUNDA_REQUEST_TASK_BACKOFF_DURATION:20m} + shutter: service: ${SHUTTER_SERVICE:false} diff --git a/src/main/resources/dmn/wa-task-cancellation-possession_service.dmn b/src/main/resources/dmn/wa-task-cancellation-possession_service.dmn new file mode 100644 index 0000000000..15db9a7238 --- /dev/null +++ b/src/main/resources/dmn/wa-task-cancellation-possession_service.dmn @@ -0,0 +1,103 @@ + + + + + + + fromState + + + + + event + + + + + state + + + + + + + + + + + + + "changeCaseState" + + + "CASE_ISSUED" + + + "Cancel" + + + "NewClaimCreateNewHearing" + + + + + + "NewClaimCreateNewHearing" + + + + + + + + "changeCaseState" + + + "CASE_STAYED" + + + "Cancel" + + + "NewClaimCreateNewHearing" + + + + + + "NewClaimCreateNewHearing" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/dmn/wa-task-completion-possession_service.dmn b/src/main/resources/dmn/wa-task-completion-possession_service.dmn new file mode 100644 index 0000000000..159e10a785 --- /dev/null +++ b/src/main/resources/dmn/wa-task-completion-possession_service.dmn @@ -0,0 +1,186 @@ + + + + + + + eventId + + + + + + + + + + "NewClaimCreateNewHearing" + + + "manual" + + + + + + + + "ReviewDefendantResponseAndCounterclaim" + + + "manual" + + + + + + + + "ReviewAdjournGenApp" + + + "manual" + + + + + + + + "ReviewSetAsideGenApp" + + + "manual" + + + + + + + + "ReviewGenApp" + + + "manual" + + + + + + + + "ReviewAdditionalDocumentsClaim" + + + "manual" + + + + + + + + "ReviewAdditionalDocumentsGenApp" + + + "manual" + + + + + + + + "ReviewAdditionalDocumentsCounterclaim" + + + "manual" + + + + + "Approve or reject case flag" + + + "ReviewCaseFlagRequest" + + + "Auto" + + + + + + + + "ReviewCaseFlag" + + + "manual" + + + + + + + + "ReviewFailedPayment" + + + "manual" + + + + + + + + "ReviewDateDue" + + + "manual" + + + + + + + + "TranslateDefendantSubmittedDocument" + + + "manual" + + + + + + + + "TranslateClaimantSubmittedDocument" + + + "manual" + + + + + + + + "TranslateCaseworkerUploadedDocument" + + + "manual" + + + + + + + + + + + + diff --git a/src/main/resources/dmn/wa-task-configuration-possession_service.dmn b/src/main/resources/dmn/wa-task-configuration-possession_service.dmn new file mode 100644 index 0000000000..a7d9a8eaf2 --- /dev/null +++ b/src/main/resources/dmn/wa-task-configuration-possession_service.dmn @@ -0,0 +1,716 @@ + + + + + + + + + + + + if(taskAttributes != null and taskAttributes.taskType != null) then + taskAttributes.taskType + else if(taskType != null) then + taskType + else + null + + + + + + + + + + + + + + "caseName" + + + caseData.caseNameHmctsInternal + + + true + + + + + + + + + + + "caseManagementCategory" + + + if (caseData.caseManagementCategory !=null and caseData.caseManagementCategory.selectedLabel !=null) then caseData.caseManagementCategory.selectedLabel else "Property Possession Claims" + + + true + + + + + + + + + + + "region" + + + if(caseData!=null and caseData.caseManagementLocation!=null and caseData.caseManagementLocation.region!=null) then caseData.caseManagementLocation.region else "1" + + + true + + + + Location Id + + + + + + + + "location" + + + if (caseData.caseManagementLocation != null and caseData.caseManagementLocation.baseLocation != null) then caseData.caseManagementLocation.baseLocation else "336559" + + + true + + + + + +Short name + + + + + + + + "locationName" + + + if (caseData.caseManagementLocation != null and caseData.caseManagementLocation.baseLocation != null and caseData.managingOffice != null) then caseData.managingOffice else "Unable to find location" + + + true + + + + + + + + "ReviewAdjournGenApp","ReviewSetAsideGenApp","TranslateDefendantSubmittedDocument","TranslateClaimantSubmittedDocument","TranslateCaseworkerUploadedDocument" + + + "majorPriority" + + + 1000 + + + true + + + + + + + + "ReviewGenApp" + + + "majorPriority" + + + 2000 + + + true + + + + + + + + "NewClaimCreateNewHearing" + + + "majorPriority" + + + 3000 + + + true + + + + + + + + "ReviewCaseFlagRequest","ReviewCaseFlag" + + + "majorPriority" + + + 4000 + + + true + + + + + + + + "ReviewDefendantResponseAndCounterclaim","ReviewAdditionalDocumentsClaim","ReviewAdditionalDocumentsGenApp","ReviewAdditionalDocumentsCounterclaim" + + + "majorPriority" + + + 5000 + + + true + + + + + + + + "ReviewFailedPayment" + + + "majorPriority" + + + 6000 + + + true + + + + + + + + "ReviewDateDue" + + + "majorPriority" + + + 7000 + + + true + + + + + + + + + + + "minorPriority" + + + 500 + + + true + + + + + + + + + + + "dueDateNonWorkingCalendar" + + + "https://www.gov.uk/bank-holidays/england-and-wales.json" + + + true + + + + + + + + + + + "dueDateNonWorkingDaysOfWeek" + + + "SATURDAY,SUNDAY" + + + false + + + + + + + + "ReviewDefendantResponseAndCounterclaim","ReviewAdditionalDocumentsClaim","ReviewAdditionalDocumentsGenApp","ReviewAdditionalDocumentsCounterclaim","ReviewDateDue","TranslateDefendantSubmittedDocument","TranslateClaimantSubmittedDocument","TranslateCaseworkerUploadedDocument" + + + "workType" + + + "routine_work" + + + true + + + + + + + + "NewClaimCreateNewHearing","ReviewCaseFlagRequest","ReviewCaseFlag" + + + "workType" + + + "decision_making_work" + + + true + + + + + + + + "ReviewAdjournGenApp","ReviewSetAsideGenApp","ReviewGenApp" + + + "workType" + + + "applications" + + + true + + + + + + + + "ReviewFailedPayment" + + + "workType" + + + "error_management" + + + true + + + + + + + + + + + "roleCategory" + + + "ADMIN" + + + true + + + + + + + + "NewClaimCreateNewHearing" + + + "description" + + + "List the case on List Assist, create and serve the Notice of Hearing on all parties, and upload a copy of the notice to the digital case file. Only mark the task as complete once all of these steps have been completed." + + + + + + + + + + + "ReviewDefendantResponseAndCounterclaim" + + + "description" + + + "Review the defendant’s response and counterclaim, check whether any further case action is required, and take the appropriate action before closing the task. Only mark the task as complete once the review is finished and any required action has been completed." + + + true + + + + + + + + "ReviewAdjournGenApp" + + + "description" + + + "Review the general application to adjourn the hearing, decide what action is needed, and take the appropriate action. Only mark the task as complete once the application has been reviewed and any required action has been completed." + + + true + + + + + + + + "ReviewSetAsideGenApp" + + + "description" + + + "Review the general application to set aside, decide what action is needed, and take the appropriate action. Only mark the task as complete once the application has been reviewed and any required action has been completed." + + + true + + + + + + + + "ReviewGenApp" + + + "description" + + + "Review the general application, decide what action is needed, and take the appropriate action. Only mark the task as complete once the application has been reviewed and any required action has been completed." + + + true + + + + + + + + "ReviewAdditionalDocumentsClaim" + + + "description" + + + Review the additional documents submitted on the claim, check whether any further case action is required, and take the appropriate action. Only mark the task as complete once the documents have been reviewed and any required action has been completed. + + + true + + + + + + + + "ReviewAdditionalDocumentsGenApp" + + + "description" + + + "Review the additional documents submitted for the general application, check whether any further case action is required, and take the appropriate action. Only mark the task as complete once the documents have been reviewed and any required action has been completed." + + + true + + + + + + + + "ReviewAdditionalDocumentsCounterclaim" + + + "description" + + + "Review the additional documents submitted for the counterclaim, check whether any further case action is required, and take the appropriate action. Only mark the task as complete once the documents have been reviewed and any required action has been completed." + + + true + + + + + + + + "ReviewCaseFlagRequest" + + + "description" + + + "Review the case flag request, decide whether it should be approved or rejected, and record the decision. The task will close when the approval or rejection event is completed." + + + true + + + + + + + + "ReviewCaseFlag" + + + "description" + + + "Review the case flags on the case flags tab, check whether any action is required, and take the appropriate action. Only mark the task as complete once the case flags have been reviewed and any required action has been completed." + + + true + + + + + + + + "ReviewFailedPayment" + + + "description" + + + "Review the failed payment on the service request screen, check whether the payment issue has been resolved or whether further action is required, and take the appropriate action. Only mark the task as complete once the payment issue has been reviewed and any required action has been completed." + + + true + + + + + + + + "ReviewDateDue" + + + "description" + + + "Review the case note about the date due, check whether any follow-up action is required, and take the appropriate action. Only mark the task as complete once the date due has been reviewed and any required action has been completed." + + + true + + + + + + + + "TranslateDefendantSubmittedDocument" + + + "description" + + + "Review the defendant submitted document and arrange translation where required. Upload or record the translated document on the case file and take any further action needed. Only mark the task as complete once the translation has been completed and any required action has been taken." + + + true + + + + + + + + "TranslateClaimantSubmittedDocument" + + + "description" + + + "Review the claimant submitted document and arrange translation. Email the translated document to the court. Only mark the task as complete once the translation has been completed." + + + true + + + + + + + + "TranslateCaseworkerUploadedDocument" + + + "description" + + + "Review the caseworker uploaded document and arrange translation. Email the translated document to the court. Only mark the task as complete once the translation has been completed." + + + true + + + + + + + + "ReviewAdjournGenApp", "ReviewSetAsideGenApp", "ReviewGenApp" + + + "dueDateIntervalDays" + + + 1 + + + + + + + + + + + "NewClaimCreateNewHearing", "Review DefendantResponseAndCounterclaim", "ReviewAdditionalDocumentsClaim", "ReviewAdditionalDocumentsGenApp", "ReviewAdditionalDocumentsCounterclaim", "ReviewCaseFlagRequest", "ReviewCaseFlag", "ReviewFailedPayment", "ReviewDateDue", "TranslateDefendantSubmittedDocument", "TranslateClaimantSubmittedDocument", "TranslateCaseworkerUploadedDocument" + + + "dueDateIntervalDays" + + + 5 + + + + + + + + + + + + + + "priorityDateOriginRef" + + + caseData.dueDate + + + true + + + + + + + + + + + "dueDateOrigin" + + + now() + + + false + + + + + + + + + + + "dueDateTime" + + + time("17:00") + + + false + + + + + + + + + + + + diff --git a/src/main/resources/dmn/wa-task-initiation-possession_service.dmn b/src/main/resources/dmn/wa-task-initiation-possession_service.dmn new file mode 100644 index 0000000000..cd8a1d50cf --- /dev/null +++ b/src/main/resources/dmn/wa-task-initiation-possession_service.dmn @@ -0,0 +1,945 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + if(additionalData != null and additionalData.Data != null and additionalData.Data.cicCaseAdminActionRequired + != null and additionalData.Data.cicCaseAdminActionRequired = ["AdminActionRequired"]) then + true +else + false + + + + + if(additionalData.Data.cicCaseFirstOrderDueDate != null and additionalData.Data.cicCaseFirstOrderDueDate != "") then + true +else + false + + + + + + + + + + + + "Notify claim - claimant is notified of case issued" + + + "CASE_ISSUED" + + + + + + + + + + + + + + + + + + "NewClaimCreateNewHearing" + + + "New Claim – Create new hearing" + + + + + + 5 + + + "NewClaimCreateNewHearing" + + + "decision_making_work" + + + "ADMIN" + + + + This rule would also be required for when this task is cancelled from a user putting the case in case stayed and then putting it back into case issued state. + + "Update case state" + + + "CASE_ISSUED" + + + + + + + + + + + + + + + + + + "NewClaimCreateNewHearing" + + + "New Claim – Create new hearing" + + + + + + 5 + + + "NewClaimCreateNewHearing" + + + "decision_making_work" + + + "ADMIN" + + + + Possible process for welsh translation when user selects they have selected completed claim in english and welsh or just welsh + + Tech team update case state + + + "PENDING_CASE_ISSUED to CASE_ISSUED" + + + + + + + + + + + + + + + + + + "NewClaimCreateNewHearing" + + + "New Claim – Create new hearing" + + + + + + 5 + + + "NewClaimCreateNewHearing" + + + "decision_making_work" + + + "ADMIN" + + + + + "Respond to claim" (Respone and counterclaim submitted) + + + + + + "PENDING_COUNTER_CLAIM_ISSUED" + + + + + + User selects (Help with fees) + + + + + + + + + "Review DefendantResponseAndCounterclaim" + + + "Review Defendant response and counterclaim" + + + + + + 5 + + + "Review DefendantResponseAndCounterclaim" + + + "routine_work" + + + "ADMIN" + + + + + "Upload Document - Citizen dashboard" (NEED NAME OF EVENT) + + + + + + + + + + + + User selects(No, the documents I'm uploading relate to the main claim) + + + + + + + + + "ReviewAdditionalDocumentsClaim" + + + "Review additional documents - claim" + + + + + + 5 + + + "ReviewAdditionalDocumentsClaim" + + + "routine_work" + + + "ADMIN" + + + + + "Upload Document (Defendant LR)", "Upload Document Claimant LR" + + + + + + + + + + + + User selects("Other" for Type of Doc) + + + + + + + + + "ReviewAdditionalDocumentsClaim" + + + "Review additional documents - claim" + + + + + + 5 + + + "ReviewAdditionalDocumentsClaim" + + + "routine_work" + + + "ADMIN" + + + + + "Upload Document - Citizen dashboard" (NEED NAME OF EVENT) + + + + + + + + + + + + User selects("Yes XXX", for Do these documents relate to an existing application?) + + + + + + + + + "ReviewAdditionalDocumentsGenApp" + + + "Review additional documents – gen app" + + + + + + 5 + + + "ReviewAdditionalDocumentsGenApp" + + + "routine_work" + + + "ADMIN" + + + + + "Upload Document (Defendant LR)", "Upload Document Claimant LR" + + + + + + + + + + + + User selects("Yes XXX", for Do these documents relate to an existing application?) + + + + + + + + + "ReviewAdditionalDocumentsGenApp" + + + "Review additional documents – gen app" + + + + + + 5 + + + "ReviewAdditionalDocumentsGenApp" + + + "routine_work" + + + "ADMIN" + + + + + "Upload Document - Citizen dashboard" (NEED NAME OF EVENT) + + + + + + + + + + + + User selects("Yes XXX", for Do these documents relate to an existing counterclaim?) + + + + + + + + + "ReviewAdditionalDocumentsCounterclaim" + + + "Review additional documents – counterclaim" + + + + + + 5 + + + "ReviewAdditionalDocumentsCounterclaim" + + + "routine_work" + + + "ADMIN" + + + + + "Upload Document (Defendant LR)", "Upload Document Claimant LR" + + + + + + + + + + + + User selects("Yes XXX", for Do these documents relate to an existing counterclaim?) + + + + + + + + + "ReviewAdditionalDocumentsCounterclaim" + + + "Review additional documents – counterclaim" + + + + + + 5 + + + "ReviewAdditionalDocumentsCounterclaim" + + + "routine_work" + + + "ADMIN" + + + + + "Submit General Application" + + + + + + + + + "GEN_APP_ISSUED" + + + User selects("Ask to adjourn the hearing" for Choose an application) + + + + + + + + + "ReviewAdjournGenApp" + + + "Review adjourn gen app" + + + + + + 1 + + + "ReviewAdjournGenApp" + + + "applications" + + + "ADMIN" + + + + + "Submit General Application" + + + + + + + + + "GEN_APP_ISSUED" + + + User selects("Ask the court to set aside" for Choose an application) + + + + + + + + + "ReviewSetAsideGenApp" + + + "Review set aside gen app" + + + + + + 1 + + + "ReviewSetAsideGenApp" + + + "applications" + + + "ADMIN" + + + + + "Submit General Application" + + + + + + + + + "Application Issued" + + + User selects("Something else" for Choose an application) + + + + + + + + + "ReviewGenApp" + + + "Review gen app" + + + + + + 1 + + + "ReviewGenApp" + + + "applications" + + + "ADMIN" + + + + + Defendant Submit response, External professional user adding flag via manage cases + + + + + + + + + + + + Flag default status is 'Requested' + + + + + + + + + "ReviewCaseFlagRequest" + + + "Review Case Flag request" + + + + + + 5 + + + "ReviewCaseFlagRequest" + + + "decision_making_work" + + + "ADMIN" + + + + + Defendant Submit response, External professional user adding flag via manage cases + + + + + + + + + + + + Flag default status is 'Active' + + + + + + + + + "ReviewCaseFlag" + + + "Review Case Flag" + + + + + + 5 + + + "ReviewCaseFlag" + + + "decision_making_work" + + + "ADMIN" + + + + + "Create Service Request (fee)" + + + + + + + + + + + + Charge back occurs (I.e. payment unsuccessful) + + + + + + + + + "ReviewFailedPayment" + + + "Review Failed Payment" + + + + + + 5 + + + "ReviewFailedPayment" + + + "error_management" + + + "ADMIN" + + + + + "Add review date" + + + + + + + + + + + + Date of review is satisfied + + + + + + + + + "ReviewDateDue" + + + "Review Date Due" + + + + + + 5 + + + "ReviewDateDue" + + + "routine_work" + + + "ADMIN" + + + + + Defendant submits Gen app, counterclaim, response or response and counterclaim + + + Submitted + + + + + + + + + User selects claim completed in welsh or english and welsh OR Claimant or defendant had indicated they want to receive communications and documents in Welsh + + + + + + + + + "TranslateDefendantSubmittedDocument" + + + "Translate Defendant Submitted Document" + + + + + + 5 + + + "TranslateDefendantSubmittedDocument" + + + "routine_work" + + + "ADMIN" + + + + + Submit Claim + + + "PENDING_CASE_ISSUED" + + + + + + + + + User selects claim completed in welsh or english and welsh OR Claimant or defendant had indicated they want to receive communications and documents in Welsh + + + + + + + + + "TranslateClaimantSubmittedDocument" + + + "Translate Claimant Submitted Document" + + + + + + 5 + + + "TranslateClaimantSubmittedDocument" + + + "routine_work" + + + "ADMIN" + + + + + Caseworker uploads document + + + + + + + + + + + + Claimant or defendant had indicated they want to receive communications and documents in Welsh + + + + + + + + + "TranslateCaseworkerUploadedDocument" + + + "Translate Caseworker Uploaded Document" + + + + + + 5 + + + "TranslateCaseworkerUploadedDocument" + + + "routine_work" + + + "ADMIN" + + + + + + + + + + + + diff --git a/src/main/resources/dmn/wa-task-permissions-possession_servcie.dmn b/src/main/resources/dmn/wa-task-permissions-possession_servcie.dmn new file mode 100644 index 0000000000..eb96e30faf --- /dev/null +++ b/src/main/resources/dmn/wa-task-permissions-possession_servcie.dmn @@ -0,0 +1,152 @@ + + + + + + + + if(taskAttributes != null and taskAttributes.taskType != null) + then taskAttributes.taskType + else "r1" + + + + + + + + + + + + + + + + + + + "NewClaimCreateNewHearing", "ReviewDefendantResponseAndCounterclaim", "ReviewAdjournGenApp", "ReviewSetAsideGenApp", "ReviewGenApp", "ReviewAdditionalDocumentsClaim", "ReviewAdditionalDocumentsGenApp", "ReviewAdditionalDocumentsCounterclaim", "ReviewCaseFlagRequest", "ReviewCaseFlag", "ReviewFailedPayment", "ReviewDateDue" + + + + + + + + + "hearing-centre-admin" + + + "Read,Own,Claim,Unclaim,Manage,Complete" + + + "ADMIN" + + + + + + + + + false + + + + + "NewClaimCreateNewHearing", "ReviewDefendantResponseAndCounterclaim", "ReviewAdjournGenApp", "ReviewSetAsideGenApp", "ReviewGenApp", "ReviewAdditionalDocumentsClaim", "ReviewAdditionalDocumentsGenApp", "ReviewAdditionalDocumentsCounterclaim", "ReviewCaseFlagRequest", "ReviewCaseFlag", "ReviewFailedPayment", "ReviewDateDue" + + + + + + + + + "hearing-centre-team-leader" + + + "Read,Own,Manage,Complete,Cancel,Assign,Unassign" + + + "ADMIN" + + + + + + + + + false + + + + + "TranslateDefendantSubmittedDocument", "TranslateClaimantSubmittedDocument", "TranslateCaseworkerUploadedDocument" + + + + + + + + + "wlu-admin" + + + "Read,Own,Claim,Unclaim,Manage,Complete" + + + "ADMIN" + + + + + + + + + false + + + + + "TranslateDefendantSubmittedDocument", "TranslateClaimantSubmittedDocument", "TranslateCaseworkerUploadedDocument" + + + + + + + + + "wlu-team-leader" + + + "Read,Own,Manage,Complete,Cancel,Assign,Unassign" + + + "ADMIN" + + + + + + + + + false + + + + + + + + + + + + diff --git a/src/main/resources/dmn/wa-task-types-possession_service.dmn b/src/main/resources/dmn/wa-task-types-possession_service.dmn new file mode 100644 index 0000000000..c199530473 --- /dev/null +++ b/src/main/resources/dmn/wa-task-types-possession_service.dmn @@ -0,0 +1,208 @@ + + + + + + + + + + + + + + + + + "NewClaimCreateNewHearing" + + + "New Claim – Create new hearing" + + + + + + + + "ReviewDefendantResponseAndCounterclaim" + + + "Review Defendant response and counterclaim" + + + + + + + + "ReviewAdjournGenApp" + + + "Review adjourn gen app" + + + + + + + + "ReviewSetAsideGenApp" + + + "Review set aside gen app" + + + + + + + + "ReviewGenApp" + + + "Review gen app" + + + + + + + + "ReviewAdditionalDocumentsClaim" + + + "Review additional documents - claim" + + + + + + + + "ReviewAdditionalDocumentsGenApp" + + + "Review additional documents – gen app" + + + + + + + + "ReviewAdditionalDocumentsCounterclaim" + + + "Review additional documents – counterclaim" + + + + + + + + "ReviewCaseFlagRequest" + + + "Review Case Flag request" + + + + + + + + "ReviewCaseFlag" + + + "Review Case Flag" + + + + + + + + "ReviewFailedPayment" + + + "Review Failed Payment" + + + + + + + + "ReviewDateDue" + + + "Review Date Due" + + + + + + + + "TranslateDefendantSubmittedDocument" + + + "Translate Defendant Submitted Document" + + + + + + + + "TranslateClaimantSubmittedDocument" + + + "Translate Claimant Submitted Document" + + + + + + + + "TranslateCaseworkerUploadedDocument" + + + "Translate Caseworker Uploaded Document" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/java/uk/gov/hmcts/reform/pcs/camunda/CamundaRequestTaskComponentTest.java b/src/test/java/uk/gov/hmcts/reform/pcs/camunda/CamundaRequestTaskComponentTest.java new file mode 100644 index 0000000000..6b12bcc931 --- /dev/null +++ b/src/test/java/uk/gov/hmcts/reform/pcs/camunda/CamundaRequestTaskComponentTest.java @@ -0,0 +1,85 @@ +package uk.gov.hmcts.reform.pcs.camunda; + +import com.github.kagkarlsson.scheduler.task.CompletionHandler; +import com.github.kagkarlsson.scheduler.task.Execution; +import com.github.kagkarlsson.scheduler.task.ExecutionContext; +import com.github.kagkarlsson.scheduler.task.TaskInstance; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.time.Duration; + +import static java.time.temporal.ChronoUnit.MINUTES; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowable; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class CamundaRequestTaskComponentTest { + + @Mock + private CamundaService camundaService; + @Mock + private TaskInstance taskInstance; + @Mock + private ExecutionContext executionContext; + + private CamundaRequestTaskComponent underTest; + + @BeforeEach + void setUp() { + underTest = new CamundaRequestTaskComponent(camundaService, 3, Duration.of(2, MINUTES)); + } + + @Test + void shouldCallCamundaService() { + // Given + CamundaRequestTaskData taskData = mock(CamundaRequestTaskData.class); + when(taskInstance.getData()).thenReturn(taskData); + + // When + underTest.camundaRequestTask().execute(taskInstance, executionContext); + + // Then + verify(camundaService).handleRequest(taskData); + } + + @Test + void shouldRemoveTaskAfterSuccessfulExecution() { + // When + CompletionHandler completionHandler + = underTest.camundaRequestTask().execute(taskInstance, executionContext); + + // Then + assertThat(completionHandler).isInstanceOf(CompletionHandler.OnCompleteRemove.class); + } + + @Test + void shouldPropagateExceptionForFailedExecution() { + // Given + CamundaRequestTaskData taskData = mock(CamundaRequestTaskData.class); + when(taskInstance.getData()).thenReturn(taskData); + + Execution execution = mock(Execution.class); + when(executionContext.getExecution()).thenReturn(execution); + + RuntimeException expectedException = mock(RuntimeException.class); + doThrow(expectedException).when(camundaService).handleRequest(taskData); + + // When + Throwable throwable = catchThrowable(() -> underTest.camundaRequestTask().execute( + taskInstance, + executionContext + )); + + // Then + assertThat(throwable).isEqualTo(expectedException); + } + +} diff --git a/src/test/java/uk/gov/hmcts/reform/pcs/camunda/CamundaServiceTest.java b/src/test/java/uk/gov/hmcts/reform/pcs/camunda/CamundaServiceTest.java new file mode 100644 index 0000000000..177db4b339 --- /dev/null +++ b/src/test/java/uk/gov/hmcts/reform/pcs/camunda/CamundaServiceTest.java @@ -0,0 +1,306 @@ +package uk.gov.hmcts.reform.pcs.camunda; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.read.ListAppender; +import com.github.kagkarlsson.scheduler.SchedulerClient; +import com.github.kagkarlsson.scheduler.task.SchedulableInstance; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.slf4j.LoggerFactory; +import uk.gov.hmcts.reform.authorisation.generators.AuthTokenGenerator; +import uk.gov.hmcts.reform.pcs.camunda.CamundaRequestTaskData.Action; +import uk.gov.hmcts.reform.pcs.service.FeatureFlag; +import uk.gov.hmcts.reform.pcs.service.FeatureToggleService; + +import java.time.Clock; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mock.Strictness.LENIENT; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class CamundaServiceTest { + + private static final long CASE_REFERENCE = 1234L; + + @Mock + private CamundaApi camundaApi; + + @Mock + private AuthTokenGenerator authTokenGenerator; + + @Mock + private SchedulerClient schedulerClient; + + @Mock(strictness = LENIENT) + private Clock utcClock; + + @Mock + private FeatureToggleService featureToggleService; + + @Captor + private ArgumentCaptor> schedulableInstanceCaptor; + + + private Logger componentLogger; + private ListAppender logAppender; + + @BeforeEach + void setUp() { + componentLogger = (Logger) LoggerFactory.getLogger(CamundaService.class); + logAppender = new ListAppender<>(); + logAppender.start(); + componentLogger.addAppender(logAppender); + + when(utcClock.instant()).thenReturn(TEST_UTC_DATE_TIME.toInstant(ZoneOffset.UTC)); + when(utcClock.getZone()).thenReturn(ZoneOffset.UTC); + } + + @AfterEach + void tearDown() { + componentLogger.detachAppender(logAppender); + } + + @InjectMocks + private CamundaService camundaService; + + private static final LocalDateTime TEST_UTC_DATE_TIME = LocalDate.of(2025, 8, 27) + .atTime(12, 51, 19); + + @Test + void shouldScheduleCamundaCreateRequestTask() { + // When + stubWaFeatureFlag(true); + camundaService.createTask(CASE_REFERENCE, TaskType.NEW_CLAIM_CREATE_NEW_HEARING); + + // Then + verify(schedulerClient).scheduleIfNotExists(schedulableInstanceCaptor.capture()); + + SchedulableInstance schedulableInstance = schedulableInstanceCaptor.getValue(); + + CamundaRequestTaskData taskData = schedulableInstance.getTaskInstance().getData(); + assertThat(taskData.getAction()).isEqualTo(Action.CREATE); + assertThat(taskData.getCaseReference()).isEqualTo(CASE_REFERENCE); + assertThat(taskData.getTaskType()).isEqualTo(TaskType.NEW_CLAIM_CREATE_NEW_HEARING); + } + + @Test + void shouldScheduleCamundaCancelRequestTask() { + // When + stubWaFeatureFlag(true); + camundaService.cancelTask(CASE_REFERENCE, TaskType.NEW_CLAIM_CREATE_NEW_HEARING); + + // Then + verify(schedulerClient).scheduleIfNotExists(schedulableInstanceCaptor.capture()); + + SchedulableInstance schedulableInstance = schedulableInstanceCaptor.getValue(); + + CamundaRequestTaskData taskData = schedulableInstance.getTaskInstance().getData(); + assertThat(taskData.getAction()).isEqualTo(Action.CANCEL); + assertThat(taskData.getCaseReference()).isEqualTo(CASE_REFERENCE); + assertThat(taskData.getTaskType()).isEqualTo(TaskType.NEW_CLAIM_CREATE_NEW_HEARING); + } + + @Test + void shouldNotScheduleCamundaCreateRequestTaskWhenWaNotEnabled() { + // When + stubWaFeatureFlag(false); + camundaService.createTask(CASE_REFERENCE, TaskType.NEW_CLAIM_CREATE_NEW_HEARING); + + // Then + verify(schedulerClient, never()).scheduleIfNotExists(any()); + } + + @Test + void shouldNotScheduleCamundaCancelRequestTaskWhenWaNotEnabled() { + // When + stubWaFeatureFlag(false); + camundaService.cancelTask(CASE_REFERENCE, TaskType.NEW_CLAIM_CREATE_NEW_HEARING); + + // Then + verify(schedulerClient, never()).scheduleIfNotExists(any()); + } + + @Test + void shouldSendCreateTaskToCamunda() { + // Given + final TaskType taskType = TaskType.NEW_CLAIM_CREATE_NEW_HEARING; + + when(authTokenGenerator.generate()).thenReturn("authToken"); + stubWaFeatureFlag(true); + + CamundaRequestTaskData taskData = buildTaskDataForCreate(taskType); + + // When + camundaService.handleRequest(taskData); + + // Then + ArgumentCaptor requestArgumentCaptor = ArgumentCaptor.forClass(SendMessageRequest.class); + verify(camundaApi).sendMessage(eq("authToken"), requestArgumentCaptor.capture()); + SendMessageRequest sendMessageRequest = requestArgumentCaptor.getValue(); + + assertThat(sendMessageRequest).isNotNull(); + assertThat(sendMessageRequest.getMessageName()).isEqualTo("createTaskMessage"); + + Map> processVariables = sendMessageRequest.getProcessVariables(); + assertThat(processVariables).isNotEmpty(); + assertThat(processVariables.get("taskState").getValue()).isEqualTo("unconfigured"); + assertThat(processVariables.get("taskState").getType()).isEqualTo("String"); + assertThat(processVariables.get("caseTypeId").getValue()).isEqualTo("PCS"); + assertThat(processVariables.get("caseTypeId").getType()).isEqualTo("String"); + assertThat(processVariables.get("dueDate").getValue()).isEqualTo("2025-09-01T12:51:19"); + assertThat(processVariables.get("dueDate").getType()).isEqualTo("String"); + assertThat(processVariables.get("workingDaysAllowed").getValue()).isEqualTo(5); + assertThat(processVariables.get("workingDaysAllowed").getType()).isEqualTo("Integer"); + assertThat(processVariables.get("jurisdiction").getValue()).isEqualTo("PCS"); + assertThat(processVariables.get("jurisdiction").getType()).isEqualTo("String"); + assertThat(processVariables.get("name").getValue()).isEqualTo("New Claim – Create new hearing"); + assertThat(processVariables.get("name").getType()).isEqualTo("String"); + assertThat(processVariables.get("taskId").getValue()).isEqualTo("NewClaimCreateNewHearing"); + assertThat(processVariables.get("taskId").getType()).isEqualTo("String"); + assertThat(processVariables.get("caseId").getValue()).isEqualTo(Long.toString(CASE_REFERENCE)); + assertThat(processVariables.get("caseId").getType()).isEqualTo("String"); + assertThat(processVariables.get("delayUntil").getValue()).isEqualTo("2025-08-27T12:51:19"); + assertThat(processVariables.get("delayUntil").getType()).isEqualTo("String"); + assertThat(processVariables.get("hasWarnings").getValue()).isEqualTo(false); + assertThat(processVariables.get("hasWarnings").getType()).isEqualTo("Boolean"); + assertThat(processVariables.get("warningList").getValue()).isEqualTo("[]"); + assertThat(processVariables.get("warningList").getType()).isEqualTo("String"); + } + + @Test + void shouldSkipCreatingTaskIfWaIsNotEnabled() { + // Given + final TaskType taskType = TaskType.NEW_CLAIM_CREATE_NEW_HEARING; + + stubWaFeatureFlag(false); + + CamundaRequestTaskData taskData = buildTaskDataForCreate(taskType); + + // When + camundaService.handleRequest(taskData); + + // Then + List infoMessages = logAppender.list.stream() + .filter(e -> e.getLevel() == Level.INFO) + .filter(e -> e.getFormattedMessage().contains("Skipped creating task for " + CASE_REFERENCE)) + .toList(); + assertThat(infoMessages).hasSize(1); + verify(camundaApi, never()).sendMessage(any(), any()); + } + + @Test + void shouldHandleFailedRequestToCamunda() { + // Given + final TaskType taskType = TaskType.NEW_CLAIM_CREATE_NEW_HEARING; + + when(authTokenGenerator.generate()).thenReturn("authToken"); + stubWaFeatureFlag(true); + doThrow(new RuntimeException()).when(camundaApi).sendMessage(any(), any()); + + CamundaRequestTaskData taskData = buildTaskDataForCreate(taskType); + + // When + camundaService.handleRequest(taskData); + + // Then + List terminalErrors = logAppender.list.stream() + .filter(e -> e.getLevel() == Level.ERROR) + .filter(e -> e.getFormattedMessage() + .contains("Failed to send Camunda request for caseId " + CASE_REFERENCE)) + .toList(); + assertThat(terminalErrors).hasSize(1); + } + + @Test + void shouldSendCancelTaskToCamunda() { + // Given + final TaskType taskType = TaskType.NEW_CLAIM_CREATE_NEW_HEARING; + + when(authTokenGenerator.generate()).thenReturn("authToken"); + stubWaFeatureFlag(true); + + CamundaRequestTaskData taskData = buildTaskDataForCancel(taskType); + + // When + camundaService.handleRequest(taskData); + + // Then + ArgumentCaptor requestArgumentCaptor = ArgumentCaptor.forClass(SendMessageRequest.class); + verify(camundaApi).sendMessage(eq("authToken"), requestArgumentCaptor.capture()); + SendMessageRequest sendMessageRequest = requestArgumentCaptor.getValue(); + + assertThat(sendMessageRequest).isNotNull(); + assertThat(sendMessageRequest.getMessageName()).isEqualTo("cancelTasks"); + + Map> processVariables = sendMessageRequest.getProcessVariables(); + assertThat(processVariables).isNotEmpty(); + assertThat(processVariables.get("cancellationProcess").getValue()).isEqualTo("CASE_EVENT_CANCELLATION"); + + Map> correlationKeys = sendMessageRequest.getCorrelationKeys(); + assertThat(correlationKeys).isNotEmpty(); + assertThat(correlationKeys.get("caseId").getValue()).isEqualTo(Long.toString(CASE_REFERENCE)); + assertThat(correlationKeys.get("__processCategory__NewClaimCreateNewHearing").getValue()).isEqualTo(true); + } + + @Test + void shouldSkipCancellingTaskIfWaIsNotEnabled() { + // Given + final TaskType taskType = TaskType.NEW_CLAIM_CREATE_NEW_HEARING; + + stubWaFeatureFlag(false); + + CamundaRequestTaskData taskData = buildTaskDataForCancel(taskType); + + // When + camundaService.handleRequest(taskData); + + // Then + List infoMessages = logAppender.list.stream() + .filter(e -> e.getLevel() == Level.INFO) + .filter(e -> e.getFormattedMessage().contains("Skipped cancelling task for " + CASE_REFERENCE)) + .toList(); + assertThat(infoMessages).hasSize(1); + verify(camundaApi, never()).sendMessage(any(), any()); + } + + private static CamundaRequestTaskData buildTaskDataForCreate(TaskType taskType) { + return CamundaRequestTaskData.builder() + .action(Action.CREATE) + .caseReference(CASE_REFERENCE) + .taskType(taskType) + .build(); + } + + private static CamundaRequestTaskData buildTaskDataForCancel(TaskType taskType) { + return CamundaRequestTaskData.builder() + .action(Action.CANCEL) + .caseReference(CASE_REFERENCE) + .taskType(taskType) + .build(); + } + + private void stubWaFeatureFlag(boolean enabled) { + when(featureToggleService.isEnabled(FeatureFlag.CASEWORKER_WA)).thenReturn(enabled); + } + +} diff --git a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/event/changecasestate/ChangeCaseStateTest.java b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/event/changecasestate/ChangeCaseStateTest.java index 26d469c42e..c276f4a80b 100644 --- a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/event/changecasestate/ChangeCaseStateTest.java +++ b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/event/changecasestate/ChangeCaseStateTest.java @@ -9,6 +9,8 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import uk.gov.hmcts.ccd.sdk.api.callback.SubmitResponse; +import uk.gov.hmcts.reform.pcs.camunda.CamundaService; +import uk.gov.hmcts.reform.pcs.camunda.TaskType; import uk.gov.hmcts.reform.pcs.ccd.domain.CaseStateOption; import uk.gov.hmcts.reform.pcs.ccd.domain.PCSCase; import uk.gov.hmcts.reform.pcs.ccd.domain.State; @@ -18,6 +20,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static uk.gov.hmcts.reform.pcs.ccd.util.AddressFormatter.COMMA_DELIMITER; @@ -29,6 +32,9 @@ class ChangeCaseStateTest extends BaseEventTest { @Mock private AddressFormatter addressFormatter; + @Mock + private CamundaService camundaService; + @InjectMocks private ChangeCaseState changeCaseState; @@ -41,14 +47,19 @@ void setUp() { @ParameterizedTest @EnumSource(CaseStateOption.class) - void shouldTransitionToSelectedTargetState(CaseStateOption targetState) { + void shouldTransitionToSelectedTargetState(CaseStateOption targetStateOption) { PCSCase pcsCase = PCSCase.builder() - .targetState(targetState) + .targetState(targetStateOption) .build(); SubmitResponse response = callSubmitHandler(pcsCase); - assertThat(response.getState()).isEqualTo(targetState.toState()); + State targetState = targetStateOption.toState(); + assertThat(response.getState()).isEqualTo(targetState); + + if (targetState == State.CASE_ISSUED || targetState == State.CASE_STAYED) { + verify(camundaService).cancelTask(1234L, TaskType.NEW_CLAIM_CREATE_NEW_HEARING); + } } @Test @@ -73,4 +84,4 @@ void shouldIncludeFormattedAddressInConfirmationBody() { assertThat(response.getConfirmationBody()).contains(FORMATTED_ADDRESS); } -} \ No newline at end of file +} diff --git a/src/test/java/uk/gov/hmcts/reform/pcs/notify/service/FeePaymentNotificationServiceTest.java b/src/test/java/uk/gov/hmcts/reform/pcs/notify/service/FeePaymentNotificationServiceTest.java index b213e204d2..448a971ff6 100644 --- a/src/test/java/uk/gov/hmcts/reform/pcs/notify/service/FeePaymentNotificationServiceTest.java +++ b/src/test/java/uk/gov/hmcts/reform/pcs/notify/service/FeePaymentNotificationServiceTest.java @@ -5,7 +5,10 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import uk.gov.hmcts.reform.pcs.camunda.CamundaService; +import uk.gov.hmcts.reform.pcs.camunda.TaskType; import uk.gov.hmcts.reform.pcs.ccd.entity.ClaimEntity; +import uk.gov.hmcts.reform.pcs.ccd.entity.PcsCaseEntity; import uk.gov.hmcts.reform.pcs.ccd.entity.feesandpay.FeePaymentEntity; import uk.gov.hmcts.reform.pcs.ccd.repository.feeandpay.FeePaymentRepository; import uk.gov.hmcts.reform.pcs.exception.FeePaymentNotFoundException; @@ -23,6 +26,8 @@ class FeePaymentNotificationServiceTest { private NotificationService notificationService; @Mock private FeePaymentRepository feePaymentRepository; + @Mock + private CamundaService camundaService; @InjectMocks private FeePaymentNotificationService underTest; @@ -30,7 +35,8 @@ class FeePaymentNotificationServiceTest { @Test void shouldSendClaimantClaimIssuedEmailNotification() { Integer feePaymentId = 1; - ClaimEntity claim = new ClaimEntity(); + PcsCaseEntity pcsCaseEntity = PcsCaseEntity.builder().caseReference(1234L).build(); + ClaimEntity claim = ClaimEntity.builder().pcsCase(pcsCaseEntity).build(); FeePaymentEntity feePayment = FeePaymentEntity.builder() .id(feePaymentId) .claim(claim) @@ -40,6 +46,7 @@ void shouldSendClaimantClaimIssuedEmailNotification() { underTest.sendClaimantPaidCaseIssuedNotification(feePaymentId); verify(notificationService).sendClaimantClaimIssuedEmailNotification(claim); + verify(camundaService).createTask(1234L, TaskType.NEW_CLAIM_CREATE_NEW_HEARING); } @Test