Skip to content

Commit

Permalink
[MODORDERS-1209-2]. Add tenant validation to integration details
Browse files Browse the repository at this point in the history
  • Loading branch information
BKadirkhodjaev committed Dec 20, 2024
1 parent 812dd45 commit a45584d
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
48 changes: 29 additions & 19 deletions src/main/java/org/folio/service/pieces/PiecesClaimingService.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -102,7 +104,7 @@ public Future<ClaimingResults> 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);
Expand Down Expand Up @@ -163,27 +165,20 @@ private Future<Pair<Organization, String>> createVendorPiecePair(Pair<String, St
});
}

private Organization checkVendorIntegrationDetails(JsonObject config, Map<Organization, List<String>> pieceIdsByVendors) {
private Organization findFirstMissingVendorWithoutIntegrationDetail(JsonObject config, Map<Organization, List<String>> 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();
})
.findFirst().orElse(null);
}

private boolean isExportTypeClaimsAndCorrectVendorId(String vendorId, Map.Entry<String, Object> configEntry) {
return configEntry.getKey().startsWith(String.format("%s_%s", EXPORT_TYPE_CLAIMS, vendorId)) && Objects.nonNull(configEntry.getValue());
}

private static Map<Organization, List<String>> transformAndGroupPieceIdsByVendor(List<Pair<Organization, String>> piecesByVendorList) {
return StreamEx.of(piecesByVendorList).distinct().filter(Objects::nonNull)
.groupingBy(Pair::getKey, mapping(Pair::getValue, toList()));
}

private Future<ClaimingResults> createJobsByVendor(ClaimingCollection claimingCollection, JsonObject config,
Map<Organization, List<String>> pieceIdsByVendor, RequestContext requestContext) {
log.info("createJobsByVendor:: Creating jobs by vendor, vendors by pieces count: {}", pieceIdsByVendor.size());
Expand All @@ -209,7 +204,8 @@ private List<Future<List<String>>> createUpdatePiecesAndJobFutures(ClaimingColle
Map<Organization, List<String>> pieceIdsByVendor, RequestContext requestContext) {
var updatePiecesAndJobFutures = new ArrayList<Future<List<String>>>();
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());
Expand All @@ -218,12 +214,6 @@ private List<Future<List<String>>> createUpdatePiecesAndJobFutures(ClaimingColle
return updatePiecesAndJobFutures;
}

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 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());
Expand All @@ -247,6 +237,26 @@ private Future<Void> createJob(String configKey, Object configValue, List<String
.mapEmpty();
}

private boolean isExportTypeClaimsAndCorrectVendorId(String vendorId, Map.Entry<String, Object> configEntry) {
return configEntry.getKey().startsWith(String.format("%s_%s", EXPORT_TYPE_CLAIMS, vendorId)) && Objects.nonNull(configEntry.getValue());
}

private boolean isCorrectTenant(String tenantId, Map.Entry<String, Object> configEntry) {
var integrationDetail = new JsonObject(String.valueOf(configEntry.getValue()));
return integrationDetail.getString(TENANT.getValue()).equals(tenantId);
}

private static Map<Organization, List<String>> transformAndGroupPieceIdsByVendor(List<Pair<Organization, String>> piecesByVendorList) {
return StreamEx.of(piecesByVendorList).distinct().filter(Objects::nonNull)
.groupingBy(Pair::getKey, mapping(Pair::getValue, toList()));
}

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 void throwHttpExceptionOnMissingVendorIntegrationDetails(ClaimingCollection claimingCollection, Organization vendorWithoutIntegrationDetails) {
var parameters = createPieceIdParameters(claimingCollection);
parameters.add(new Parameter().withKey(VENDOR_CODE_PARAMETER).withValue(vendorWithoutIntegrationDetails.getCode()));
Expand All @@ -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<Parameter> createPieceIdParameters(ClaimingCollection claimingCollection) {
private List<Parameter> createPieceIdParameters(ClaimingCollection claimingCollection) {
return claimingCollection.getClaimingPieceIds().stream()
.map(pieceId -> new Parameter().withKey(PIECE_ID_PARAMETER).withValue(pieceId))
.collect(Collectors.toCollection(ArrayList::new));
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit a45584d

Please sign in to comment.