diff --git a/src/main/java/org/folio/models/claiming/IntegrationDetailField.java b/src/main/java/org/folio/models/claiming/IntegrationDetailField.java index bf75daa7d..f7c5110e6 100644 --- a/src/main/java/org/folio/models/claiming/IntegrationDetailField.java +++ b/src/main/java/org/folio/models/claiming/IntegrationDetailField.java @@ -6,6 +6,7 @@ @Getter @AllArgsConstructor public enum IntegrationDetailField { + TENANT("tenant"), EXPORT_TYPE_SPECIFIC_PARAMETERS("exportTypeSpecificParameters"), VENDOR_EDI_ORDERS_EXPORT_CONFIG("vendorEdiOrdersExportConfig"), CLAIM_PIECE_IDS("claimPieceIds"); diff --git a/src/main/java/org/folio/service/pieces/PiecesClaimingService.java b/src/main/java/org/folio/service/pieces/PiecesClaimingService.java index 5175f5a0f..63203401a 100644 --- a/src/main/java/org/folio/service/pieces/PiecesClaimingService.java +++ b/src/main/java/org/folio/service/pieces/PiecesClaimingService.java @@ -20,6 +20,7 @@ import org.folio.rest.jaxrs.model.Parameter; import org.folio.rest.jaxrs.model.Piece; import org.folio.rest.jaxrs.model.PieceBatchStatusCollection; +import org.folio.rest.tools.utils.TenantTool; import org.folio.service.caches.ConfigurationEntriesCache; import org.folio.service.orders.PurchaseOrderLineService; import org.folio.service.orders.PurchaseOrderStorageService; @@ -40,6 +41,7 @@ import static java.util.stream.Collectors.toList; 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.TENANT; import static org.folio.models.claiming.IntegrationDetailField.VENDOR_EDI_ORDERS_EXPORT_CONFIG; import static org.folio.orders.utils.HelperUtils.DATA_EXPORT_SPRING_CONFIG_MODULE_NAME; import static org.folio.orders.utils.HelperUtils.collectResultsOnSuccess; @@ -102,7 +104,7 @@ public Future sendClaims(ClaimingCollection claimingCollection, } pieceIdsByVendors.forEach((vendor, piecesByVendor) -> log.info("createVendorPiecePair:: Using pieces by vendor map, vendorId: {}, piecesByVendor: {}", vendor.getId(), piecesByVendor)); - var vendorWithoutIntegrationDetails = checkVendorIntegrationDetails(config, pieceIdsByVendors); + var vendorWithoutIntegrationDetails = findFirstMissingVendorWithoutIntegrationDetail(config, pieceIdsByVendors, requestContext); if (Objects.nonNull(vendorWithoutIntegrationDetails)) { log.info("sendClaims:: Unable to generate claims because no claim integrations exist - No claims are sent"); throwHttpExceptionOnMissingVendorIntegrationDetails(claimingCollection, vendorWithoutIntegrationDetails); @@ -163,11 +165,13 @@ private Future> createVendorPiecePair(Pair> pieceIdsByVendors) { + private Organization findFirstMissingVendorWithoutIntegrationDetail(JsonObject config, Map> pieceIdsByVendors, + RequestContext requestContext) { return pieceIdsByVendors.keySet().stream() .filter(vendor -> { var vendorIntegrationDetails = config.stream() .filter(configEntry -> isExportTypeClaimsAndCorrectVendorId(vendor.getId(), configEntry)) + .filter(configEntry -> isCorrectTenant(TenantTool.tenantId(requestContext.getHeaders()), configEntry)) .toList(); log.info("checkVendorIntegrationDetails:: Found vendor integration details, vendorId: {}, integrationDetails: {}", vendor.getId(), vendorIntegrationDetails); return vendorIntegrationDetails.isEmpty(); @@ -175,15 +179,6 @@ private Organization checkVendorIntegrationDetails(JsonObject config, Map configEntry) { - return configEntry.getKey().startsWith(String.format("%s_%s", EXPORT_TYPE_CLAIMS, vendorId)) && Objects.nonNull(configEntry.getValue()); - } - - private static Map> transformAndGroupPieceIdsByVendor(List> piecesByVendorList) { - return StreamEx.of(piecesByVendorList).distinct().filter(Objects::nonNull) - .groupingBy(Pair::getKey, mapping(Pair::getValue, toList())); - } - private Future createJobsByVendor(ClaimingCollection claimingCollection, JsonObject config, Map> pieceIdsByVendor, RequestContext requestContext) { log.info("createJobsByVendor:: Creating jobs by vendor, vendors by pieces count: {}", pieceIdsByVendor.size()); @@ -209,7 +204,8 @@ private List>> createUpdatePiecesAndJobFutures(ClaimingColle Map> pieceIdsByVendor, RequestContext requestContext) { var updatePiecesAndJobFutures = new ArrayList>>(); pieceIdsByVendor.forEach((vendor, pieceIds) -> config.stream() - .filter(pieceIdsByVendorIdEntry -> isExportTypeClaimsAndCorrectVendorId(vendor.getId(), pieceIdsByVendorIdEntry)) + .filter(configEntry -> isExportTypeClaimsAndCorrectVendorId(vendor.getId(), configEntry)) + .filter(configEntry -> isCorrectTenant(TenantTool.tenantId(requestContext.getHeaders()), configEntry)) .forEach(configEntry -> { log.info("createUpdatePiecesAndJobFutures:: Preparing job integration detail for vendor, vendor id: {}, pieces: {}, job key: {}", vendor.getId(), pieceIds.size(), configEntry.getKey()); @@ -218,12 +214,6 @@ private List>> createUpdatePiecesAndJobFutures(ClaimingColle return updatePiecesAndJobFutures; } - private List createSuccessClaimingResults(List> updatedPieceLists) { - return updatedPieceLists.stream().flatMap(Collection::stream).distinct() - .map(pieceId -> new ClaimingPieceResult().withPieceId(pieceId).withStatus(SUCCESS)) - .toList(); - } - private Future updatePiecesAndCreateJob(ClaimingCollection claimingCollection, List pieceIds, Map.Entry configEntry, RequestContext requestContext) { log.info("updatePiecesAndCreateJob:: Updating pieces and creating a job, job key: {}, count: {}", configEntry.getKey(), pieceIds.size()); @@ -247,6 +237,26 @@ private Future createJob(String configKey, Object configValue, List configEntry) { + return configEntry.getKey().startsWith(String.format("%s_%s", EXPORT_TYPE_CLAIMS, vendorId)) && Objects.nonNull(configEntry.getValue()); + } + + private boolean isCorrectTenant(String tenantId, Map.Entry configEntry) { + var integrationDetail = new JsonObject(String.valueOf(configEntry.getValue())); + return integrationDetail.getString(TENANT.getValue()).equals(tenantId); + } + + private static Map> transformAndGroupPieceIdsByVendor(List> piecesByVendorList) { + return StreamEx.of(piecesByVendorList).distinct().filter(Objects::nonNull) + .groupingBy(Pair::getKey, mapping(Pair::getValue, toList())); + } + + private List createSuccessClaimingResults(List> updatedPieceLists) { + return updatedPieceLists.stream().flatMap(Collection::stream).distinct() + .map(pieceId -> new ClaimingPieceResult().withPieceId(pieceId).withStatus(SUCCESS)) + .toList(); + } + private void throwHttpExceptionOnMissingVendorIntegrationDetails(ClaimingCollection claimingCollection, Organization vendorWithoutIntegrationDetails) { var parameters = createPieceIdParameters(claimingCollection); parameters.add(new Parameter().withKey(VENDOR_CODE_PARAMETER).withValue(vendorWithoutIntegrationDetails.getCode())); @@ -263,7 +273,7 @@ private void throwHttpException(ErrorCodes errorCode, ClaimingCollection claimin throw new HttpException(httpStatus.toInt(), new Errors().withErrors(errors).withTotalRecords(errors.size())); } - private ArrayList createPieceIdParameters(ClaimingCollection claimingCollection) { + private List createPieceIdParameters(ClaimingCollection claimingCollection) { return claimingCollection.getClaimingPieceIds().stream() .map(pieceId -> new Parameter().withKey(PIECE_ID_PARAMETER).withValue(pieceId)) .collect(Collectors.toCollection(ArrayList::new)); diff --git a/src/test/java/org/folio/service/pieces/PiecesClaimingServiceTest.java b/src/test/java/org/folio/service/pieces/PiecesClaimingServiceTest.java index 421546376..afdb76c1a 100644 --- a/src/test/java/org/folio/service/pieces/PiecesClaimingServiceTest.java +++ b/src/test/java/org/folio/service/pieces/PiecesClaimingServiceTest.java @@ -1,6 +1,7 @@ package org.folio.service.pieces; import io.vertx.core.Future; +import io.vertx.core.Vertx; import io.vertx.core.json.JsonObject; import io.vertx.junit5.VertxExtension; import io.vertx.junit5.VertxTestContext; @@ -32,13 +33,16 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.List; +import java.util.Map; import java.util.UUID; import static org.folio.models.claiming.IntegrationDetailField.CLAIM_PIECE_IDS; +import static org.folio.models.claiming.IntegrationDetailField.TENANT; import static org.folio.models.claiming.IntegrationDetailField.VENDOR_EDI_ORDERS_EXPORT_CONFIG; import static org.folio.models.claiming.IntegrationDetailField.EXPORT_TYPE_SPECIFIC_PARAMETERS; import static org.folio.orders.utils.ResourcePathResolver.DATA_EXPORT_SPRING_CREATE_JOB; import static org.folio.orders.utils.ResourcePathResolver.resourcesPath; +import static org.folio.rest.RestVerticle.OKAPI_HEADER_TENANT; 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_RETRIEVE_CONFIG_ENTRIES; import static org.folio.rest.core.exceptions.ErrorCodes.CANNOT_SEND_CLAIMS_PIECE_IDS_ARE_EMPTY; @@ -246,6 +250,38 @@ void testSendClaims_missingOrganizationIntegrationDetailsForThreeOrganizations(V }))); } + @Test + void testSendClaims_incorrectRequestTenant(VertxTestContext testContext) { + var pieceId = UUID.randomUUID().toString(); + var piece = new Piece().withId(pieceId).withPoLineId("poLineId").withReceivingStatus(Piece.ReceivingStatus.LATE); + var claimingCollection = new ClaimingCollection().withClaimingPieceIds(List.of(pieceId)); + var requestContext = new RequestContext(Vertx.vertx().getOrCreateContext(), Map.of(OKAPI_HEADER_TENANT, "folio")); + + when(configurationEntriesCache.loadConfiguration(any(), any())).thenReturn(Future.succeededFuture(new JsonObject() + .put("CLAIMS_vendorId", createIntegrationDetail()))); + when(pieceStorageService.getPiecesByIds(any(), any())).thenReturn(Future.succeededFuture(List.of(piece))); + when(purchaseOrderLineService.getOrderLineById(any(), any())).thenReturn(Future.succeededFuture(new PoLine().withPurchaseOrderId("orderId"))); + when(purchaseOrderStorageService.getPurchaseOrderById(any(), any())).thenReturn(Future.succeededFuture(new PurchaseOrder().withVendor("vendorId"))); + when(organizationService.getVendorById(any(), any())).thenReturn(Future.succeededFuture(new Organization().withId("vendorId").withCode("VENDOR").withIsVendor(true))); + + piecesClaimingService.sendClaims(claimingCollection, requestContext) + .onComplete(testContext.failing(throwable -> testContext.verify(() -> { + Assertions.assertInstanceOf(HttpException.class, throwable); + var httpException = (HttpException) throwable; + var error = httpException.getErrors().getErrors().get(0); + assertEquals(UNABLE_TO_GENERATE_CLAIMS_FOR_ORG_NO_INTEGRATION_DETAILS.getCode(), error.getCode()); + assertEquals(String.format(UNABLE_TO_GENERATE_CLAIMS_FOR_ORG_NO_INTEGRATION_DETAILS.getDescription(), "VENDOR"), error.getMessage()); + Assertions.assertEquals(2, error.getParameters().size()); + var parameter1 = error.getParameters().get(0); + Assertions.assertEquals("pieceId", parameter1.getKey()); + Assertions.assertEquals(pieceId, parameter1.getValue()); + var parameter2 = error.getParameters().get(1); + Assertions.assertEquals("vendorCode", parameter2.getKey()); + Assertions.assertEquals("VENDOR", parameter2.getValue()); + testContext.completeNow(); + }))); + } + @Test void testSendClaims_success(VertxTestContext testContext) { var claimingCollection = new ClaimingCollection().withClaimingPieceIds(List.of("pieceId1")).withClaimingInterval(1); @@ -411,6 +447,6 @@ void testSendClaims_successWithMultipleOrganizationsAndOneIntegrationDetail(Vert private JsonObject createIntegrationDetail() { var vendorEditOrdersExportConfig = new JsonObject().put(CLAIM_PIECE_IDS.getValue(), List.of()); var exportTypeSpecificParameters = new JsonObject().put(VENDOR_EDI_ORDERS_EXPORT_CONFIG.getValue(), vendorEditOrdersExportConfig); - return new JsonObject().put(EXPORT_TYPE_SPECIFIC_PARAMETERS.getValue(), exportTypeSpecificParameters); + return new JsonObject().put(EXPORT_TYPE_SPECIFIC_PARAMETERS.getValue(), exportTypeSpecificParameters).put(TENANT.getValue(), "folio_shared"); } } diff --git a/src/test/resources/mockdata/configurations.entries/test_diku_limit_10_claims.json b/src/test/resources/mockdata/configurations.entries/test_diku_limit_10_claims.json index 833dd24b7..b06c817d4 100644 --- a/src/test/resources/mockdata/configurations.entries/test_diku_limit_10_claims.json +++ b/src/test/resources/mockdata/configurations.entries/test_diku_limit_10_claims.json @@ -77,7 +77,7 @@ }, { "id": "245bc212-4e56-4875-8c84-4401018d4242", - "value": "{\"id\":\"245bc212-4e56-4875-8c84-4401018d4242\",\"type\":\"CLAIMS\",\"tenant\":\"diku\",\"exportTypeSpecificParameters\":{\"vendorEdiOrdersExportConfig\":{\"exportConfigId\":\"245bc212-4e56-4875-8c84-4401018d4242\",\"vendorId\":\"e0fb5df2-cdf1-11e8-a8d5-f2801f1b9fd1\",\"integrationType\":\"Claiming\",\"transmissionMethod\":\"File download\",\"fileFormat\":\"CSV\",\"configName\":\"Test 1\",\"ediConfig\":{\"accountNoList\":[\"1\"],\"ediNamingConvention\":\"{organizationCode}-{integrationName}-{exportJobEndDate}\",\"libEdiCode\":\"Test 1\",\"libEdiType\":\"31B/US-SAN\",\"vendorEdiCode\":\"Test 1\",\"vendorEdiType\":\"31B/US-SAN\",\"sendAccountNumber\":false,\"supportOrder\":false,\"supportInvoice\":false},\"ediFtp\":{\"ftpConnMode\":\"Active\",\"ftpFormat\":\"SFTP\",\"ftpMode\":\"ASCII\",\"ftpPort\":1,\"serverAddress\":\"ftp://test1.org\"},\"isDefaultConfig\":false}},\"schedulePeriod\":\"NONE\"}", + "value": "{\"id\":\"245bc212-4e56-4875-8c84-4401018d4242\",\"type\":\"CLAIMS\",\"tenant\":\"test_diku_limit_10_claims\",\"exportTypeSpecificParameters\":{\"vendorEdiOrdersExportConfig\":{\"exportConfigId\":\"245bc212-4e56-4875-8c84-4401018d4242\",\"vendorId\":\"e0fb5df2-cdf1-11e8-a8d5-f2801f1b9fd1\",\"integrationType\":\"Claiming\",\"transmissionMethod\":\"File download\",\"fileFormat\":\"CSV\",\"configName\":\"Test 1\",\"ediConfig\":{\"accountNoList\":[\"1\"],\"ediNamingConvention\":\"{organizationCode}-{integrationName}-{exportJobEndDate}\",\"libEdiCode\":\"Test 1\",\"libEdiType\":\"31B/US-SAN\",\"vendorEdiCode\":\"Test 1\",\"vendorEdiType\":\"31B/US-SAN\",\"sendAccountNumber\":false,\"supportOrder\":false,\"supportInvoice\":false},\"ediFtp\":{\"ftpConnMode\":\"Active\",\"ftpFormat\":\"SFTP\",\"ftpMode\":\"ASCII\",\"ftpPort\":1,\"serverAddress\":\"ftp://test1.org\"},\"isDefaultConfig\":false}},\"schedulePeriod\":\"NONE\"}", "module": "mod-data-export-spring", "default": true, "enabled": true,