Skip to content

Commit

Permalink
[MODORDERS-1209-2]. Throw http exception for all handled exception cases
Browse files Browse the repository at this point in the history
  • Loading branch information
BKadirkhodjaev committed Dec 19, 2024
1 parent e06264f commit 812dd45
Show file tree
Hide file tree
Showing 5 changed files with 200 additions and 147 deletions.
18 changes: 0 additions & 18 deletions src/main/java/org/folio/models/claiming/ClaimingError.java

This file was deleted.

9 changes: 8 additions & 1 deletion src/main/java/org/folio/rest/core/exceptions/ErrorCodes.java
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,14 @@ public enum ErrorCodes {
RECEIVING_WORKFLOW_INCORRECT_FOR_BINDARY_ACTIVE("receivingWorkflowIncorrectForBindaryActive", "When PoLine is bindery active, its receiving workflow must be set to 'Independent order and receipt quantity'"),
BIND_ITEM_MUST_INCLUDE_EITHER_HOLDING_ID_OR_LOCATION_ID("bindItemMustIncludeEitherHoldingIdOrLocationId", "During binding pieces, the bindItem object must have either holdingId or locationId field populated"),
BUDGET_NOT_FOUND_FOR_FISCAL_YEAR("budgetNotFoundForFiscalYear", "Could not find an active budget for a fund with the current fiscal year of another fund in the fund distribution"),
LAST_PIECE("lastPiece", "The piece cannot be deleted because it is the last piece for the poLine with Receiving Workflow 'Synchronized order and receipt quantity' and cost quantity '1'"),;
LAST_PIECE("lastPiece", "The piece cannot be deleted because it is the last piece for the poLine with Receiving Workflow 'Synchronized order and receipt quantity' and cost quantity '1'"),
CANNOT_SEND_CLAIMS_PIECE_IDS_ARE_EMPTY("cannotSendClaimsPieceIdsAreEmpty", "Cannot send claims, piece ids are empty"),
CANNOT_FIND_PIECES_WITH_LATE_STATUS_TO_PROCESS("cannotFindPiecesWithLatestStatusToProcess", "Cannot find pieces with LATE status to process"),
CANNOT_RETRIEVE_CONFIG_ENTRIES("cannotRetrieveConfigEntries", "Cannot retrieve config entries"),
CANNOT_GROUP_PIECES_BY_VENDOR("cannotGroupPiecesByVendorMessage", "Cannot group pieces by vendor"),
CANNOT_CREATE_JOBS_AND_UPDATE_PIECES("cannotCreateJobsAndUpdatePieces", "Cannot create jobs and update pieces"),
CANNOT_FIND_PIECE_BY_ID("cannotFindPieceById", "Cannot find a piece by '%s' id"),
UNABLE_TO_GENERATE_CLAIMS_FOR_ORG_NO_INTEGRATION_DETAILS("unableToGenerateClaimsForOrgNoIntegrationDetails", "Unable to generate claims for %s because no claim integrations exist");

private final String code;
private final String description;
Expand Down
95 changes: 55 additions & 40 deletions src/main/java/org/folio/service/pieces/PiecesClaimingService.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@
import org.folio.HttpStatus;
import org.folio.rest.acq.model.Organization;
import org.folio.rest.core.RestClient;
import org.folio.rest.core.exceptions.ErrorCodes;
import org.folio.rest.core.exceptions.HttpException;
import org.folio.rest.core.models.RequestContext;
import org.folio.rest.jaxrs.model.ClaimingCollection;
import org.folio.rest.jaxrs.model.ClaimingPieceResult;
import org.folio.rest.jaxrs.model.ClaimingResults;
import org.folio.rest.jaxrs.model.Error;
import org.folio.rest.jaxrs.model.Errors;
import org.folio.rest.jaxrs.model.Parameter;
import org.folio.rest.jaxrs.model.Piece;
import org.folio.rest.jaxrs.model.PieceBatchStatusCollection;
import org.folio.service.caches.ConfigurationEntriesCache;
Expand All @@ -32,16 +34,10 @@
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.stream.Collectors;

import static java.util.stream.Collectors.mapping;
import static java.util.stream.Collectors.toList;
import static org.folio.models.claiming.ClaimingError.CANNOT_CREATE_JOBS_AND_UPDATE_PIECES;
import static org.folio.models.claiming.ClaimingError.CANNOT_FIND_A_PIECE_BY_ID;
import static org.folio.models.claiming.ClaimingError.CANNOT_FIND_PIECES_WITH_LATE_STATUS_TO_PROCESS;
import static org.folio.models.claiming.ClaimingError.CANNOT_GROUP_PIECES_BY_VENDOR_MESSAGE;
import static org.folio.models.claiming.ClaimingError.CANNOT_RETRIEVE_CONFIG_ENTRIES;
import static org.folio.models.claiming.ClaimingError.CANNOT_SEND_CLAIMS_PIECE_IDS_ARE_EMPTY;
import static org.folio.models.claiming.ClaimingError.UNABLE_TO_GENERATE_CLAIMS_FOR_ORG_NO_INTEGRATION_DETAILS;
import static org.folio.models.claiming.IntegrationDetailField.CLAIM_PIECE_IDS;
import static org.folio.models.claiming.IntegrationDetailField.EXPORT_TYPE_SPECIFIC_PARAMETERS;
import static org.folio.models.claiming.IntegrationDetailField.VENDOR_EDI_ORDERS_EXPORT_CONFIG;
Expand All @@ -50,7 +46,13 @@
import static org.folio.orders.utils.ResourcePathResolver.DATA_EXPORT_SPRING_CREATE_JOB;
import static org.folio.orders.utils.ResourcePathResolver.DATA_EXPORT_SPRING_EXECUTE_JOB;
import static org.folio.orders.utils.ResourcePathResolver.resourcesPath;
import static org.folio.rest.jaxrs.model.ClaimingPieceResult.Status.FAILURE;
import static org.folio.rest.core.exceptions.ErrorCodes.CANNOT_CREATE_JOBS_AND_UPDATE_PIECES;
import static org.folio.rest.core.exceptions.ErrorCodes.CANNOT_FIND_PIECES_WITH_LATE_STATUS_TO_PROCESS;
import static org.folio.rest.core.exceptions.ErrorCodes.CANNOT_FIND_PIECE_BY_ID;
import static org.folio.rest.core.exceptions.ErrorCodes.CANNOT_GROUP_PIECES_BY_VENDOR;
import static org.folio.rest.core.exceptions.ErrorCodes.CANNOT_RETRIEVE_CONFIG_ENTRIES;
import static org.folio.rest.core.exceptions.ErrorCodes.CANNOT_SEND_CLAIMS_PIECE_IDS_ARE_EMPTY;
import static org.folio.rest.core.exceptions.ErrorCodes.UNABLE_TO_GENERATE_CLAIMS_FOR_ORG_NO_INTEGRATION_DETAILS;
import static org.folio.rest.jaxrs.model.ClaimingPieceResult.Status.SUCCESS;

@Log4j2
Expand All @@ -60,6 +62,8 @@ public class PiecesClaimingService {

private static final String JOB_STATUS = "status";
private static final String EXPORT_TYPE_CLAIMS = "CLAIMS";
private static final String VENDOR_CODE_PARAMETER = "vendorCode";
private static final String PIECE_ID_PARAMETER = "pieceId";

private final ConfigurationEntriesCache configurationEntriesCache;
private final PieceStorageService pieceStorageService;
Expand All @@ -79,28 +83,29 @@ public class PiecesClaimingService {
*/
public Future<ClaimingResults> sendClaims(ClaimingCollection claimingCollection, RequestContext requestContext) {
if (CollectionUtils.isEmpty(claimingCollection.getClaimingPieceIds())) {
log.info("sendClaims:: No claims are sent, claiming piece ids are empty");
return Future.succeededFuture(createEmptyClaimingResults(CANNOT_SEND_CLAIMS_PIECE_IDS_ARE_EMPTY.getValue()));
log.info("sendClaims:: Cannot send claims piece ids are empty - No claims are sent");
throwHttpException(CANNOT_SEND_CLAIMS_PIECE_IDS_ARE_EMPTY, claimingCollection, HttpStatus.HTTP_BAD_REQUEST);
}
return configurationEntriesCache.loadConfiguration(DATA_EXPORT_SPRING_CONFIG_MODULE_NAME, requestContext)
.compose(config -> {
if (CollectionUtils.isEmpty(config.getMap())) {
log.info("sendClaims:: No claims are sent, config has no entries");
return Future.succeededFuture(createEmptyClaimingResults(CANNOT_RETRIEVE_CONFIG_ENTRIES.getValue()));
log.info("sendClaims:: Cannot retrieve config entries - No claims are sent");
throwHttpException(CANNOT_RETRIEVE_CONFIG_ENTRIES, claimingCollection, HttpStatus.HTTP_BAD_REQUEST);
}
var pieceIds = claimingCollection.getClaimingPieceIds().stream().toList();
log.info("sendClaims:: Received pieces to be claimed, pieceIds: {}", pieceIds);
return groupPieceIdsByVendor(pieceIds, requestContext)
.compose(pieceIdsByVendors -> {
if (CollectionUtils.isEmpty(pieceIdsByVendors)) {
return Future.succeededFuture(createEmptyClaimingResults(CANNOT_FIND_PIECES_WITH_LATE_STATUS_TO_PROCESS.getValue()));
log.info("sendClaims:: Cannot find pieces with late status to process - No claims are sent");
throwHttpException(CANNOT_FIND_PIECES_WITH_LATE_STATUS_TO_PROCESS, claimingCollection, HttpStatus.HTTP_BAD_REQUEST);
}
pieceIdsByVendors.forEach((key, value) ->
log.info("createVendorPiecePair:: Using pieces by vendor map, vendorId: {}, piecesByVendor: {}", key.getId(), value));
pieceIdsByVendors.forEach((vendor, piecesByVendor) ->
log.info("createVendorPiecePair:: Using pieces by vendor map, vendorId: {}, piecesByVendor: {}", vendor.getId(), piecesByVendor));
var vendorWithoutIntegrationDetails = checkVendorIntegrationDetails(config, pieceIdsByVendors);
if (Objects.nonNull(vendorWithoutIntegrationDetails)) {
var errors = List.of(new Error().withMessage(String.format(UNABLE_TO_GENERATE_CLAIMS_FOR_ORG_NO_INTEGRATION_DETAILS.getValue(), vendorWithoutIntegrationDetails.getCode())));
throw new HttpException(HttpStatus.HTTP_UNPROCESSABLE_ENTITY.toInt(), new Errors().withErrors(errors).withTotalRecords(errors.size()));
log.info("sendClaims:: Unable to generate claims because no claim integrations exist - No claims are sent");
throwHttpExceptionOnMissingVendorIntegrationDetails(claimingCollection, vendorWithoutIntegrationDetails);
}
return createJobsByVendor(claimingCollection, config, pieceIdsByVendors, requestContext);
});
Expand Down Expand Up @@ -132,7 +137,7 @@ private List<Future<Pair<Organization, String>>> createPieceIdByVendorFutures(Li
uniquePiecePoLinePairs.forEach(piecePoLinePairs -> {
var foundPiece = pieces.stream()
.filter(Objects::nonNull).filter(piece -> Objects.nonNull(piece.getId())).filter(piece -> piece.getId().equals(piecePoLinePairs.getRight()))
.findFirst().orElseThrow(() -> new NoSuchElementException(String.format(CANNOT_FIND_A_PIECE_BY_ID.getValue(), piecePoLinePairs.getRight())));
.findFirst().orElseThrow(() -> new NoSuchElementException(String.format(CANNOT_FIND_PIECE_BY_ID.getDescription(), piecePoLinePairs.getRight())));
var pieceIdByVendorFuture = createVendorPiecePair(piecePoLinePairs, foundPiece, requestContext);
if (Objects.nonNull(pieceIdByVendorFuture)) {
pieceIdByVendorFutures.add(pieceIdByVendorFuture);
Expand Down Expand Up @@ -183,15 +188,14 @@ private Future<ClaimingResults> createJobsByVendor(ClaimingCollection claimingCo
Map<Organization, List<String>> pieceIdsByVendor, RequestContext requestContext) {
log.info("createJobsByVendor:: Creating jobs by vendor, vendors by pieces count: {}", pieceIdsByVendor.size());
if (CollectionUtils.isEmpty(pieceIdsByVendor)) {
log.info("createJobsByVendor:: No jobs are created, pieceIdsByVendor is empty");
return Future.succeededFuture(new ClaimingResults()
.withClaimingPieceResults(createErrorClaimingResults(pieceIdsByVendor, CANNOT_GROUP_PIECES_BY_VENDOR_MESSAGE.getValue())));
log.info("createJobsByVendor:: Cannot group pieces by vendor - No jobs were create or pieces processed");
throwHttpException(CANNOT_GROUP_PIECES_BY_VENDOR, claimingCollection, HttpStatus.HTTP_NOT_FOUND);
}
return collectResultsOnSuccess(createUpdatePiecesAndJobFutures(claimingCollection, config, pieceIdsByVendor, requestContext))
.map(updatedPieceLists -> {
if (CollectionUtils.isEmpty(updatedPieceLists)) {
log.info("createJobsByVendor:: No pieces were processed for claiming");
return new ClaimingResults().withClaimingPieceResults(createErrorClaimingResults(pieceIdsByVendor, CANNOT_CREATE_JOBS_AND_UPDATE_PIECES.getValue()));
log.info("createJobsByVendor:: Cannot create jobs and update pieces - No jobs were create or pieces processed");
throwHttpException(CANNOT_CREATE_JOBS_AND_UPDATE_PIECES, claimingCollection, HttpStatus.HTTP_NOT_FOUND);
}
var successClaimingPieceResults = createSuccessClaimingResults(updatedPieceLists);
log.info("createJobsByVendor:: Successfully processed pieces for claiming, count: {}", successClaimingPieceResults.size());
Expand All @@ -209,34 +213,23 @@ private List<Future<List<String>>> createUpdatePiecesAndJobFutures(ClaimingColle
.forEach(configEntry -> {
log.info("createUpdatePiecesAndJobFutures:: Preparing job integration detail for vendor, vendor id: {}, pieces: {}, job key: {}",
vendor.getId(), pieceIds.size(), configEntry.getKey());
updatePiecesAndJobFutures.add(updatePiecesAndCreateJob(claimingCollection, pieceIds, configEntry, requestContext));
updatePiecesAndJobFutures.add(updatePiecesAndCreateJob(claimingCollection, pieceIds, configEntry, requestContext).map(pieceIds));
}));
return updatePiecesAndJobFutures;
}

private static ClaimingResults createEmptyClaimingResults(String message) {
return new ClaimingResults().withClaimingPieceResults(List.of(new ClaimingPieceResult().withError(new Error().withMessage(message))));
}

private List<ClaimingPieceResult> createSuccessClaimingResults(List<List<String>> updatedPieceLists) {
return updatedPieceLists.stream().flatMap(Collection::stream).distinct()
.map(pieceId -> new ClaimingPieceResult().withPieceId(pieceId).withStatus(SUCCESS))
.toList();
}

private List<ClaimingPieceResult> createErrorClaimingResults(Map<Organization, List<String>> pieceIdsByVendor, String message) {
return pieceIdsByVendor.values().stream()
.flatMap(Collection::stream)
.map(pieceId -> new ClaimingPieceResult().withPieceId(pieceId).withStatus(FAILURE).withError(new Error().withMessage(message)))
.toList();
}

private Future<List<String>> updatePiecesAndCreateJob(ClaimingCollection claimingCollection, List<String> pieceIds,
Map.Entry<String, Object> configEntry, RequestContext requestContext) {
private Future<Void> updatePiecesAndCreateJob(ClaimingCollection claimingCollection, List<String> pieceIds,
Map.Entry<String, Object> configEntry, RequestContext requestContext) {
log.info("updatePiecesAndCreateJob:: Updating pieces and creating a job, job key: {}, count: {}", configEntry.getKey(), pieceIds.size());
return pieceUpdateFlowManager.updatePiecesStatuses(pieceIds, PieceBatchStatusCollection.ReceivingStatus.CLAIM_SENT,
claimingCollection.getClaimingInterval(), claimingCollection.getInternalNote(), claimingCollection.getExternalNote(), requestContext)
.compose(v -> createJob(configEntry.getKey(), configEntry.getValue(), pieceIds, requestContext).map(pieceIds));
return createJob(configEntry.getKey(), configEntry.getValue(), pieceIds, requestContext).map(pieceIds)
.compose(v -> pieceUpdateFlowManager.updatePiecesStatuses(pieceIds, PieceBatchStatusCollection.ReceivingStatus.CLAIM_SENT,
claimingCollection.getClaimingInterval(), claimingCollection.getInternalNote(), claimingCollection.getExternalNote(), requestContext));
}

private Future<Void> createJob(String configKey, Object configValue, List<String> pieceIds, RequestContext requestContext) {
Expand All @@ -253,4 +246,26 @@ private Future<Void> createJob(String configKey, Object configValue, List<String
})
.mapEmpty();
}

private void throwHttpExceptionOnMissingVendorIntegrationDetails(ClaimingCollection claimingCollection, Organization vendorWithoutIntegrationDetails) {
var parameters = createPieceIdParameters(claimingCollection);
parameters.add(new Parameter().withKey(VENDOR_CODE_PARAMETER).withValue(vendorWithoutIntegrationDetails.getCode()));
var errors = List.of(new Error().withCode(UNABLE_TO_GENERATE_CLAIMS_FOR_ORG_NO_INTEGRATION_DETAILS.getCode())
.withMessage(String.format(UNABLE_TO_GENERATE_CLAIMS_FOR_ORG_NO_INTEGRATION_DETAILS.getDescription(), vendorWithoutIntegrationDetails.getCode()))
.withParameters(parameters));
throw new HttpException(HttpStatus.HTTP_UNPROCESSABLE_ENTITY.toInt(), new Errors().withErrors(errors).withTotalRecords(errors.size()));
}

private void throwHttpException(ErrorCodes errorCode, ClaimingCollection claimingCollection, HttpStatus httpStatus) {
var errors = List.of(new Error().withCode(errorCode.getCode())
.withMessage(errorCode.getDescription())
.withParameters(createPieceIdParameters(claimingCollection)));
throw new HttpException(httpStatus.toInt(), new Errors().withErrors(errors).withTotalRecords(errors.size()));
}

private ArrayList<Parameter> createPieceIdParameters(ClaimingCollection claimingCollection) {
return claimingCollection.getClaimingPieceIds().stream()
.map(pieceId -> new Parameter().withKey(PIECE_ID_PARAMETER).withValue(pieceId))
.collect(Collectors.toCollection(ArrayList::new));
}
}
9 changes: 6 additions & 3 deletions src/test/java/org/folio/rest/impl/PiecesClaimingApiTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -186,15 +186,13 @@ void testPostPiecesClaim(String name, int vendorIdx, int poLineIdx, int pieceIdx
.filter(Objects::nonNull).filter(poLineId -> poLineId.equals(purchaseOrder.getId()))
.toList();

purchaseOrderRetrievals.forEach(entry -> logger.info("PurchaseOrders: {}", entry));
purchaseOrderRetrievals.forEach(entry -> logger.info("Retrieved PurchaseOrder: {}", entry));

var organizationSearches = getOrganizationSearches();
var pieceUpdates = getPieceUpdates();
var jobCreations = getDataExportSpringJobCreations();
var jobExecutions = getDataExportSpringJobExecutions();

pieceUpdates.forEach(pieceUpdate -> logger.info("Updated piece: {}", pieceUpdate.encodePrettily()));

if (response instanceof ClaimingResults claimingResults) {
if (Objects.nonNull(dto)) {
assertThat(pieceSearches, not(nullValue()));
Expand All @@ -213,6 +211,8 @@ void testPostPiecesClaim(String name, int vendorIdx, int poLineIdx, int pieceIdx
assertThat(jobExecutions, hasSize(dto.jobExecutions));
assertThat(claimingResults.getClaimingPieceResults().size(), equalTo(dto.claimingResults));

pieceUpdates.forEach(pieceUpdate -> logger.info("Updated Piece: {}", pieceUpdate.encodePrettily()));

var claimedPieceIds = jobCreations.stream()
.peek(job -> logger.info("Created job: {}", JsonObject.mapFrom(job).encodePrettily()))
.map(job -> job.getJsonObject(EXPORT_TYPE_SPECIFIC_PARAMETERS.getValue())
Expand All @@ -234,7 +234,10 @@ void testPostPiecesClaim(String name, int vendorIdx, int poLineIdx, int pieceIdx
}
} else if (response instanceof HttpException exception) {
assertThat(exception.getErrors().getErrors().size(), is(1));
assertThat(exception.getError().getCode(), is("unableToGenerateClaimsForOrgNoIntegrationDetails"));
assertThat(exception.getError().getMessage(), is("Unable to generate claims for AMAZ because no claim integrations exist"));
assertThat(exception.getError().getParameters().get(0).getValue(), is(piece.getId()));
assertThat(exception.getError().getParameters().get(1).getValue(), is("AMAZ"));
}
}
}
Loading

0 comments on commit 812dd45

Please sign in to comment.