Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions frontend/cypress/e2e/pages/ClientDetailsPage.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1917,6 +1917,43 @@ describe("Client Details Page", () => {
cy.get("#contact-null-SaveBtn").shadow().find("button").should("be.enabled");
});
});

describe("delete a contact that is in use by another system", () => {
Comment thread
mamartinezmejia marked this conversation as resolved.
Comment thread
mamartinezmejia marked this conversation as resolved.
it("shows the backend error message in the toast", function () {
init.call(this);

cy.intercept("PATCH", "/api/clients/details/*", {
statusCode: 409,
body:
"You can't delete this contact yet because it's being used by EMS, GAS2, LEXIS, " +
"or SCS. Remove it from the other system first, then try again.",
delay: 250,
}).as("saveClientDetails");

cy.visit("/clients/details/p");

// Switch to the Contacts tab
cy.get("#tab-contacts").click();

// Clicks to expand the accordion
cy.get("#contact-10 [slot='title']").click();

cy.get("#contact-10-EditBtn").click();

// Delete contact
cy.get("#contact-10-DeleteBtn").click();
cy.get("#modal-delete .cds--modal-submit-btn").filter(":visible").click();

cy.wait("@saveClientDetails");

cy.get("cds-toast-notification[kind='error']").should("be.visible");

cy.get("cds-toast-notification[kind='error']").contains(
"You can't delete this contact yet because it's being used by EMS, GAS2, LEXIS, " +
"or SCS.",
);
});
});
});
});

Expand Down
5 changes: 4 additions & 1 deletion frontend/src/pages/ClientDetailsPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -779,7 +779,10 @@ const operateContact =
kind: "Error",
active: true,
handler: () => {},
message: `Failed to ${action.infinitive} contact`,
message:
error.response?.status === 409 && typeof error.response.data === "string"
? error.response.data
: `Failed to ${action.infinitive} contact`,
toastTitle: undefined,
};
toastBus.emit(toastNotification);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
Expand Down Expand Up @@ -45,4 +46,18 @@ public Flux<ForestClientContactDto> findIndividuals(
return service.search(firstName, lastName, email, phone);
}

/**
* Checks whether a contact is being used (referenced) by another system, such as EMS, GAS2,
* LEXIS, or SCS.
*
* @param contactId the id of the contact to check
* @return a {@link Mono} emitting {@code true} if the contact is in use, {@code false}
* otherwise
*/
@GetMapping("/{contactId}/in-use")
Comment thread
mamartinezmejia marked this conversation as resolved.
public Mono<Boolean> isContactInUse(@PathVariable Long contactId) {
log.info("Receiving request to check if contact {} is in use by another system", contactId);
return service.isContactInUse(contactId);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package ca.bc.gov.app.entity;

import static ca.bc.gov.app.ApplicationConstants.ORACLE_ATTRIBUTE_SCHEMA;

import java.time.LocalDate;
import java.time.LocalDateTime;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.With;
import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.Table;

@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
@With
@Table(name = "SCALE_SITE_CONTACT", schema = ORACLE_ATTRIBUTE_SCHEMA)
public class ScaleSiteContactEntity {

@Id
@Column("CLIENT_CONTACT_ID")
private Long clientContactId;

@Column("SCALE_SITE_ID_NMBR")
@NotNull
@Size(min = 1, max = 4)
private String scaleSiteIdNumber;

@Column("CONTACT_ROLE_DESCRIPTION")
@NotNull
@Size(min = 1, max = 40)
private String contactRoleDescription;

@Column("PRIMARY_CONTACT_IND")
@NotNull
@Size(min = 1, max = 1)
private String primaryContactInd;

@Column("SITE_INFORMATION_ACCESS_IND")
@NotNull
@Size(min = 1, max = 1)
private String siteInformationAccessInd;

@Column("EFFECTIVE_DATE")
@NotNull
private LocalDate effectiveDate;

@Column("EXPIRY_DATE")
private LocalDate expiryDate;

@Column("ENTRY_TIMESTAMP")
@NotNull
private LocalDateTime createdAt;

@Column("ENTRY_USERID")
@NotNull
@Size(min = 1, max = 30)
private String createdBy;

@Column("UPDATE_TIMESTAMP")
@NotNull
private LocalDateTime updatedAt;

@Column("UPDATE_USERID")
@NotNull
@Size(min = 1, max = 30)
private String updatedBy;

@Column("REVISION_COUNT")
@NotNull
private Long revision;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package ca.bc.gov.app.exception;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.server.ResponseStatusException;

/**
* Exception thrown when an attempt is made to delete a client contact that is still
* referenced by another system (e.g. EMS, GAS2, LEXIS, or SCS).
*/
@ResponseStatus(HttpStatus.CONFLICT)
public class ContactInUseException extends ResponseStatusException {

public ContactInUseException() {
super(
HttpStatus.CONFLICT,
"You can't delete this contact yet because it's being used by EMS, GAS2, LEXIS, or SCS. "
+ "Remove it from the other system first, then try again."
);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package ca.bc.gov.app.repository;

import ca.bc.gov.app.entity.ScaleSiteContactEntity;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
import org.springframework.stereotype.Repository;
import reactor.core.publisher.Mono;

/**
* Repository for the {@link ScaleSiteContactEntity}.
*
* <p>Provides methods to query the SCALE_SITE_CONTACT table, which is used by other
* systems (EMS, GAS2, LEXIS, SCS) to reference client contacts.</p>
*/
@Repository
public interface ScaleSiteContactRepository
extends ReactiveCrudRepository<ScaleSiteContactEntity, Long> {

/**
* Checks whether a record exists in SCALE_SITE_CONTACT for the given client contact id.
*
* @param clientContactId the client contact id to check
* @return a {@link Mono} emitting {@code true} if a matching record exists, {@code false}
* otherwise
*/
Mono<Boolean> existsByClientContactId(Long clientContactId);

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import ca.bc.gov.app.entity.ForestClientContactEntity;
import ca.bc.gov.app.mappers.ForestClientContactMapper;
import ca.bc.gov.app.repository.ForestClientContactRepository;
import ca.bc.gov.app.repository.ScaleSiteContactRepository;
import io.micrometer.observation.annotation.Observed;
import java.util.Locale;
import lombok.RequiredArgsConstructor;
Expand All @@ -25,6 +26,7 @@ public class ClientContactService {
private final R2dbcEntityOperations entityTemplate;
private final ForestClientContactRepository repository;
private final ForestClientContactMapper mapper;
private final ScaleSiteContactRepository scaleSiteContactRepository;

public Mono<String> saveAndGetIndex(ForestClientContactDto dto) {
log.info("Saving forest client contact {} {}", dto.clientNumber(), dto.contactName());
Expand Down Expand Up @@ -106,6 +108,25 @@ public Flux<ForestClientContactDto> search(
.map(mapper::toDto);
}

/**
* Checks whether a contact is currently being used (referenced) by another system, such as
* EMS, GAS2, LEXIS, or SCS. This is determined by the presence of a matching record in the
* {@code THE.SCALE_SITE_CONTACT} table.
*
* @param contactId the client contact id to check
* @return a {@link Mono} emitting {@code true} if the contact is in use by another system,
* {@code false} otherwise
*/
public Mono<Boolean> isContactInUse(Long contactId) {
log.info("Checking if contact {} is in use by another system", contactId);
return
scaleSiteContactRepository
.existsByClientContactId(contactId)
.doOnNext(inUse ->
log.info("Contact {} in use by another system? {}", contactId, inUse)
);
}

/**
* Locates a client contact based on the provided client number, location code, and contact name.
* This method performs a query to find any existing client contact entities that match the given
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package ca.bc.gov.app.service.patch;

import ca.bc.gov.app.exception.ContactInUseException;
import ca.bc.gov.app.repository.ScaleSiteContactRepository;
import ca.bc.gov.app.util.PatchUtils;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
Expand Down Expand Up @@ -30,6 +32,7 @@ public class PatchOperationContactRemoveService implements ClientPatchOperation
)""";

private final R2dbcEntityOperations entityTemplate;
private final ScaleSiteContactRepository scaleSiteContactRepository;

@Override
public String getPrefix() {
Expand Down Expand Up @@ -67,11 +70,35 @@ public Mono<Void> applyPatch(
.filter(node -> !node.get("path").asText().contains("locationCodes"))
.map(node -> node.get("path").asText().replace("/", StringUtils.EMPTY))
.map(Long::parseLong)
.flatMap(entityId -> removeAllByEntityId(clientNumber, entityId))
.flatMap(entityId ->
Comment thread
mamartinezmejia marked this conversation as resolved.
Outdated
verifyNotInUse(entityId)
.then(removeAllByEntityId(clientNumber, entityId))
)
Comment thread
mamartinezmejia marked this conversation as resolved.
Outdated
.then();

}

/**
* Verifies that the contact identified by {@code entityId} is not currently being used by
* another system (e.g. EMS, GAS2, LEXIS, or SCS) before allowing it to be deleted.
*
* @param entityId the client contact id to verify
* @return a {@link Mono} that completes successfully if the contact can be deleted, or errors
* with {@link ContactInUseException} if the contact is still in use
*/
private Mono<Void> verifyNotInUse(Long entityId) {
return
scaleSiteContactRepository
.existsByClientContactId(entityId)
.doOnNext(inUse ->
log.info("Contact {} in use by another system? {}", entityId, inUse)
)
.flatMap(inUse -> inUse
? Mono.error(new ContactInUseException())
: Mono.empty()
);
}

private Mono<Long> removeAllByEntityId(String clientNumber, Long entityId) {
return
entityTemplate
Expand Down
Loading
Loading