diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json index 8aa900318..d28607e6c 100644 --- a/descriptors/ModuleDescriptor-template.json +++ b/descriptors/ModuleDescriptor-template.json @@ -106,6 +106,10 @@ "id": "orders-storage.po-lines", "version": "12.1" }, + { + "id": "orders-storage.pieces", + "version": "5.0" + }, { "id": "organizations.organizations", "version": "1.2" diff --git a/folio-export-common b/folio-export-common index dcc732532..839deafc2 160000 --- a/folio-export-common +++ b/folio-export-common @@ -1 +1 @@ -Subproject commit dcc732532ea07c4f90f7d999ce9c3126c331817a +Subproject commit 839deafc288914ba7bcc58a8c598b369f4af466c diff --git a/pom.xml b/pom.xml index 9d16a573f..11d0fe7f2 100644 --- a/pom.xml +++ b/pom.xml @@ -59,6 +59,7 @@ 5.7.1 12.1 2.9.2 + 0.8.3 2.4.0 @@ -205,7 +206,7 @@ io.xlate staedi - 1.24.0 + 1.25.2 @@ -250,6 +251,12 @@ pom + + one.util + streamex + ${streamex.version} + + diff --git a/src/main/java/org/folio/dew/batch/acquisitions/edifact/CompositePOConverter.java b/src/main/java/org/folio/dew/batch/acquisitions/edifact/CompositePOConverter.java index d94c95dcf..0b7e8b92a 100644 --- a/src/main/java/org/folio/dew/batch/acquisitions/edifact/CompositePOConverter.java +++ b/src/main/java/org/folio/dew/batch/acquisitions/edifact/CompositePOConverter.java @@ -7,11 +7,14 @@ import org.folio.dew.batch.acquisitions.edifact.services.ConfigurationService; import org.folio.dew.domain.dto.CompositePoLine; import org.folio.dew.domain.dto.CompositePurchaseOrder; +import org.folio.dew.domain.dto.Piece; import org.folio.dew.domain.dto.acquisitions.edifact.EdiFileConfig; import org.folio.dew.error.NotFoundException; import java.text.DateFormat; import java.text.SimpleDateFormat; +import java.util.List; +import java.util.Map; public class CompositePOConverter { private static final String RUSH_ORDER = "224"; @@ -25,7 +28,8 @@ public CompositePOConverter(CompositePOLineConverter compositePOLineConverter, C this.configurationService = configurationService; } - public void convertPOtoEdifact(EDIStreamWriter writer, CompositePurchaseOrder compPO, EdiFileConfig ediFileConfig) throws EDIStreamException { + public void convertPOtoEdifact(EDIStreamWriter writer, CompositePurchaseOrder compPO, + Map> poLineToPieces, EdiFileConfig ediFileConfig) throws EDIStreamException { int messageSegmentCount = 0; messageSegmentCount++; @@ -65,7 +69,8 @@ public void convertPOtoEdifact(EDIStreamWriter writer, CompositePurchaseOrder co int totalNumberOfLineItems = 0; for (CompositePoLine poLine : compPO.getCompositePoLines()) { int quantityOrdered = getPoLineQuantityOrdered(poLine); - int segments = compositePOLineConverter.convertPOLine(poLine, writer, ++totalNumberOfLineItems, quantityOrdered); + var pieces = poLineToPieces.getOrDefault(poLine.getId(), List.of()); + int segments = compositePOLineConverter.convertPOLine(poLine, pieces, writer, ++totalNumberOfLineItems, quantityOrdered); messageSegmentCount += segments; totalQuantity += quantityOrdered; } diff --git a/src/main/java/org/folio/dew/batch/acquisitions/edifact/CompositePOLineConverter.java b/src/main/java/org/folio/dew/batch/acquisitions/edifact/CompositePOLineConverter.java index f4b354e96..cb09bac3e 100644 --- a/src/main/java/org/folio/dew/batch/acquisitions/edifact/CompositePOLineConverter.java +++ b/src/main/java/org/folio/dew/batch/acquisitions/edifact/CompositePOLineConverter.java @@ -1,12 +1,17 @@ package org.folio.dew.batch.acquisitions.edifact; +import static org.folio.dew.domain.dto.ReferenceNumberItem.RefNumberTypeEnum.ORDER_REFERENCE_NUMBER; + import javax.money.CurrencyUnit; import javax.money.Monetary; import javax.money.MonetaryAmount; import io.xlate.edi.stream.EDIStreamException; import io.xlate.edi.stream.EDIStreamWriter; -import liquibase.util.StringUtil; +import one.util.streamex.StreamEx; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; import org.folio.dew.batch.acquisitions.edifact.services.ExpenseClassService; import org.folio.dew.batch.acquisitions.edifact.services.HoldingService; import org.folio.dew.batch.acquisitions.edifact.services.IdentifierTypeService; @@ -17,16 +22,18 @@ import org.folio.dew.domain.dto.Cost; import org.folio.dew.domain.dto.FundDistribution; import org.folio.dew.domain.dto.Location; +import org.folio.dew.domain.dto.Piece; import org.folio.dew.domain.dto.ProductIdentifier; import org.folio.dew.domain.dto.ReferenceNumberItem; +import org.folio.dew.domain.dto.VendorDetail; import org.javamoney.moneta.Money; -import org.springframework.beans.factory.annotation.Autowired; import java.math.BigDecimal; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; public class CompositePOLineConverter { private static final int MAX_CHARS_PER_LINE = 70; @@ -44,18 +51,22 @@ public class CompositePOLineConverter { private static final String EN_PRODUCT_ID_QUALIFIER = "EN"; private static final int EAN_IDENTIFIER_LENGTH = 13; - @Autowired - private IdentifierTypeService identifierTypeService; - @Autowired - private MaterialTypeService materialTypeService; - @Autowired - private ExpenseClassService expenseClassService; - @Autowired - private LocationService locationService; - @Autowired - private HoldingService holdingService; - - public int convertPOLine(CompositePoLine poLine, EDIStreamWriter writer, int currentLineNumber, int quantityOrdered) throws EDIStreamException { + private final IdentifierTypeService identifierTypeService; + private final MaterialTypeService materialTypeService; + private final ExpenseClassService expenseClassService; + private final LocationService locationService; + private final HoldingService holdingService; + + public CompositePOLineConverter(IdentifierTypeService identifierTypeService, MaterialTypeService materialTypeService, + ExpenseClassService expenseClassService, LocationService locationService, HoldingService holdingService) { + this.identifierTypeService = identifierTypeService; + this.materialTypeService = materialTypeService; + this.expenseClassService = expenseClassService; + this.locationService = locationService; + this.holdingService = holdingService; + } + + public int convertPOLine(CompositePoLine poLine, List pieces, EDIStreamWriter writer, int currentLineNumber, int quantityOrdered) throws EDIStreamException { int messageSegmentCount = 0; Map productTypeProductIdentifierMap = new HashMap<>(); @@ -82,6 +93,14 @@ public int convertPOLine(CompositePoLine poLine, EDIStreamWriter writer, int cur writeTitle(titlePart, writer); } + for (Piece piece : pieces) { + var pieceDetails = getPieceDetails(piece); + if (StringUtils.isNotBlank(pieceDetails)) { + messageSegmentCount++; + writePiece(pieceDetails, writer); + } + } + if (poLine.getPublisher() != null) { messageSegmentCount++; writePublisher(poLine.getPublisher(), writer); @@ -93,13 +112,13 @@ public int convertPOLine(CompositePoLine poLine, EDIStreamWriter writer, int cur } String physicalMaterial = getPhysicalMaterial(poLine); - if (StringUtil.isNotEmpty(physicalMaterial)) { + if (StringUtils.isNotEmpty(physicalMaterial)) { messageSegmentCount++; writeMaterialType(physicalMaterial, writer); } String electronicMaterial = getElectronicMaterial(poLine); - if (StringUtil.isNotEmpty(electronicMaterial)) { + if (StringUtils.isNotEmpty(electronicMaterial)) { messageSegmentCount++; writeMaterialType(electronicMaterial, writer); } @@ -121,7 +140,7 @@ public int convertPOLine(CompositePoLine poLine, EDIStreamWriter writer, int cur messageSegmentCount++; writePoLineCurrency(poLine, writer); - if (poLine.getVendorDetail() != null && StringUtil.isNotEmpty(poLine.getVendorDetail().getInstructions())) { + if (poLine.getVendorDetail() != null && StringUtils.isNotEmpty(poLine.getVendorDetail().getInstructions())) { messageSegmentCount++; writeInstructionsToVendor(poLine.getVendorDetail().getInstructions(), writer); } @@ -141,16 +160,25 @@ public int convertPOLine(CompositePoLine poLine, EDIStreamWriter writer, int cur writeFundCode(getFundAndExpenseClass(fundDistribution), writer); } - if (poLine.getVendorDetail() != null && referenceQuantity < MAX_NUMBER_OF_REFS) { - for (ReferenceNumberItem number : poLine.getVendorDetail().getReferenceNumbers()) { - if (referenceQuantity >= MAX_NUMBER_OF_REFS) { - break; - } - if (number.getRefNumber() != null) { - referenceQuantity++; - messageSegmentCount++; - writeVendorReferenceNumber(number.getRefNumber(), writer); - } + var referenceNumbers = getVendorReferenceNumbers(poLine); + // Non-empty pieces list is a sign that the export is for claims + if (CollectionUtils.isNotEmpty(pieces)) { + // Vendor order number is a required field for claims export, it must be included regardless of the number of references + var vendorOrderNumber = getAndRemoveVendorOrderNumber(referenceNumbers); + if (vendorOrderNumber != null) { + messageSegmentCount++; + writeVendorOrderNumber(vendorOrderNumber.getRefNumber(), writer); + } + } + + for (ReferenceNumberItem number : getVendorReferenceNumbers(poLine)) { + if (referenceQuantity >= MAX_NUMBER_OF_REFS) { + break; + } + if (number.getRefNumber() != null) { + referenceQuantity++; + messageSegmentCount++; + writeVendorReferenceNumber(number.getRefNumber(), writer); } } @@ -245,6 +273,19 @@ private void writeTitle(String titlePart, EDIStreamWriter writer) throws EDIStre .writeEndSegment(); } + private void writePiece(String pieceDetails, EDIStreamWriter writer) throws EDIStreamException { + writer.writeStartSegment("IMD") + .writeElement("L") + .writeElement("080") + .writeStartElement() + .writeComponent("") + .writeComponent("") + .writeComponent("") + .writeComponent(pieceDetails) + .endElement() + .writeEndSegment(); + } + private void writePublisher(String publisher, EDIStreamWriter writer) throws EDIStreamException { writer.writeStartSegment("IMD") .writeElement("L") @@ -356,10 +397,18 @@ private void writeFundCode(String fundAndExpenseClass, EDIStreamWriter writer) t .writeEndSegment(); } + private void writeVendorOrderNumber(String number, EDIStreamWriter writer) throws EDIStreamException { + writeVendorReferenceNumber(number, "SNA", writer); + } + private void writeVendorReferenceNumber(String number, EDIStreamWriter writer) throws EDIStreamException { + writeVendorReferenceNumber(number, "SLI", writer); + } + + private void writeVendorReferenceNumber(String number, String component, EDIStreamWriter writer) throws EDIStreamException { writer.writeStartSegment("RFF") .writeStartElement() - .writeComponent("SLI") + .writeComponent(component) .writeComponent(number) .endElement() .writeEndSegment(); @@ -417,6 +466,12 @@ private String[] getTitleParts(CompositePoLine poLine) { return title.split("(?<=\\G.{" + MAX_CHARS_PER_LINE + "})"); } + private String getPieceDetails(Piece piece) { + return StreamEx.of(piece.getDisplaySummary(), piece.getChronology(), piece.getEnumeration()) + .filter(StringUtils::isNotBlank) + .joining(":"); + } + private String getPhysicalMaterial(CompositePoLine poLine) { if (poLine.getPhysical() != null && poLine.getPhysical().getMaterialType() != null) { String materialTypeId = poLine.getPhysical().getMaterialType(); @@ -457,6 +512,21 @@ private String getExpenseClassCode(FundDistribution fundDistribution) { return ""; } + private List getVendorReferenceNumbers(CompositePoLine poLine) { + return Optional.ofNullable(poLine.getVendorDetail()) + .map(VendorDetail::getReferenceNumbers) + .orElse(new ArrayList<>()); + } + + private ReferenceNumberItem getAndRemoveVendorOrderNumber(List referenceNumberItems) { + var vendorOrderNumber = referenceNumberItems.stream() + .filter(r -> r.getRefNumberType() == ORDER_REFERENCE_NUMBER) + .findFirst() + .orElse(null); + referenceNumberItems.remove(vendorOrderNumber); + return vendorOrderNumber; + } + private List getLocations(CompositePoLine poLine) { if (poLine.getLocations() != null) { return poLine.getLocations(); diff --git a/src/main/java/org/folio/dew/batch/acquisitions/edifact/PurchaseOrdersToEdifactMapper.java b/src/main/java/org/folio/dew/batch/acquisitions/edifact/PurchaseOrdersToEdifactMapper.java index e59d19df4..3d4443cd0 100644 --- a/src/main/java/org/folio/dew/batch/acquisitions/edifact/PurchaseOrdersToEdifactMapper.java +++ b/src/main/java/org/folio/dew/batch/acquisitions/edifact/PurchaseOrdersToEdifactMapper.java @@ -1,7 +1,8 @@ package org.folio.dew.batch.acquisitions.edifact; +import static java.util.stream.Collectors.groupingBy; + import java.io.ByteArrayOutputStream; -import java.nio.charset.StandardCharsets; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.List; @@ -12,6 +13,8 @@ import io.xlate.edi.stream.EDIOutputFactory; import io.xlate.edi.stream.EDIStreamException; import io.xlate.edi.stream.EDIStreamWriter; + +import org.folio.dew.domain.dto.Piece; import org.folio.dew.domain.dto.VendorEdiOrdersExportConfig; import org.folio.dew.domain.dto.acquisitions.edifact.EdiFileConfig; @@ -23,6 +26,10 @@ public PurchaseOrdersToEdifactMapper(CompositePOConverter compositePOConverter) } public String convertOrdersToEdifact(List compPOs, VendorEdiOrdersExportConfig ediExportConfig, String jobName) throws EDIStreamException { + return convertOrdersToEdifact(compPOs, List.of(), ediExportConfig, jobName); + } + + public String convertOrdersToEdifact(List compPOs, List pieces, VendorEdiOrdersExportConfig ediExportConfig, String jobName) throws EDIStreamException { ByteArrayOutputStream stream = new ByteArrayOutputStream(); EDIOutputFactory factory = EDIOutputFactory.newFactory(); @@ -44,9 +51,10 @@ public String convertOrdersToEdifact(List compPOs, Vendo writeInterchangeHeader(writer, ediFileConfig); + var poLineIdToPieces = pieces.stream().collect(groupingBy(Piece::getPoLineId)); // Purchase orders for (CompositePurchaseOrder compPO : compPOs) { - compositePOConverter.convertPOtoEdifact(writer, compPO, ediFileConfig); + compositePOConverter.convertPOtoEdifact(writer, compPO, poLineIdToPieces, ediFileConfig); messageCount++; } @@ -57,10 +65,6 @@ public String convertOrdersToEdifact(List compPOs, Vendo return stream.toString(); } - public byte[] convertOrdersToEdifactArray(List compPOs, VendorEdiOrdersExportConfig ediExportConfig, String jobName) throws EDIStreamException { - return convertOrdersToEdifact(compPOs, ediExportConfig, jobName).getBytes(StandardCharsets.UTF_8); - } - // Start of file - Can contain multiple order messages private void writeStartFile(EDIStreamWriter writer) throws EDIStreamException { writer.writeStartSegment("UNA") diff --git a/src/main/java/org/folio/dew/batch/acquisitions/edifact/config/EdifactPurchaseOrderConfig.java b/src/main/java/org/folio/dew/batch/acquisitions/edifact/config/EdifactPurchaseOrderConfig.java index c2d2feb05..f7dbbf7c2 100644 --- a/src/main/java/org/folio/dew/batch/acquisitions/edifact/config/EdifactPurchaseOrderConfig.java +++ b/src/main/java/org/folio/dew/batch/acquisitions/edifact/config/EdifactPurchaseOrderConfig.java @@ -4,6 +4,11 @@ import org.folio.dew.batch.acquisitions.edifact.CompositePOLineConverter; import org.folio.dew.batch.acquisitions.edifact.PurchaseOrdersToEdifactMapper; import org.folio.dew.batch.acquisitions.edifact.services.ConfigurationService; +import org.folio.dew.batch.acquisitions.edifact.services.ExpenseClassService; +import org.folio.dew.batch.acquisitions.edifact.services.HoldingService; +import org.folio.dew.batch.acquisitions.edifact.services.IdentifierTypeService; +import org.folio.dew.batch.acquisitions.edifact.services.LocationService; +import org.folio.dew.batch.acquisitions.edifact.services.MaterialTypeService; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @@ -12,8 +17,9 @@ @ComponentScan({ "org.folio.dew.batch.acquisitions.edifact" }) public class EdifactPurchaseOrderConfig { @Bean - CompositePOLineConverter compositePOLineConverter() { - return new CompositePOLineConverter(); + CompositePOLineConverter compositePOLineConverter(IdentifierTypeService identifierTypeService, MaterialTypeService materialTypeService, + ExpenseClassService expenseClassService, LocationService locationService, HoldingService holdingService) { + return new CompositePOLineConverter(identifierTypeService, materialTypeService, expenseClassService, locationService, holdingService); } @Bean diff --git a/src/main/java/org/folio/dew/batch/acquisitions/edifact/exceptions/OrderNotFoundException.java b/src/main/java/org/folio/dew/batch/acquisitions/edifact/exceptions/OrderNotFoundException.java deleted file mode 100644 index 23f9f3bf2..000000000 --- a/src/main/java/org/folio/dew/batch/acquisitions/edifact/exceptions/OrderNotFoundException.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.folio.dew.batch.acquisitions.edifact.exceptions; - -public class OrderNotFoundException extends RuntimeException { - - public OrderNotFoundException(String message, boolean writableStackTrace) { - super(message, null, false, writableStackTrace); - } - -} diff --git a/src/main/java/org/folio/dew/batch/acquisitions/edifact/jobs/EdifactExportJobConfig.java b/src/main/java/org/folio/dew/batch/acquisitions/edifact/jobs/EdifactExportJobConfig.java index 667b18e0c..ba5367604 100644 --- a/src/main/java/org/folio/dew/batch/acquisitions/edifact/jobs/EdifactExportJobConfig.java +++ b/src/main/java/org/folio/dew/batch/acquisitions/edifact/jobs/EdifactExportJobConfig.java @@ -17,18 +17,18 @@ @Log4j2 @RequiredArgsConstructor public class EdifactExportJobConfig { - @Bean - public Job edifactExportJob( - EdiExportJobCompletionListener ediExportJobCompletionListener, - JobRepository jobRepository, - Step mapToEdifactStep, - Step saveToFTPStep, - Step saveToMinIOStep, - Step createExportHistoryRecordsStep) { - return new JobBuilder(ExportType.EDIFACT_ORDERS_EXPORT.getValue(), jobRepository) - .incrementer(new RunIdIncrementer()) + + public static final String POL_MEM_KEY = "poLineIds"; + + private Job constructEdifactExportJob(JobBuilder jobBuilder, + EdiExportJobCompletionListener ediExportJobCompletionListener, + Step mapToEdifactOrdersStep, + Step saveToFTPStep, + Step saveToMinIOStep, + Step createExportHistoryRecordsStep) { + return jobBuilder.incrementer(new RunIdIncrementer()) .listener(ediExportJobCompletionListener) - .start(mapToEdifactStep) + .start(mapToEdifactOrdersStep) .next(saveToMinIOStep) .next(saveToFTPStep) .next(createExportHistoryRecordsStep) @@ -36,10 +36,32 @@ public Job edifactExportJob( } @Bean - public Step mapToEdifactStep(MapToEdifactTasklet mapToEdifactTasklet, JobRepository jobRepository, - PlatformTransactionManager transactionManager) { + public Job edifactOrdersExportJob(EdiExportJobCompletionListener ediExportJobCompletionListener, JobRepository jobRepository, + Step mapToEdifactOrdersStep, Step saveToFTPStep, Step saveToMinIOStep, Step createExportHistoryRecordsStep) { + return constructEdifactExportJob(new JobBuilder(ExportType.EDIFACT_ORDERS_EXPORT.getValue(), jobRepository), + ediExportJobCompletionListener, mapToEdifactOrdersStep, saveToFTPStep, saveToMinIOStep, createExportHistoryRecordsStep); + } + + @Bean + public Job edifactClaimsExportJob(EdiExportJobCompletionListener ediExportJobCompletionListener, JobRepository jobRepository, + Step mapToEdifactClaimsStep, Step saveToFTPStep, Step saveToMinIOStep, Step createExportHistoryRecordsStep) { + return constructEdifactExportJob(new JobBuilder(ExportType.CLAIMS.getValue(), jobRepository), + ediExportJobCompletionListener, mapToEdifactClaimsStep, saveToFTPStep, saveToMinIOStep, createExportHistoryRecordsStep); + } + + @Bean + public Step mapToEdifactOrdersStep(MapToEdifactOrdersTasklet mapToEdifactOrdersTasklet, JobRepository jobRepository, + PlatformTransactionManager transactionManager) { + return new StepBuilder("mapToEdifactStep", jobRepository) + .tasklet(mapToEdifactOrdersTasklet, transactionManager) + .build(); + } + + @Bean + public Step mapToEdifactClaimsStep(MapToEdifactClaimsTasklet mapToEdifactClaimsTasklet, JobRepository jobRepository, + PlatformTransactionManager transactionManager) { return new StepBuilder("mapToEdifactStep", jobRepository) - .tasklet(mapToEdifactTasklet, transactionManager) + .tasklet(mapToEdifactClaimsTasklet, transactionManager) .build(); } diff --git a/src/main/java/org/folio/dew/batch/acquisitions/edifact/jobs/ExportHistoryTasklet.java b/src/main/java/org/folio/dew/batch/acquisitions/edifact/jobs/ExportHistoryTasklet.java index ca668b25c..2035f2bd6 100644 --- a/src/main/java/org/folio/dew/batch/acquisitions/edifact/jobs/ExportHistoryTasklet.java +++ b/src/main/java/org/folio/dew/batch/acquisitions/edifact/jobs/ExportHistoryTasklet.java @@ -1,5 +1,6 @@ package org.folio.dew.batch.acquisitions.edifact.jobs; +import static org.folio.dew.batch.acquisitions.edifact.jobs.EdifactExportJobConfig.POL_MEM_KEY; import static org.folio.dew.domain.dto.JobParameterNames.EDIFACT_FILE_NAME; import static org.folio.dew.domain.dto.JobParameterNames.EDIFACT_ORDERS_EXPORT; @@ -77,7 +78,7 @@ ExportHistory buildExportHistory(ChunkContext chunkContext) { List getPoLineIdsFromExecutionContext(StepExecution stepExecutionContext) { try { - return ediObjectMapper.readValue((String) ExecutionContextUtils.getExecutionVariable(stepExecutionContext, "polineIds"), new TypeReference<>() {}); + return ediObjectMapper.readValue((String) ExecutionContextUtils.getExecutionVariable(stepExecutionContext, POL_MEM_KEY), new TypeReference<>() {}); } catch (Exception e) { return Collections.emptyList(); } diff --git a/src/main/java/org/folio/dew/batch/acquisitions/edifact/jobs/MapToEdifactClaimsTasklet.java b/src/main/java/org/folio/dew/batch/acquisitions/edifact/jobs/MapToEdifactClaimsTasklet.java new file mode 100644 index 000000000..a45ffa5f1 --- /dev/null +++ b/src/main/java/org/folio/dew/batch/acquisitions/edifact/jobs/MapToEdifactClaimsTasklet.java @@ -0,0 +1,50 @@ +package org.folio.dew.batch.acquisitions.edifact.jobs; + +import static org.folio.dew.utils.QueryUtils.convertIdsToCqlQuery; + +import java.util.List; +import java.util.Map; + +import org.apache.commons.collections4.CollectionUtils; +import org.folio.dew.batch.acquisitions.edifact.PurchaseOrdersToEdifactMapper; +import org.folio.dew.batch.acquisitions.edifact.services.OrdersService; +import org.folio.dew.domain.dto.Piece; +import org.folio.dew.domain.dto.VendorEdiOrdersExportConfig; +import org.folio.dew.domain.dto.acquisitions.edifact.EdifactExportHolder; +import org.folio.dew.error.NotFoundException; +import org.springframework.batch.core.configuration.annotation.StepScope; +import org.springframework.batch.core.scope.context.ChunkContext; +import org.springframework.stereotype.Component; + +import com.fasterxml.jackson.databind.ObjectMapper; + +@Component +@StepScope +public class MapToEdifactClaimsTasklet extends MapToEdifactTasklet { + + public static final String CLAIM_PIECE_IDS = "claimPieceIds"; + + public MapToEdifactClaimsTasklet(ObjectMapper ediObjectMapper, OrdersService ordersService, + PurchaseOrdersToEdifactMapper purchaseOrdersToEdifactMapper) { + super(ediObjectMapper, ordersService, purchaseOrdersToEdifactMapper); + } + + protected List getExportConfigMissingFields(VendorEdiOrdersExportConfig ediOrdersExportConfig) { + return CollectionUtils.isEmpty(ediOrdersExportConfig.getClaimPieceIds()) + ? List.of(CLAIM_PIECE_IDS) + : List.of(); + } + + @Override + protected EdifactExportHolder buildEdifactExportHolder(ChunkContext chunkContext, VendorEdiOrdersExportConfig ediExportConfig, Map jobParameters) { + var pieces = ordersService.getPiecesByIdsAndReceivingStatus(ediExportConfig.getClaimPieceIds(), Piece.ReceivingStatusEnum.LATE); + if (pieces.isEmpty()) { + throw new NotFoundException(Piece.class); + } + + var poLineQuery = convertIdsToCqlQuery(pieces.stream().map(Piece::getPoLineId).toList()); + var compOrders = getCompositeOrders(poLineQuery); + return new EdifactExportHolder(compOrders, pieces); + } + +} diff --git a/src/main/java/org/folio/dew/batch/acquisitions/edifact/jobs/MapToEdifactOrdersTasklet.java b/src/main/java/org/folio/dew/batch/acquisitions/edifact/jobs/MapToEdifactOrdersTasklet.java new file mode 100644 index 000000000..f78df6a0b --- /dev/null +++ b/src/main/java/org/folio/dew/batch/acquisitions/edifact/jobs/MapToEdifactOrdersTasklet.java @@ -0,0 +1,95 @@ +package org.folio.dew.batch.acquisitions.edifact.jobs; + +import static org.folio.dew.utils.QueryUtils.combineCqlExpressions; +import static org.folio.dew.utils.QueryUtils.convertFieldListToEnclosedCqlQuery; +import static org.folio.dew.utils.QueryUtils.getCqlExpressionForFieldNullValue; +import static org.folio.dew.utils.QueryUtils.negateQuery; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import org.apache.commons.collections4.CollectionUtils; +import org.folio.dew.batch.acquisitions.edifact.PurchaseOrdersToEdifactMapper; +import org.folio.dew.batch.acquisitions.edifact.services.OrdersService; +import org.folio.dew.client.DataExportSpringClient; +import org.folio.dew.domain.dto.ExportConfigCollection; +import org.folio.dew.domain.dto.ExportType; +import org.folio.dew.domain.dto.VendorEdiOrdersExportConfig; +import org.folio.dew.domain.dto.acquisitions.edifact.EdifactExportHolder; +import org.springframework.batch.core.configuration.annotation.StepScope; +import org.springframework.batch.core.scope.context.ChunkContext; +import org.springframework.stereotype.Component; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import lombok.extern.log4j.Log4j2; + +@Component +@StepScope +@Log4j2 +public class MapToEdifactOrdersTasklet extends MapToEdifactTasklet { + + private final DataExportSpringClient dataExportSpringClient; + + public MapToEdifactOrdersTasklet(ObjectMapper ediObjectMapper, OrdersService ordersService, + DataExportSpringClient dataExportSpringClient, + PurchaseOrdersToEdifactMapper purchaseOrdersToEdifactMapper) { + super(ediObjectMapper, ordersService, purchaseOrdersToEdifactMapper); + this.dataExportSpringClient = dataExportSpringClient; + } + + protected List getExportConfigMissingFields(VendorEdiOrdersExportConfig ediOrdersExportConfig) { + return List.of(); + } + + @Override + protected EdifactExportHolder buildEdifactExportHolder(ChunkContext chunkContext, VendorEdiOrdersExportConfig ediExportConfig, Map jobParameters) { + var poLineQuery = getPoLineQuery(ediExportConfig); + var compOrders = getCompositeOrders(poLineQuery); + return new EdifactExportHolder(compOrders, List.of()); + } + + protected String getPoLineQuery(VendorEdiOrdersExportConfig ediConfig) { + var acqMethods = ediConfig.getEdiConfig().getDefaultAcquisitionMethods(); + var resultQuery = combineCqlExpressions("AND", + // Order filters + "purchaseOrder.workflowStatus==Open", // order status is Open + "purchaseOrder.vendor==%s".formatted(ediConfig.getVendorId()), // vendor id matches + negateQuery("purchaseOrder.manualPo==true"), // not a manual order + + // Order line filters + "automaticExport==true", // line with automatic export + getCqlExpressionForFieldNullValue("lastEDIExportDate"), // has not been exported yet + convertFieldListToEnclosedCqlQuery(acqMethods, "acquisitionMethod", true), // acquisitionMethod in default list + getVendorAccountFilter(ediConfig) // vendor account no filter + ); + log.info("getPoLineQuery:: Fetching purchase order lines with query: {}", resultQuery); + return resultQuery; + } + + private String getVendorAccountFilter(VendorEdiOrdersExportConfig ediConfig) { + if (Boolean.TRUE.equals(ediConfig.getIsDefaultConfig())) { + var configQuery = "configName==%s_%s*".formatted(ExportType.EDIFACT_ORDERS_EXPORT, ediConfig.getVendorId()); + var configs = dataExportSpringClient.getExportConfigs(configQuery); + return configs.getTotalRecords() > 1 + ? negateQuery(convertFieldListToEnclosedCqlQuery(extractAllAccountNoLists(configs), "vendorDetail.vendorAccount", true)) + : ""; + } + return convertFieldListToEnclosedCqlQuery(ediConfig.getEdiConfig().getAccountNoList(), "vendorDetail.vendorAccount", true); + } + + private Set extractAllAccountNoLists(ExportConfigCollection configs) { + return configs.getConfigs().stream() + .map(config -> config.getExportTypeSpecificParameters().getVendorEdiOrdersExportConfig().getEdiConfig()) + .map(config -> Objects.nonNull(config) && CollectionUtils.isNotEmpty(config.getAccountNoList()) + ? config.getAccountNoList() + : List.of()) + .flatMap(Collection::stream) + .collect(Collectors.toSet()); + } + +} diff --git a/src/main/java/org/folio/dew/batch/acquisitions/edifact/jobs/MapToEdifactTasklet.java b/src/main/java/org/folio/dew/batch/acquisitions/edifact/jobs/MapToEdifactTasklet.java index 04695c88a..d5fcd6b16 100644 --- a/src/main/java/org/folio/dew/batch/acquisitions/edifact/jobs/MapToEdifactTasklet.java +++ b/src/main/java/org/folio/dew/batch/acquisitions/edifact/jobs/MapToEdifactTasklet.java @@ -2,75 +2,62 @@ import static java.util.Objects.requireNonNullElse; import static java.util.stream.Collectors.groupingBy; +import static org.folio.dew.batch.acquisitions.edifact.jobs.EdifactExportJobConfig.POL_MEM_KEY; import static org.folio.dew.domain.dto.JobParameterNames.EDIFACT_ORDERS_EXPORT; -import java.util.Collection; -import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; -import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.folio.dew.batch.ExecutionContextUtils; import org.folio.dew.batch.acquisitions.edifact.PurchaseOrdersToEdifactMapper; import org.folio.dew.batch.acquisitions.edifact.exceptions.CompositeOrderMappingException; import org.folio.dew.batch.acquisitions.edifact.exceptions.EdifactException; -import org.folio.dew.batch.acquisitions.edifact.exceptions.OrderNotFoundException; import org.folio.dew.batch.acquisitions.edifact.services.OrdersService; -import org.folio.dew.client.DataExportSpringClient; import org.folio.dew.domain.dto.CompositePoLine; import org.folio.dew.domain.dto.CompositePurchaseOrder; import org.folio.dew.domain.dto.JobParameterNames; -import org.folio.dew.domain.dto.EdiConfig; -import org.folio.dew.domain.dto.ExportConfig; -import org.folio.dew.domain.dto.ExportConfigCollection; -import org.folio.dew.domain.dto.ExportType; import org.folio.dew.domain.dto.PoLine; import org.folio.dew.domain.dto.PurchaseOrder; import org.folio.dew.domain.dto.VendorEdiOrdersExportConfig; +import org.folio.dew.domain.dto.acquisitions.edifact.EdifactExportHolder; +import org.folio.dew.error.NotFoundException; import org.springframework.batch.core.StepContribution; -import org.springframework.batch.core.configuration.annotation.StepScope; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.core.step.tasklet.Tasklet; import org.springframework.batch.repeat.RepeatStatus; -import org.springframework.stereotype.Component; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import io.xlate.edi.stream.EDIStreamException; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; @RequiredArgsConstructor -@Component -@StepScope @Log4j2 -public class MapToEdifactTasklet implements Tasklet { - private final ObjectMapper ediObjectMapper; +public abstract class MapToEdifactTasklet implements Tasklet { - private final OrdersService ordersService; - private final DataExportSpringClient dataExportSpringClient; + private final ObjectMapper ediObjectMapper; + protected final OrdersService ordersService; private final PurchaseOrdersToEdifactMapper purchaseOrdersToEdifactMapper; @Override public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { - log.info("Execute MapToEdifactTasklet"); + log.info("execute:: Executing MapToEdifactTasklet with job: {}", chunkContext.getStepContext().getJobName()); var jobParameters = chunkContext.getStepContext().getJobParameters(); var ediExportConfig = ediObjectMapper.readValue((String)jobParameters.get(EDIFACT_ORDERS_EXPORT), VendorEdiOrdersExportConfig.class); validateEdiExportConfig(ediExportConfig); - List compOrders = getCompPOList(ediExportConfig); - // save poLineIds in memory - persistPoLineIds(chunkContext, compOrders); + var holder = buildEdifactExportHolder(chunkContext, ediExportConfig, jobParameters); + persistPoLineIds(chunkContext, holder.orders()); String jobName = jobParameters.get(JobParameterNames.JOB_NAME).toString(); - String edifactOrderAsString = purchaseOrdersToEdifactMapper.convertOrdersToEdifact(compOrders, ediExportConfig, jobName); + var edifactStringResult = purchaseOrdersToEdifactMapper.convertOrdersToEdifact(holder.orders(), holder.pieces(), ediExportConfig, jobName); + // save edifact file content in memory - ExecutionContextUtils.addToJobExecutionContext(chunkContext.getStepContext().getStepExecution(), "edifactOrderAsString", edifactOrderAsString, ""); + ExecutionContextUtils.addToJobExecutionContext(chunkContext.getStepContext().getStepExecution(), "edifactOrderAsString", edifactStringResult, ""); return RepeatStatus.FINISHED; } @@ -86,93 +73,34 @@ private void validateEdiExportConfig(VendorEdiOrdersExportConfig ediExportConfig if (port.isEmpty()) { throw new EdifactException("Export configuration is incomplete, missing FTP/SFTP Port"); } + + var missingFields = getExportConfigMissingFields(ediExportConfig); + if (!missingFields.isEmpty()) { + throw new EdifactException("Export configuration is incomplete, missing required fields: %s".formatted(missingFields)); + } } - private List getCompPOList(VendorEdiOrdersExportConfig ediConfig) { - var poLineQuery = buildPoLineQuery(ediConfig); +protected List getCompositeOrders(String poLineQuery) { var poLines = ordersService.getPoLinesByQuery(poLineQuery); - var orderIds = poLines.stream() .map(PoLine::getPurchaseOrderId) .distinct() .toList(); - var orders = ordersService.getPurchaseOrdersByIds(orderIds); - - var compOrders = assembleCompositeOrders(orders, poLines); - - log.debug("composite purchase orders: {}", compOrders); + var compOrders = assembleCompositeOrders(ordersService.getPurchaseOrdersByIds(orderIds), poLines); + log.debug("getCompositeOrders:: {}", compOrders); if (compOrders.isEmpty()) { - throw new OrderNotFoundException("Orders for export not found", false); + throw new NotFoundException(PurchaseOrder.class); } return compOrders; } - private String buildPoLineQuery(VendorEdiOrdersExportConfig ediConfig) { - // Order filters - var workflowStatusFilter = "purchaseOrder.workflowStatus==Open"; // order status is Open - var vendorFilter = String.format(" AND purchaseOrder.vendor==%s", ediConfig.getVendorId()); // vendor id matches - var notManualFilter = " AND (cql.allRecords=1 NOT purchaseOrder.manualPo==true)"; // not a manual order - - // Order line filters - var automaticExportFilter = " AND automaticExport==true"; // line with automatic export - var ediExportDateFilter = " AND (cql.allRecords=1 NOT lastEDIExportDate=\"\")"; // has not been exported yet - var acqMethodsFilter = fieldInListFilter("acquisitionMethod", - ediConfig.getEdiConfig().getDefaultAcquisitionMethods()); // acquisitionMethod in default list - String vendorAccountFilter = ""; - if (Boolean.TRUE.equals(ediConfig.getIsDefaultConfig())) { - var configQuery = String.format("configName==%s_%s*", ExportType.EDIFACT_ORDERS_EXPORT, ediConfig.getVendorId()); - var configs = dataExportSpringClient.getExportConfigs(configQuery); - if (configs.getTotalRecords() > 1) { - var accountNoSetForExclude = getAccountNoSet(configs); - vendorAccountFilter = fieldNotInListFilter("vendorDetail.vendorAccount", accountNoSetForExclude); - } - } else { - // vendorAccount in the config account number list - vendorAccountFilter = fieldInListFilter("vendorDetail.vendorAccount", - ediConfig.getEdiConfig().getAccountNoList()); - } - - var resultQuery = String.format("%s%s%s%s%s%s%s", workflowStatusFilter, vendorFilter, notManualFilter, - automaticExportFilter, ediExportDateFilter, acqMethodsFilter, vendorAccountFilter); - log.info("GET purchase order line query: {}", resultQuery); - return resultQuery; - } - - private Set getAccountNoSet(ExportConfigCollection configs) { - Set accountNoSet = new HashSet<>(); - for (ExportConfig exportConfig : configs.getConfigs()) { - EdiConfig ediConfig = exportConfig.getExportTypeSpecificParameters().getVendorEdiOrdersExportConfig().getEdiConfig(); - if (Objects.nonNull(ediConfig)) { - List currentAccountNoList = ediConfig.getAccountNoList(); - if (CollectionUtils.isNotEmpty(currentAccountNoList)) { - accountNoSet.addAll(currentAccountNoList); - } - } - } - return accountNoSet; - } - - private void persistPoLineIds(ChunkContext chunkContext, List compOrders) throws JsonProcessingException { - var polineIds = compOrders.stream() + protected void persistPoLineIds(ChunkContext chunkContext, List compOrders) throws JsonProcessingException { + var poLineIds = compOrders.stream() .flatMap(ord -> ord.getCompositePoLines().stream()) .map(CompositePoLine::getId) .toList(); - ExecutionContextUtils.addToJobExecutionContext(chunkContext.getStepContext().getStepExecution(),"polineIds", ediObjectMapper.writeValueAsString(polineIds),""); - } - - private String fieldInListFilter(String fieldName, List list) { - return String.format(" AND %s==%s", fieldName, - list.stream() - .map(item -> String.format("\"%s\"", item.toString())) - .collect(Collectors.joining(" OR ", "(", ")"))); - } - - private static String fieldNotInListFilter(String fieldName, Collection list) { - return String.format(" AND cql.allRecords=1 NOT %s==%s", fieldName, - list.stream() - .map(item -> String.format("\"%s\"", item.toString())) - .collect(Collectors.joining(" OR ", "(", ")"))); + ExecutionContextUtils.addToJobExecutionContext(chunkContext.getStepContext().getStepExecution(), POL_MEM_KEY, ediObjectMapper.writeValueAsString(poLineIds),""); } private List assembleCompositeOrders(List orders, List poLines) { @@ -193,4 +121,10 @@ private T convertTo(Object value, Class c) { throw new CompositeOrderMappingException(String.format("%s for object %s", ex.getMessage(), value)); } } + + protected abstract List getExportConfigMissingFields(VendorEdiOrdersExportConfig ediOrdersExportConfig); + + protected abstract EdifactExportHolder buildEdifactExportHolder(ChunkContext chunkContext, VendorEdiOrdersExportConfig ediExportConfig, + Map jobParameters) throws JsonProcessingException, EDIStreamException; + } diff --git a/src/main/java/org/folio/dew/batch/acquisitions/edifact/services/OrdersService.java b/src/main/java/org/folio/dew/batch/acquisitions/edifact/services/OrdersService.java index 1ee319f78..fe0ba2d2f 100644 --- a/src/main/java/org/folio/dew/batch/acquisitions/edifact/services/OrdersService.java +++ b/src/main/java/org/folio/dew/batch/acquisitions/edifact/services/OrdersService.java @@ -1,28 +1,35 @@ package org.folio.dew.batch.acquisitions.edifact.services; +import static org.folio.dew.utils.QueryUtils.combineCqlExpressions; +import static org.folio.dew.utils.QueryUtils.convertIdsToCqlQuery; + import org.folio.dew.client.OrdersStorageClient; +import org.folio.dew.domain.dto.Piece; import org.folio.dew.domain.dto.PoLine; import org.folio.dew.domain.dto.PurchaseOrder; +import org.folio.dew.utils.QueryUtils; import org.springframework.stereotype.Service; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; +import one.util.streamex.StreamEx; import java.util.ArrayList; -import java.util.Collection; import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.IntStream; @Service @RequiredArgsConstructor @Log4j2 public class OrdersService { + private static final int CHUNK_SIZE = 50; + private static final String PIECES_BY_REC_STATUS_QUERY = "receivingStatus==%s"; + + private final OrdersStorageClient ordersStorageClient; public List getPoLinesByQuery(String query) { - log.debug("OrdersService.getPoLinesByQuery: {}", query); + log.debug("getPoLinesByQuery:: Fetching PoLines by query: {}", query); var allLines = new ArrayList(); String lastId = null; while (true) { @@ -37,27 +44,31 @@ public List getPoLinesByQuery(String query) { allLines.addAll(linesInChunk); lastId = linesInChunk.get(linesInChunk.size() - 1).getId(); } - log.debug("OrdersService.getPoLinesByQuery: returned {} lines", allLines.size()); + log.debug("getPoLinesByQuery:: Fetched {} PoLines", allLines.size()); return allLines; } public List getPurchaseOrdersByIds(List orderIds) { - log.debug("OrdersService.getPurchaseOrdersByIds: {}", orderIds); - List orders = new ArrayList<>(); - Collection> idChunks = partitionUsingChunkSize(orderIds); - for (List idChunk : idChunks) { - String query = idChunk.stream().collect(Collectors.joining(" OR ", "id==(", ")")); - orders.addAll(ordersStorageClient.getPurchaseOrdersByQuery(query, 0, Integer.MAX_VALUE).getPurchaseOrders()); - } - log.debug("OrdersService.getPurchaseOrdersByIds: returned {} orders", orders.size()); + log.debug("getPurchaseOrdersByIds: Fetching orders: {}", orderIds); + var orders = StreamEx.ofSubLists(orderIds, CHUNK_SIZE) + .map(QueryUtils::convertIdsToCqlQuery) + .map(query -> ordersStorageClient.getPurchaseOrdersByQuery(query, 0, Integer.MAX_VALUE)) + .flatMap(collection -> collection.getPurchaseOrders().stream()) + .toList(); + log.debug("getPurchaseOrdersByIds:: Fetched {} orders", orders.size()); return orders; } - private Collection> partitionUsingChunkSize(List inputList) { - return IntStream.range(0, inputList.size()) - .boxed() - .collect(Collectors.groupingBy(partition -> partition / CHUNK_SIZE, - Collectors.mapping(inputList::get, Collectors.toList()))) - .values(); + public List getPiecesByIdsAndReceivingStatus(List pieceIds, Piece.ReceivingStatusEnum receivingStatus) { + log.debug("getPiecesByIdsAndReceivingStatus:: Fetching pieces: {} by status: {}", pieceIds, receivingStatus); + var receivingStatusQuery = PIECES_BY_REC_STATUS_QUERY.formatted(receivingStatus.getValue()); + var pieces = StreamEx.ofSubLists(pieceIds, CHUNK_SIZE) + .map(ids -> combineCqlExpressions("and", convertIdsToCqlQuery(ids), receivingStatusQuery)) + .map(query -> ordersStorageClient.getPiecesByQuery(query, 0, Integer.MAX_VALUE)) + .flatMap(collection -> collection.getPieces().stream()) + .toList(); + log.debug("getPiecesByIdsAndReceivingStatus:: Fetched {} pieces", pieces.size()); + return pieces; } + } diff --git a/src/main/java/org/folio/dew/client/OrdersStorageClient.java b/src/main/java/org/folio/dew/client/OrdersStorageClient.java index 2439d2688..a77edbf96 100644 --- a/src/main/java/org/folio/dew/client/OrdersStorageClient.java +++ b/src/main/java/org/folio/dew/client/OrdersStorageClient.java @@ -1,5 +1,6 @@ package org.folio.dew.client; +import org.folio.dew.domain.dto.PieceCollection; import org.folio.dew.domain.dto.PoLineCollection; import org.folio.dew.domain.dto.PurchaseOrderCollection; import org.springframework.cloud.openfeign.FeignClient; @@ -24,4 +25,11 @@ PoLineCollection getPoLinesByQuery( @RequestParam("limit") int limit ); + @GetMapping(value = "/pieces", produces = MediaType.APPLICATION_JSON_VALUE) + PieceCollection getPiecesByQuery( + @RequestParam("query") String query, + @RequestParam("offset") int offset, + @RequestParam("limit") int limit + ); + } diff --git a/src/main/java/org/folio/dew/domain/dto/acquisitions/edifact/EdifactExportHolder.java b/src/main/java/org/folio/dew/domain/dto/acquisitions/edifact/EdifactExportHolder.java new file mode 100644 index 000000000..8108aed51 --- /dev/null +++ b/src/main/java/org/folio/dew/domain/dto/acquisitions/edifact/EdifactExportHolder.java @@ -0,0 +1,10 @@ +package org.folio.dew.domain.dto.acquisitions.edifact; + +import java.util.List; + +import org.folio.dew.domain.dto.CompositePurchaseOrder; +import org.folio.dew.domain.dto.Piece; + +public record EdifactExportHolder(List orders, List pieces) { + +} diff --git a/src/main/java/org/folio/dew/error/NotFoundException.java b/src/main/java/org/folio/dew/error/NotFoundException.java index d8bf36e6c..8317c2648 100644 --- a/src/main/java/org/folio/dew/error/NotFoundException.java +++ b/src/main/java/org/folio/dew/error/NotFoundException.java @@ -1,7 +1,15 @@ package org.folio.dew.error; public class NotFoundException extends RuntimeException { + + private static final String EXCEPTION_MESSAGE = "Entities not found: %s"; + + public NotFoundException(Class entityClass) { + super(EXCEPTION_MESSAGE.formatted(entityClass.getSimpleName()), null, false, false); + } + public NotFoundException(String message) { super(message); } + } diff --git a/src/main/java/org/folio/dew/utils/QueryUtils.java b/src/main/java/org/folio/dew/utils/QueryUtils.java new file mode 100644 index 000000000..17c7f8576 --- /dev/null +++ b/src/main/java/org/folio/dew/utils/QueryUtils.java @@ -0,0 +1,119 @@ +package org.folio.dew.utils; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Collection; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; + +import one.util.streamex.StreamEx; + +public class QueryUtils { + + public static final String ID = "id"; + private static final String CQL_COMBINE_OPERATOR = ") %s ("; + private static final String CQL_MATCH_STRICT = "%s==%s"; + private static final String CQL_MATCH = "%s=%s"; + private static final String CQL_PREFIX = "("; + private static final String CQL_SUFFIX = ")"; + private static final String CQL_NEGATE_PREFIX = "cql.allRecords=1 NOT "; + private static final String CQL_UNDEFINED_FIELD_EXPRESSION = CQL_NEGATE_PREFIX + "%s=\"\""; + private static final Pattern CQL_SORT_BY_PATTERN = Pattern.compile("(.*)(\\ssortBy\\s.*)", Pattern.CASE_INSENSITIVE); //NOSONAR + + private QueryUtils() {} + + public static String encodeQuery(String query) { + return URLEncoder.encode(query, StandardCharsets.UTF_8); + } + + /** + * Combines multiple CQL expressions using the specified logical operator. For example:
+ * Call: combineCqlExpressions("and", "field1==value1", "field2==value2")
+ * Result: (field1==value1) and (field2==value2) + * + * @param operator The logical operator to combine the expressions (e.g., "and", "or"). + * @param expressions The CQL expressions to combine. + * @return A single CQL query string combining the provided expressions with the specified operator. + */ + public static String combineCqlExpressions(String operator, String... expressions) { + if (ArrayUtils.isEmpty(expressions)) { + return StringUtils.EMPTY; + } + var sorting = StringUtils.EMPTY; + // Check whether last expression contains sorting query. If it does, extract it to be added in the end of the resulting query + Matcher matcher = CQL_SORT_BY_PATTERN.matcher(expressions[expressions.length - 1]); + if (matcher.find()) { + expressions[expressions.length - 1] = matcher.group(1); + sorting = matcher.group(2); + } + + var suffix = CQL_SUFFIX + sorting; + var delimiter = String.format(CQL_COMBINE_OPERATOR, operator); + return StreamEx.of(expressions) + .filter(StringUtils::isNotBlank) + .joining(delimiter, CQL_PREFIX, suffix); + } + + /** + * Converts a collection of IDs to a CQL query string using the specified ID field. + * + * @param ids The collection of IDs to be converted. + * @param idField The field name to be used in the CQL query. + * @return A CQL query string representing the IDs. + */ + public static String convertIdsToCqlQuery(Collection ids, String idField) { + return convertFieldListToCqlQuery(ids, idField, true, false); + } + + /** + * Converts a collection of IDs to a CQL query string using the default ID field. + * + * @param ids The collection of IDs to be converted. + * @return A CQL query string representing the IDs. + */ + public static String convertIdsToCqlQuery(Collection ids) { + return convertFieldListToCqlQuery(ids, ID, true, false); + } + + /** + * Transform list of values for some property to CQL query using 'or' operation and enclosing values with quotes + * + * @param values list of field values + * @param fieldName the property name to search by + * @param strictMatch indicates whether strict match mode (i.e. ==) should be used or not (i.e. =) + * @return String representing CQL query to get records by some property enclosed values + */ + public static String convertFieldListToEnclosedCqlQuery(Collection values, String fieldName, boolean strictMatch) { + return convertFieldListToCqlQuery(values, fieldName, strictMatch, true); + } + + /** + * Transform list of values for some property to CQL query using 'or' operation + * + * @param values list of field values + * @param fieldName the property name to search by + * @param strictMatch indicates whether strict match mode (i.e. ==) should be used or not (i.e. =) + * @param enclosed indicates whether values should be enclosed with quotes (i.e. asd) or not (i.e. "asd") + * @return String representing CQL query to get records by some property values + */ + public static String convertFieldListToCqlQuery(Collection values, String fieldName, boolean strictMatch, boolean enclosed) { + var prefix = String.format(strictMatch ? CQL_MATCH_STRICT : CQL_MATCH, fieldName, CQL_PREFIX); + var enclose = enclosed ? "\"%s\"" : "%s"; + return StreamEx.of(values) + .map(Object::toString) + .map(enclose::formatted) + .joining(" or ", prefix, CQL_SUFFIX); + } + + public static String negateQuery(String cql) { + return CQL_NEGATE_PREFIX + cql; + } + + public static String getCqlExpressionForFieldNullValue(String fieldName) { + return String.format(CQL_UNDEFINED_FIELD_EXPRESSION, fieldName); + } + +} diff --git a/src/main/resources/swagger.api/order-export.yaml b/src/main/resources/swagger.api/order-export.yaml index c87821b23..31ee4e1fa 100644 --- a/src/main/resources/swagger.api/order-export.yaml +++ b/src/main/resources/swagger.api/order-export.yaml @@ -61,3 +61,7 @@ components: $ref: '../../../../folio-export-common/schemas/acquisitions/mod-orders-storage/purchase_order.json#/PurchaseOrder' purchaseOrderCollection: $ref: '../../../../folio-export-common/schemas/acquisitions/mod-orders-storage/purchase_order_collection.json#/PurchaseOrderCollection' + piece: + $ref: '../../../../folio-export-common/schemas/acquisitions/mod-orders-storage/piece.json#/Piece' + pieceCollection: + $ref: '../../../../folio-export-common/schemas/acquisitions/mod-orders-storage/piece_collection.json#/PieceCollection' diff --git a/src/test/java/org/folio/dew/BaseBatchTest.java b/src/test/java/org/folio/dew/BaseBatchTest.java index cbd47e5cc..4c4e9016e 100644 --- a/src/test/java/org/folio/dew/BaseBatchTest.java +++ b/src/test/java/org/folio/dew/BaseBatchTest.java @@ -175,7 +175,7 @@ public static HttpHeaders defaultHeaders() { } @BeforeEach - void setUp() { + protected void setUp() { okapiHeaders = new LinkedHashMap<>(); okapiHeaders.put(XOkapiHeaders.TENANT, TENANT); okapiHeaders.put(XOkapiHeaders.TOKEN, TOKEN); diff --git a/src/test/java/org/folio/dew/batch/acquisitions/edifact/MappingOrdersToEdifactTest.java b/src/test/java/org/folio/dew/batch/acquisitions/edifact/MappingOrdersToEdifactTest.java index d8f1cec5c..2d92ef9ee 100644 --- a/src/test/java/org/folio/dew/batch/acquisitions/edifact/MappingOrdersToEdifactTest.java +++ b/src/test/java/org/folio/dew/batch/acquisitions/edifact/MappingOrdersToEdifactTest.java @@ -1,5 +1,7 @@ package org.folio.dew.batch.acquisitions.edifact; +import static org.folio.dew.domain.dto.ExportType.CLAIMS; +import static org.folio.dew.domain.dto.ExportType.EDIFACT_ORDERS_EXPORT; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -16,101 +18,133 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.stream.Stream; import lombok.extern.log4j.Log4j2; import org.apache.commons.io.IOUtils; -import org.folio.dew.BaseBatchTest; import org.folio.dew.batch.acquisitions.edifact.services.ConfigurationService; import org.folio.dew.batch.acquisitions.edifact.services.ExpenseClassService; import org.folio.dew.batch.acquisitions.edifact.services.HoldingService; import org.folio.dew.batch.acquisitions.edifact.services.IdentifierTypeService; import org.folio.dew.batch.acquisitions.edifact.services.LocationService; import org.folio.dew.batch.acquisitions.edifact.services.MaterialTypeService; +import org.folio.dew.config.JacksonConfiguration; import org.folio.dew.domain.dto.CompositePurchaseOrder; +import org.folio.dew.domain.dto.ExportType; +import org.folio.dew.domain.dto.Piece; +import org.folio.dew.domain.dto.PieceCollection; import org.folio.dew.domain.dto.VendorEdiOrdersExportConfig; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.mock.mockito.MockBean; @Log4j2 @ExtendWith(MockitoExtension.class) -class MappingOrdersToEdifactTest extends BaseBatchTest { - @Autowired +class MappingOrdersToEdifactTest { + + private static final Map EXPORT_EDI_PATHS = Map.of( + EDIFACT_ORDERS_EXPORT,"edifact/acquisitions/edifact_orders_result.edi", + CLAIMS, "edifact/acquisitions/edifact_claims_result.edi" + ); + private ObjectMapper objectMapper; - @Autowired private PurchaseOrdersToEdifactMapper purchaseOrdersToEdifactMapper; - @MockBean + + @Mock private IdentifierTypeService identifierTypeService; - @MockBean + @Mock private MaterialTypeService materialTypeService; - @MockBean + @Mock private ExpenseClassService expenseClassService; - @MockBean + @Mock private LocationService locationService; - @MockBean + @Mock private HoldingService holdingService; - @MockBean + @Mock private ConfigurationService configurationService; - @Test - void convertOrdersToEdifact() throws Exception { + @BeforeEach + void setUp() { + var compositePOLineConverter = new CompositePOLineConverter(identifierTypeService, materialTypeService, expenseClassService, locationService, holdingService); + var compositePOConverter = new CompositePOConverter(compositePOLineConverter, configurationService); + purchaseOrdersToEdifactMapper = new PurchaseOrdersToEdifactMapper(compositePOConverter); + objectMapper = new JacksonConfiguration().objectMapper(); + } + + @ParameterizedTest + @EnumSource(value = ExportType.class, names = {"EDIFACT_ORDERS_EXPORT", "CLAIMS"}) + void convertOrdersToEdifact(ExportType type) throws Exception { String jobName = "123456789012345"; String fileIdExpected = "23456789012345"; - List compPOs = getTestOrdersFromJson(); + List compPOs = getTestOrdersFromJson(type); + List pieces = getTestPiecesFromJson(type); serviceMocks(); - String ediOrder = purchaseOrdersToEdifactMapper.convertOrdersToEdifact(compPOs, getTestEdiConfig(), jobName); - log.info(ediOrder); + String ediOrder; + if (type == EDIFACT_ORDERS_EXPORT) { + ediOrder = purchaseOrdersToEdifactMapper.convertOrdersToEdifact(compPOs, getTestEdiConfig(), jobName); + } else { + var piecePoLineIds = pieces.stream().map(Piece::getPoLineId).toList(); + compPOs = compPOs.stream() + .peek(po -> po.getCompositePoLines().stream().filter(line -> piecePoLineIds.contains(line.getId())).toList()) + .filter(po -> !po.getCompositePoLines().isEmpty()) + .toList(); + ediOrder = purchaseOrdersToEdifactMapper.convertOrdersToEdifact(compPOs, pieces, getTestEdiConfig(), jobName); + } + assertFalse(ediOrder.isEmpty()); - validateEdifactOrders(ediOrder, fileIdExpected); + validateEdifactOrders(type, ediOrder, fileIdExpected); } - @Test - void convertOrdersToEdifactByteArray() throws Exception { + @ParameterizedTest + @EnumSource(value = ExportType.class, names = {"EDIFACT_ORDERS_EXPORT", "CLAIMS"}) + void convertOrdersToEdifactByteArray(ExportType type) throws Exception { String jobName = "12345"; - List compPOs = getTestOrdersFromJson(); + List compPOs = getTestOrdersFromJson(type); + List pieces = getTestPiecesFromJson(type); serviceMocks(); - byte[] ediOrder = purchaseOrdersToEdifactMapper.convertOrdersToEdifactArray(compPOs, getTestEdiConfig(), jobName); + byte[] ediOrder; + if (type == EDIFACT_ORDERS_EXPORT) { + ediOrder = purchaseOrdersToEdifactMapper.convertOrdersToEdifact(compPOs, getTestEdiConfig(), jobName).getBytes(StandardCharsets.UTF_8); + } else { + ediOrder = purchaseOrdersToEdifactMapper.convertOrdersToEdifact(compPOs, pieces, getTestEdiConfig(), jobName).getBytes(StandardCharsets.UTF_8); + } + assertNotNull(ediOrder); - String ediOrderString = new String(ediOrder); - log.info(ediOrderString); - validateEdifactOrders(ediOrderString, jobName); + validateEdifactOrders(type, new String(ediOrder), jobName); } private VendorEdiOrdersExportConfig getTestEdiConfig() throws IOException { return objectMapper.readValue(getMockData("edifact/acquisitions/vendorEdiOrdersExportConfig.json"), VendorEdiOrdersExportConfig.class); } - private List getTestOrdersFromJson() throws IOException { - - CompositePurchaseOrder compPo = objectMapper.readValue(getMockData("edifact/acquisitions/composite_purchase_order.json"), CompositePurchaseOrder.class); - - CompositePurchaseOrder comprehensiveCompPo = objectMapper.readValue(getMockData("edifact/acquisitions/comprehensive_composite_purchase_order.json"), CompositePurchaseOrder.class); - - CompositePurchaseOrder minimalisticCompPo = objectMapper.readValue(getMockData("edifact/acquisitions/minimalistic_composite_purchase_order.json"), CompositePurchaseOrder.class); - - CompositePurchaseOrder compPoWithEmptyVendorAccount = objectMapper.readValue(getMockData("edifact/acquisitions/purchase_order_empty_vendor_account.json"), CompositePurchaseOrder.class); - - CompositePurchaseOrder compPoWithNonEANProductIds = objectMapper.readValue(getMockData("edifact/acquisitions/purchase_order_non_ean_product_ids.json"), CompositePurchaseOrder.class); - - CompositePurchaseOrder compPoTitleWithEscapeChars = objectMapper.readValue(getMockData("edifact/acquisitions/purchase_order_title_with_escape_chars.json"), CompositePurchaseOrder.class); - + private List getTestOrdersFromJson(ExportType type) throws IOException { List compPOs = new ArrayList<>(); - compPOs.add(compPo); - compPOs.add(comprehensiveCompPo); - compPOs.add(minimalisticCompPo); - compPOs.add(compPoWithEmptyVendorAccount); - compPOs.add(compPoWithNonEANProductIds); - compPOs.add(compPoTitleWithEscapeChars); + compPOs.add(objectMapper.readValue(getMockData("edifact/acquisitions/composite_purchase_order.json"), CompositePurchaseOrder.class)); + compPOs.add(objectMapper.readValue(getMockData("edifact/acquisitions/comprehensive_composite_purchase_order.json"), CompositePurchaseOrder.class)); + compPOs.add(objectMapper.readValue(getMockData("edifact/acquisitions/minimalistic_composite_purchase_order.json"), CompositePurchaseOrder.class)); + if (type == EDIFACT_ORDERS_EXPORT) { + compPOs.add(objectMapper.readValue(getMockData("edifact/acquisitions/purchase_order_empty_vendor_account.json"), CompositePurchaseOrder.class)); + compPOs.add(objectMapper.readValue(getMockData("edifact/acquisitions/purchase_order_non_ean_product_ids.json"), CompositePurchaseOrder.class)); + compPOs.add(objectMapper.readValue(getMockData("edifact/acquisitions/purchase_order_title_with_escape_chars.json"), CompositePurchaseOrder.class)); + } return compPOs; } + + private List getTestPiecesFromJson(ExportType type) throws IOException { + return type == EDIFACT_ORDERS_EXPORT + ? List.of() + : objectMapper.readValue(getMockData("edifact/acquisitions/pieces_collection_mixed.json"), PieceCollection.class).getPieces(); + } + public static String getMockData(String path) throws IOException { try (InputStream resourceAsStream = MappingOrdersToEdifactTest.class.getClassLoader().getResourceAsStream(path)) { if (resourceAsStream != null) { @@ -142,10 +176,9 @@ private void serviceMocks() { .thenReturn("Bockenheimer Landstr. 134-13"); } - private void validateEdifactOrders(String ediOrder, String fileId) throws IOException { - String ediOrderExpected = getMockData("edifact/acquisitions/edifact_orders_result.edi") - .replaceAll("\\{fileId}", fileId); - + private void validateEdifactOrders(ExportType type, String ediOrder, String fileId) throws IOException { + log.info("Generated EDI file:\n{}", ediOrder); + String ediOrderExpected = getMockData(EXPORT_EDI_PATHS.get(type)).replaceAll("\\{fileId}", fileId); String ediOrderWithRemovedDateTime = ediOrder.replaceFirst("\\d{6}:\\d{4}\\+", "ddmmyy:hhmm+"); assertEquals(ediOrderExpected, ediOrderWithRemovedDateTime); } diff --git a/src/test/java/org/folio/dew/batch/acquisitions/edifact/OrderServiceTest.java b/src/test/java/org/folio/dew/batch/acquisitions/edifact/OrderServiceTest.java index 591bc41f5..a00df7761 100644 --- a/src/test/java/org/folio/dew/batch/acquisitions/edifact/OrderServiceTest.java +++ b/src/test/java/org/folio/dew/batch/acquisitions/edifact/OrderServiceTest.java @@ -70,12 +70,12 @@ void getPurchaseOrdersByIdsTest() { orderList1.add(order); query1Builder.append(id); if (i != 49) - query1Builder.append(" OR "); + query1Builder.append(" or "); } else { orderList2.add(order); query2Builder.append(id); if (i != 59) - query2Builder.append(" OR "); + query2Builder.append(" or "); } } query1Builder.append(")"); diff --git a/src/test/java/org/folio/dew/batch/acquisitions/edifact/jobs/ExportHistoryTaskletTest.java b/src/test/java/org/folio/dew/batch/acquisitions/edifact/jobs/ExportHistoryTaskletTest.java index 38578ac6d..db9a1e071 100644 --- a/src/test/java/org/folio/dew/batch/acquisitions/edifact/jobs/ExportHistoryTaskletTest.java +++ b/src/test/java/org/folio/dew/batch/acquisitions/edifact/jobs/ExportHistoryTaskletTest.java @@ -12,6 +12,7 @@ import org.springframework.batch.item.ExecutionContext; import org.springframework.batch.test.JobLauncherTestUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.annotation.DirtiesContext; @@ -26,6 +27,7 @@ class ExportHistoryTaskletTest extends BaseBatchTest { @Autowired + @Qualifier("edifactOrdersExportJob") private Job edifactExportJob; @MockBean private OrganizationsService organizationsService; diff --git a/src/test/java/org/folio/dew/batch/acquisitions/edifact/jobs/MapToEdifactClaimsTaskletTest.java b/src/test/java/org/folio/dew/batch/acquisitions/edifact/jobs/MapToEdifactClaimsTaskletTest.java new file mode 100644 index 000000000..5f05296cb --- /dev/null +++ b/src/test/java/org/folio/dew/batch/acquisitions/edifact/jobs/MapToEdifactClaimsTaskletTest.java @@ -0,0 +1,118 @@ +package org.folio.dew.batch.acquisitions.edifact.jobs; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.folio.dew.utils.QueryUtils.convertIdsToCqlQuery; +import static org.folio.dew.utils.TestUtils.getMockData; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.verify; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.folio.dew.domain.dto.Piece; +import org.folio.dew.domain.dto.PieceCollection; +import org.folio.dew.domain.dto.PoLine; +import org.folio.dew.domain.dto.PoLineCollection; +import org.folio.dew.domain.dto.PurchaseOrder; +import org.folio.dew.domain.dto.PurchaseOrderCollection; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.batch.core.BatchStatus; +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.test.JobLauncherTestUtils; +import org.springframework.beans.factory.annotation.Autowired; + +import com.fasterxml.jackson.databind.node.ObjectNode; + +import lombok.SneakyThrows; + +class MapToEdifactClaimsTaskletTest extends MapToEdifactTaskletAbstractTest { + + private static final String SAMPLE_PIECES_PATH = "edifact/acquisitions/pieces_collection.json"; + + @Autowired + Job edifactClaimsExportJob; + + private List orders; + private List poLines; + private List pieces; + private List pieceIds; + + @BeforeEach + @SneakyThrows + @Override + protected void setUp() { + super.setUp(); + edifactExportJob = edifactClaimsExportJob; + orders = objectMapper.readValue(getMockData(SAMPLE_PURCHASE_ORDERS_PATH), PurchaseOrderCollection.class).getPurchaseOrders(); + poLines = objectMapper.readValue(getMockData(SAMPLE_PO_LINES_PATH), PoLineCollection.class).getPoLines(); + pieces = objectMapper.readValue(getMockData(SAMPLE_PIECES_PATH), PieceCollection.class).getPieces(); + + pieceIds = pieces.stream().map(Piece::getId).toList(); + doReturn(pieces).when(ordersService).getPiecesByIdsAndReceivingStatus(pieceIds, Piece.ReceivingStatusEnum.LATE); + } + + @Test + void testEdifactClaimsExport() throws Exception { + JobLauncherTestUtils testLauncher = createTestLauncher(edifactExportJob); + String poLineQuery = convertIdsToCqlQuery(pieces.stream().map(Piece::getPoLineId).toList()); + + doReturn(poLines).when(ordersService).getPoLinesByQuery(poLineQuery); + doReturn(orders).when(ordersService).getPurchaseOrdersByIds(anyList()); + doReturn("test1").when(purchaseOrdersToEdifactMapper).convertOrdersToEdifact(any(), any(), anyString()); + + var exportConfig = getEdifactExportConfig(SAMPLE_EDI_ORDERS_EXPORT, pieceIds); + JobExecution jobExecution = testLauncher.launchStep(MAP_TO_EDIFACT_STEP, getJobParameters(exportConfig)); + + assertThat(jobExecution.getExitStatus()).isEqualTo(ExitStatus.COMPLETED); + verify(ordersService).getPiecesByIdsAndReceivingStatus(pieceIds, Piece.ReceivingStatusEnum.LATE); + verify(ordersService).getPoLinesByQuery(poLineQuery); + verify(ordersService).getPurchaseOrdersByIds(anyList()); + } + + @Test + void testEdifactClaimsExportNoPiecesFound() throws Exception { + JobLauncherTestUtils testLauncher = createTestLauncher(edifactExportJob); + doReturn(List.of()).when(ordersService).getPiecesByIdsAndReceivingStatus(pieceIds, Piece.ReceivingStatusEnum.LATE); + + var exportConfig = getEdifactExportConfig(SAMPLE_EDI_ORDERS_EXPORT, pieceIds); + JobExecution jobExecution = testLauncher.launchStep(MAP_TO_EDIFACT_STEP, getJobParameters(exportConfig)); + + assertThat(jobExecution.getExitStatus().getExitCode()).isEqualTo(ExitStatus.FAILED.getExitCode()); + assertThat(jobExecution.getExitStatus().getExitDescription()).contains("Entities not found: Piece"); + verify(ordersService).getPiecesByIdsAndReceivingStatus(pieceIds, Piece.ReceivingStatusEnum.LATE); + } + + @Test + void testEdifactClaimsExportMissingRequiredFields() throws Exception { + JobLauncherTestUtils testLauncher = createTestLauncher(edifactExportJob); + var exportConfig = getEdifactExportConfig(SAMPLE_EDI_ORDERS_EXPORT, List.of()); + + JobExecution jobExecution = testLauncher.launchStep(MAP_TO_EDIFACT_STEP, getJobParameters(exportConfig)); + var status = new ArrayList<>(jobExecution.getStepExecutions()).get(0).getStatus(); + + assertEquals(BatchStatus.FAILED, status); + assertThat(jobExecution.getExitStatus().getExitCode()).isEqualTo(ExitStatus.FAILED.getExitCode()); + assertThat(jobExecution.getExitStatus().getExitDescription()).contains("Export configuration is incomplete, missing required fields: [claimPieceIds]"); + } + + @Override + protected ObjectNode getEdifactExportConfig(String path) throws IOException { + return getEdifactExportConfig(path, pieceIds); + } + + protected ObjectNode getEdifactExportConfig(String path, List pieceIds) throws IOException { + var exportConfig = super.getEdifactExportConfig(path); + var arr = exportConfig.putArray("claimPieceIds"); + pieceIds.forEach(arr::add); + return exportConfig; + } + +} diff --git a/src/test/java/org/folio/dew/batch/acquisitions/edifact/jobs/MapToEdifactOrderTaskletTest.java b/src/test/java/org/folio/dew/batch/acquisitions/edifact/jobs/MapToEdifactOrderTaskletTest.java new file mode 100644 index 000000000..cbd9b14ba --- /dev/null +++ b/src/test/java/org/folio/dew/batch/acquisitions/edifact/jobs/MapToEdifactOrderTaskletTest.java @@ -0,0 +1,129 @@ +package org.folio.dew.batch.acquisitions.edifact.jobs; + +import static org.folio.dew.utils.TestUtils.getMockData; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.verify; + +import java.io.IOException; +import java.util.List; + +import org.assertj.core.api.Assertions; +import org.folio.dew.domain.dto.ExportConfigCollection; +import org.folio.dew.domain.dto.PoLine; +import org.folio.dew.domain.dto.PoLineCollection; +import org.folio.dew.domain.dto.PurchaseOrder; +import org.folio.dew.domain.dto.PurchaseOrderCollection; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.test.JobLauncherTestUtils; +import org.springframework.beans.factory.annotation.Autowired; + +import com.fasterxml.jackson.databind.node.ObjectNode; + +import lombok.SneakyThrows; + +class MapToEdifactOrderTaskletTest extends MapToEdifactTaskletAbstractTest { + + private static final String DATA_EXPORT_CONFIGS_PATH = "edifact/dataExportConfigs.json"; + + @Autowired + Job edifactOrdersExportJob; + + private List orders; + private List poLines; + + @BeforeEach + @SneakyThrows + @Override + protected void setUp() { + super.setUp(); + edifactExportJob = edifactOrdersExportJob; + orders = objectMapper.readValue(getMockData(SAMPLE_PURCHASE_ORDERS_PATH), PurchaseOrderCollection.class).getPurchaseOrders(); + poLines = objectMapper.readValue(getMockData(SAMPLE_PO_LINES_PATH), PoLineCollection.class).getPoLines(); + + } + + @Test + void testEdifactOrdersExport() throws Exception { + JobLauncherTestUtils testLauncher = createTestLauncher(edifactExportJob); + String cqlString = "(purchaseOrder.workflowStatus==Open)" + + " AND (purchaseOrder.vendor==d0fb5aa0-cdf1-11e8-a8d5-f2801f1b9fd1)" + + " AND (cql.allRecords=1 NOT purchaseOrder.manualPo==true)" + + " AND (automaticExport==true)" + + " AND (cql.allRecords=1 NOT lastEDIExportDate=\"\")" + + " AND (acquisitionMethod==(\"306489dd-0053-49ee-a068-c316444a8f55\"))" + + " AND (vendorDetail.vendorAccount==(\"BRXXXXX-01\"))"; + doReturn(poLines).when(ordersService).getPoLinesByQuery(cqlString); + doReturn(orders).when(ordersService).getPurchaseOrdersByIds(anyList()); + doReturn("test1").when(purchaseOrdersToEdifactMapper).convertOrdersToEdifact(any(), any(), anyString()); + + JobExecution jobExecution = testLauncher.launchStep(MAP_TO_EDIFACT_STEP, getJobParameters(getEdifactExportConfig(SAMPLE_EDI_ORDERS_EXPORT))); + + Assertions.assertThat(jobExecution.getExitStatus()).isEqualTo(ExitStatus.COMPLETED); + verify(ordersService).getPoLinesByQuery(cqlString); + verify(ordersService).getPurchaseOrdersByIds(anyList()); + } + + @Test + void testEdifactOrdersExportDefaultConfig() throws Exception { + JobLauncherTestUtils testLauncher = createTestLauncher(edifactExportJob); + String cqlString = "(purchaseOrder.workflowStatus==Open)" + + " AND (purchaseOrder.vendor==d0fb5aa0-cdf1-11e8-a8d5-f2801f1b9fd1)" + + " AND (cql.allRecords=1 NOT purchaseOrder.manualPo==true)" + + " AND (automaticExport==true)" + + " AND (cql.allRecords=1 NOT lastEDIExportDate=\"\")" + + " AND (acquisitionMethod==(\"306489dd-0053-49ee-a068-c316444a8f55\"))"; + String configSql = "configName==EDIFACT_ORDERS_EXPORT_d0fb5aa0-cdf1-11e8-a8d5-f2801f1b9fd1*"; + ExportConfigCollection exportConfigCollection = new ExportConfigCollection(); + exportConfigCollection.setTotalRecords(1); + poLines.get(0).getVendorDetail().setVendorAccount(null); + doReturn(poLines).when(ordersService).getPoLinesByQuery(cqlString); + doReturn(exportConfigCollection).when(dataExportSpringClient).getExportConfigs(configSql); + doReturn(orders).when(ordersService).getPurchaseOrdersByIds(anyList()); + doReturn("test1").when(purchaseOrdersToEdifactMapper).convertOrdersToEdifact(any(), any(), anyString()); + + var exportConfig = getEdifactExportConfig(SAMPLE_EDI_ORDERS_EXPORT, true); + JobExecution jobExecution = testLauncher.launchStep(MAP_TO_EDIFACT_STEP, getJobParameters(exportConfig)); + + Assertions.assertThat(jobExecution.getExitStatus()).isEqualTo(ExitStatus.COMPLETED); + verify(ordersService).getPoLinesByQuery(cqlString); + verify(ordersService).getPurchaseOrdersByIds(anyList()); + } + + @Test + void testEdifactOrdersExportDefaultConfigWithTwoExportConfigs() throws Exception { + JobLauncherTestUtils testLauncher = createTestLauncher(edifactExportJob); + String cqlString = "(purchaseOrder.workflowStatus==Open)" + + " AND (purchaseOrder.vendor==d0fb5aa0-cdf1-11e8-a8d5-f2801f1b9fd1)" + + " AND (cql.allRecords=1 NOT purchaseOrder.manualPo==true)" + + " AND (automaticExport==true)" + + " AND (cql.allRecords=1 NOT lastEDIExportDate=\"\")" + + " AND (acquisitionMethod==(\"306489dd-0053-49ee-a068-c316444a8f55\"))" + + " AND (cql.allRecords=1 NOT vendorDetail.vendorAccount==(\"org1\" or \"org2\"))"; + String configSql = "configName==EDIFACT_ORDERS_EXPORT_d0fb5aa0-cdf1-11e8-a8d5-f2801f1b9fd1*"; + ExportConfigCollection exportConfigCollection = objectMapper.readValue(getMockData(DATA_EXPORT_CONFIGS_PATH), ExportConfigCollection.class); + poLines.get(0).getVendorDetail().setVendorAccount(null); + doReturn(poLines).when(ordersService).getPoLinesByQuery(cqlString); + doReturn(exportConfigCollection).when(dataExportSpringClient).getExportConfigs(configSql); + doReturn(orders).when(ordersService).getPurchaseOrdersByIds(anyList()); + doReturn("test1").when(purchaseOrdersToEdifactMapper).convertOrdersToEdifact(any(), any(), anyString()); + + var exportConfig = getEdifactExportConfig(SAMPLE_EDI_ORDERS_EXPORT, true); + JobExecution jobExecution = testLauncher.launchStep(MAP_TO_EDIFACT_STEP, getJobParameters(exportConfig)); + + Assertions.assertThat(jobExecution.getExitStatus()).isEqualTo(ExitStatus.COMPLETED); + verify(ordersService).getPoLinesByQuery(cqlString); + verify(ordersService).getPurchaseOrdersByIds(anyList()); + } + + protected ObjectNode getEdifactExportConfig(String path, boolean isDefaultConfig) throws IOException { + return getEdifactExportConfig(path).put("isDefaultConfig", isDefaultConfig); + } + +} diff --git a/src/test/java/org/folio/dew/batch/acquisitions/edifact/jobs/MapToEdifactTaskletAbstractTest.java b/src/test/java/org/folio/dew/batch/acquisitions/edifact/jobs/MapToEdifactTaskletAbstractTest.java new file mode 100644 index 000000000..00a9b0753 --- /dev/null +++ b/src/test/java/org/folio/dew/batch/acquisitions/edifact/jobs/MapToEdifactTaskletAbstractTest.java @@ -0,0 +1,101 @@ +package org.folio.dew.batch.acquisitions.edifact.jobs; + +import static org.folio.dew.utils.TestUtils.getMockData; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.verify; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import org.folio.dew.BaseBatchTest; +import org.folio.dew.batch.acquisitions.edifact.PurchaseOrdersToEdifactMapper; +import org.folio.dew.batch.acquisitions.edifact.services.OrdersService; +import org.folio.dew.client.DataExportSpringClient; +import org.junit.jupiter.api.Test; +import org.springframework.batch.core.BatchStatus; +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.test.JobLauncherTestUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; + +abstract class MapToEdifactTaskletAbstractTest extends BaseBatchTest { + + protected static final String SAMPLE_PURCHASE_ORDERS_PATH = "edifact/acquisitions/purchase_order_collection.json"; + protected static final String SAMPLE_PO_LINES_PATH = "edifact/acquisitions/po_line_collection.json"; + protected static final String MAP_TO_EDIFACT_STEP = "mapToEdifactStep"; + + protected static final String SAMPLE_EDI_ORDERS_EXPORT = "edifact/edifactOrdersExport.json"; + private static final String SAMPLE_EDI_ORDERS_EXPORT_MISSING_FIELDS = "edifact/edifactOrdersExportWithoutRequiredFields.json"; + private static final String SAMPLE_EDI_ORDERS_EXPORT_MISSING_PORT = "edifact/edifactOrdersExportWithoutPort.json"; + + @MockBean + protected OrdersService ordersService; + @MockBean + protected DataExportSpringClient dataExportSpringClient; + @MockBean + protected PurchaseOrdersToEdifactMapper purchaseOrdersToEdifactMapper; + @Autowired + protected ObjectMapper objectMapper; + protected Job edifactExportJob; + + @Test + void testEdifactExportMissingRequiredFields() throws Exception { + JobLauncherTestUtils testLauncher = createTestLauncher(edifactExportJob); + JobExecution jobExecution = testLauncher.launchStep(MAP_TO_EDIFACT_STEP, getJobParameters(getEdifactExportConfig(SAMPLE_EDI_ORDERS_EXPORT_MISSING_FIELDS))); + var status = new ArrayList<>(jobExecution.getStepExecutions()).get(0).getStatus(); + + assertEquals(BatchStatus.FAILED, status); + assertThat(jobExecution.getExitStatus().getExitCode()).isEqualTo(ExitStatus.FAILED.getExitCode()); + assertThat(jobExecution.getExitStatus().getExitDescription()).contains("Export configuration is incomplete, missing library EDI code/Vendor EDI code"); + } + + @Test + void testEdifactExportMissingFtpPort() throws Exception { + JobLauncherTestUtils testLauncher = createTestLauncher(edifactExportJob); + JobExecution jobExecution = testLauncher.launchStep(MAP_TO_EDIFACT_STEP, getJobParameters(getEdifactExportConfig(SAMPLE_EDI_ORDERS_EXPORT_MISSING_PORT))); + var status = new ArrayList<>(jobExecution.getStepExecutions()).get(0).getStatus(); + + assertEquals(BatchStatus.FAILED, status); + assertThat(jobExecution.getExitStatus().getExitCode()).isEqualTo(ExitStatus.FAILED.getExitCode()); + assertThat(jobExecution.getExitStatus().getExitDescription()).contains("Export configuration is incomplete, missing FTP/SFTP Port"); + } + + @Test + void testEdifactExportPurchaseOrdersNotFound() throws Exception { + JobLauncherTestUtils testLauncher = createTestLauncher(edifactExportJob); + doReturn(List.of()).when(ordersService).getPoLinesByQuery(anyString()); + + JobExecution jobExecution = testLauncher.launchStep(MAP_TO_EDIFACT_STEP, getJobParameters(getEdifactExportConfig(SAMPLE_EDI_ORDERS_EXPORT))); + + assertThat(jobExecution.getExitStatus().getExitCode()).isEqualTo(ExitStatus.FAILED.getExitCode()); + assertThat(jobExecution.getExitStatus().getExitDescription()).contains("Entities not found: PurchaseOrder"); + verify(ordersService).getPoLinesByQuery(anyString()); + verify(ordersService).getPurchaseOrdersByIds(anyList()); + } + + protected ObjectNode getEdifactExportConfig(String path) throws IOException { + return (ObjectNode) objectMapper.readTree(getMockData(path)); + } + + protected JobParameters getJobParameters(ObjectNode edifactExport) { + return new JobParametersBuilder() + .addString("jobId", UUID.randomUUID().toString()) + .addString("edifactOrdersExport", edifactExport.toString()) + .addString("jobName", "000015") + .toJobParameters(); + } + +} diff --git a/src/test/java/org/folio/dew/batch/acquisitions/edifact/jobs/MapToEdifactTaskletTest.java b/src/test/java/org/folio/dew/batch/acquisitions/edifact/jobs/MapToEdifactTaskletTest.java deleted file mode 100644 index 7e205293c..000000000 --- a/src/test/java/org/folio/dew/batch/acquisitions/edifact/jobs/MapToEdifactTaskletTest.java +++ /dev/null @@ -1,213 +0,0 @@ -package org.folio.dew.batch.acquisitions.edifact.jobs; - -import static org.folio.dew.utils.TestUtils.getMockData; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsString; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyList; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.verify; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -import com.fasterxml.jackson.databind.node.ObjectNode; -import org.assertj.core.api.Assertions; -import org.folio.dew.BaseBatchTest; -import org.folio.dew.batch.acquisitions.edifact.PurchaseOrdersToEdifactMapper; -import org.folio.dew.batch.acquisitions.edifact.services.OrdersService; -import org.folio.dew.client.DataExportSpringClient; -import org.folio.dew.domain.dto.ExportConfigCollection; -import org.folio.dew.domain.dto.PoLine; -import org.folio.dew.domain.dto.PoLineCollection; -import org.folio.dew.domain.dto.PurchaseOrder; -import org.folio.dew.domain.dto.PurchaseOrderCollection; -import org.junit.jupiter.api.Test; -import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; -import org.springframework.batch.test.JobLauncherTestUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.mock.mockito.MockBean; - -import com.fasterxml.jackson.databind.ObjectMapper; - -class MapToEdifactTaskletTest extends BaseBatchTest { - - @MockBean - private OrdersService ordersService; - @MockBean - DataExportSpringClient dataExportSpringClient; - @MockBean - private PurchaseOrdersToEdifactMapper purchaseOrdersToEdifactMapper; - - @Autowired - protected ObjectMapper objectMapper; - @Autowired - Job edifactExportJob; - - @Test - void edifactExportJobTestSuccess() throws Exception { - JobLauncherTestUtils testLauncher = createTestLauncher(edifactExportJob); - List orders = objectMapper.readValue(getMockData( - "edifact/acquisitions/purchase_order_collection.json"), PurchaseOrderCollection.class).getPurchaseOrders(); - List poLines = objectMapper.readValue(getMockData("edifact/acquisitions/po_line_collection.json"), - PoLineCollection.class).getPoLines(); - String cqlString = "purchaseOrder.workflowStatus==Open" + - " AND purchaseOrder.vendor==d0fb5aa0-cdf1-11e8-a8d5-f2801f1b9fd1" + - " AND (cql.allRecords=1 NOT purchaseOrder.manualPo==true)" + - " AND automaticExport==true" + - " AND (cql.allRecords=1 NOT lastEDIExportDate=\"\")" + - " AND acquisitionMethod==(\"306489dd-0053-49ee-a068-c316444a8f55\")" + - " AND vendorDetail.vendorAccount==(\"BRXXXXX-01\")"; - doReturn(poLines).when(ordersService).getPoLinesByQuery(cqlString); - doReturn(orders).when(ordersService).getPurchaseOrdersByIds(anyList()); - doReturn("test1").when(purchaseOrdersToEdifactMapper).convertOrdersToEdifact(any(), any(), anyString()); - - JobExecution jobExecution = testLauncher.launchStep("mapToEdifactStep", getJobParameters(false)); - - Assertions.assertThat(jobExecution.getExitStatus()).isEqualTo(ExitStatus.COMPLETED); - verify(ordersService).getPoLinesByQuery(cqlString); - verify(ordersService).getPurchaseOrdersByIds(anyList()); - } - - @Test - void testShouldReturnEdifactExceptionBecauseRequiredFieldsIsNull() throws Exception { - JobLauncherTestUtils testLauncher = createTestLauncher(edifactExportJob); - JobExecution jobExecution = testLauncher.launchStep("mapToEdifactStep", getJobParametersWithoutRequiredFields()); - String expectedMessage = "Export configuration is incomplete, missing library EDI code/Vendor EDI code"; - var status = new ArrayList<>(jobExecution.getStepExecutions()).get(0).getStatus().name(); - - assertTrue(jobExecution.getExitStatus().getExitDescription().contains(expectedMessage)); - assertEquals("FAILED", status); - } - - @Test - void testShouldReturnEdifactExceptionBecauseFtpPortIsNull() throws Exception { - JobLauncherTestUtils testLauncher = createTestLauncher(edifactExportJob); - JobExecution jobExecution = testLauncher.launchStep("mapToEdifactStep", getJobParametersWithoutPort()); - String expectedMessage = "Export configuration is incomplete, missing FTP/SFTP Port"; - var status = new ArrayList<>(jobExecution.getStepExecutions()).get(0).getStatus().name(); - - assertTrue(jobExecution.getExitStatus().getExitDescription().contains(expectedMessage)); - assertEquals("FAILED", status); - } - - @Test - void edifactExportJobIfDefaultConfigTestSuccess() throws Exception { - JobLauncherTestUtils testLauncher = createTestLauncher(edifactExportJob); - List orders = objectMapper.readValue(getMockData( - "edifact/acquisitions/purchase_order_collection.json"), PurchaseOrderCollection.class).getPurchaseOrders(); - List poLines = objectMapper.readValue(getMockData("edifact/acquisitions/po_line_collection.json"), - PoLineCollection.class).getPoLines(); - String cqlString = "purchaseOrder.workflowStatus==Open" + - " AND purchaseOrder.vendor==d0fb5aa0-cdf1-11e8-a8d5-f2801f1b9fd1" + - " AND (cql.allRecords=1 NOT purchaseOrder.manualPo==true)" + - " AND automaticExport==true" + - " AND (cql.allRecords=1 NOT lastEDIExportDate=\"\")" + - " AND acquisitionMethod==(\"306489dd-0053-49ee-a068-c316444a8f55\")"; - String configSql = "configName==EDIFACT_ORDERS_EXPORT_d0fb5aa0-cdf1-11e8-a8d5-f2801f1b9fd1*"; - ExportConfigCollection exportConfigCollection = new ExportConfigCollection(); - exportConfigCollection.setTotalRecords(1); - poLines.get(0).getVendorDetail().setVendorAccount(null); - doReturn(poLines).when(ordersService).getPoLinesByQuery(cqlString); - doReturn(exportConfigCollection).when(dataExportSpringClient).getExportConfigs(configSql); - doReturn(orders).when(ordersService).getPurchaseOrdersByIds(anyList()); - doReturn("test1").when(purchaseOrdersToEdifactMapper).convertOrdersToEdifact(any(), any(), anyString()); - - JobExecution jobExecution = testLauncher.launchStep("mapToEdifactStep", getJobParameters(true)); - - Assertions.assertThat(jobExecution.getExitStatus()).isEqualTo(ExitStatus.COMPLETED); - verify(ordersService).getPoLinesByQuery(cqlString); - verify(ordersService).getPurchaseOrdersByIds(anyList()); - } - - @Test - void edifactExportJobIfDefaultConfigNotOneTestSuccess() throws Exception { - JobLauncherTestUtils testLauncher = createTestLauncher(edifactExportJob); - List orders = objectMapper.readValue(getMockData( - "edifact/acquisitions/purchase_order_collection.json"), PurchaseOrderCollection.class).getPurchaseOrders(); - List poLines = objectMapper.readValue(getMockData("edifact/acquisitions/po_line_collection.json"), - PoLineCollection.class).getPoLines(); - String cqlString = "purchaseOrder.workflowStatus==Open" + - " AND purchaseOrder.vendor==d0fb5aa0-cdf1-11e8-a8d5-f2801f1b9fd1" + - " AND (cql.allRecords=1 NOT purchaseOrder.manualPo==true)" + - " AND automaticExport==true" + - " AND (cql.allRecords=1 NOT lastEDIExportDate=\"\")" + - " AND acquisitionMethod==(\"306489dd-0053-49ee-a068-c316444a8f55\")" + - " AND cql.allRecords=1 NOT vendorDetail.vendorAccount==(\"org1\" OR \"org2\")"; - String configSql = "configName==EDIFACT_ORDERS_EXPORT_d0fb5aa0-cdf1-11e8-a8d5-f2801f1b9fd1*"; - ExportConfigCollection exportConfigCollection = objectMapper.readValue(getMockData("edifact/dataExportConfigs.json"), ExportConfigCollection.class); - poLines.get(0).getVendorDetail().setVendorAccount(null); - doReturn(poLines).when(ordersService).getPoLinesByQuery(cqlString); - doReturn(exportConfigCollection).when(dataExportSpringClient).getExportConfigs(configSql); - doReturn(orders).when(ordersService).getPurchaseOrdersByIds(anyList()); - doReturn("test1").when(purchaseOrdersToEdifactMapper).convertOrdersToEdifact(any(), any(), anyString()); - - JobExecution jobExecution = testLauncher.launchStep("mapToEdifactStep", getJobParameters(true)); - - Assertions.assertThat(jobExecution.getExitStatus()).isEqualTo(ExitStatus.COMPLETED); - verify(ordersService).getPoLinesByQuery(cqlString); - verify(ordersService).getPurchaseOrdersByIds(anyList()); - } - - @Test - void purchaseOrdersNotFound() throws Exception { - JobLauncherTestUtils testLauncher = createTestLauncher(edifactExportJob); - List poLines = List.of(); - String cqlString = "purchaseOrder.workflowStatus==Open" + - " AND purchaseOrder.vendor==d0fb5aa0-cdf1-11e8-a8d5-f2801f1b9fd1" + - " AND (cql.allRecords=1 NOT purchaseOrder.manualPo==true)" + - " AND automaticExport==true" + - " AND (cql.allRecords=1 NOT lastEDIExportDate=\"\")" + - " AND acquisitionMethod==(\"306489dd-0053-49ee-a068-c316444a8f55\")" + - " AND vendorDetail.vendorAccount==(\"BRXXXXX-01\")"; - doReturn(poLines).when(ordersService).getPoLinesByQuery(cqlString); - - JobExecution jobExecution = testLauncher.launchStep("mapToEdifactStep", getJobParameters(false)); - - assertThat(jobExecution.getExitStatus().getExitDescription(), containsString("Orders for export not found")); - verify(ordersService).getPoLinesByQuery(cqlString); - verify(ordersService).getPurchaseOrdersByIds(anyList()); - } - - private JobParameters getJobParameters(boolean isDefaultConfig) throws IOException { - JobParametersBuilder paramsBuilder = new JobParametersBuilder(); - var edifactOrdersExportJson = (ObjectNode) objectMapper.readTree(getMockData("edifact/edifactOrdersExport.json")); - edifactOrdersExportJson.put("isDefaultConfig", isDefaultConfig); - - paramsBuilder.addString("jobId", UUID.randomUUID().toString()); - paramsBuilder.addString("edifactOrdersExport", edifactOrdersExportJson.toString()); - paramsBuilder.addString("jobName", "000015"); - - return paramsBuilder.toJobParameters(); - } - - private JobParameters getJobParametersWithoutRequiredFields() throws IOException { - JobParametersBuilder paramsBuilder = new JobParametersBuilder(); - var edifactOrdersExportJson = (ObjectNode) objectMapper.readTree(getMockData("edifact/edifactOrdersExportWithoutRequiredFields.json")); - edifactOrdersExportJson.put("isDefaultConfig", false); - - paramsBuilder.addString("jobId", UUID.randomUUID().toString()); - paramsBuilder.addString("edifactOrdersExport", edifactOrdersExportJson.toString()); - paramsBuilder.addString("jobName", "000015"); - - return paramsBuilder.toJobParameters(); - } - - private JobParameters getJobParametersWithoutPort() throws IOException { - JobParametersBuilder paramsBuilder = new JobParametersBuilder(); - - paramsBuilder.addString("edifactOrdersExport", getMockData("edifact/edifactOrdersExportWithoutPort.json")); - paramsBuilder.addString("jobId", UUID.randomUUID().toString()); - - return paramsBuilder.toJobParameters(); - } -} diff --git a/src/test/java/org/folio/dew/batch/acquisitions/edifact/jobs/SaveToFileStorageTaskletTest.java b/src/test/java/org/folio/dew/batch/acquisitions/edifact/jobs/SaveToFileStorageTaskletTest.java index 7dac5161b..1bd2fcbeb 100644 --- a/src/test/java/org/folio/dew/batch/acquisitions/edifact/jobs/SaveToFileStorageTaskletTest.java +++ b/src/test/java/org/folio/dew/batch/acquisitions/edifact/jobs/SaveToFileStorageTaskletTest.java @@ -29,6 +29,7 @@ import org.springframework.batch.item.ExecutionContext; import org.springframework.batch.test.JobLauncherTestUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.annotation.DirtiesContext; @@ -36,6 +37,7 @@ class SaveToFileStorageTaskletTest extends BaseBatchTest { @Autowired + @Qualifier("edifactOrdersExportJob") private Job edifactExportJob; @MockBean private SFTPObjectStorageRepository sftpObjectStorageRepository; diff --git a/src/test/java/org/folio/dew/batch/acquisitions/edifact/jobs/SaveToMinioTaskletFailTest.java b/src/test/java/org/folio/dew/batch/acquisitions/edifact/jobs/SaveToMinioTaskletFailTest.java index ec0b6f130..281569784 100644 --- a/src/test/java/org/folio/dew/batch/acquisitions/edifact/jobs/SaveToMinioTaskletFailTest.java +++ b/src/test/java/org/folio/dew/batch/acquisitions/edifact/jobs/SaveToMinioTaskletFailTest.java @@ -23,6 +23,7 @@ import org.springframework.batch.item.ExecutionContext; import org.springframework.batch.test.JobLauncherTestUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.annotation.DirtiesContext; @@ -30,6 +31,7 @@ class SaveToMinioTaskletFailTest extends BaseBatchTest { @Autowired + @Qualifier("edifactOrdersExportJob") private Job edifactExportJob; @MockBean private OrganizationsService organizationsService; @@ -78,4 +80,4 @@ protected JobLauncherTestUtils createTestLauncher(Job job) { testLauncher.setJobRepository(jobRepository); return testLauncher; } -} \ No newline at end of file +} diff --git a/src/test/java/org/folio/dew/batch/acquisitions/edifact/jobs/SaveToMinioTaskletTest.java b/src/test/java/org/folio/dew/batch/acquisitions/edifact/jobs/SaveToMinioTaskletTest.java index 907bea346..daa6111ef 100644 --- a/src/test/java/org/folio/dew/batch/acquisitions/edifact/jobs/SaveToMinioTaskletTest.java +++ b/src/test/java/org/folio/dew/batch/acquisitions/edifact/jobs/SaveToMinioTaskletTest.java @@ -20,6 +20,7 @@ import org.springframework.batch.item.ExecutionContext; import org.springframework.batch.test.JobLauncherTestUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.annotation.DirtiesContext; @@ -27,6 +28,7 @@ class SaveToMinioTaskletTest extends BaseBatchTest { @Autowired + @Qualifier("edifactOrdersExportJob") private Job edifactExportJob; @MockBean private OrganizationsService organizationsService; @@ -69,4 +71,4 @@ protected JobLauncherTestUtils createTestLauncher(Job job) { testLauncher.setJobRepository(jobRepository); return testLauncher; } -} \ No newline at end of file +} diff --git a/src/test/resources/edifact/acquisitions/composite_purchase_order.json b/src/test/resources/edifact/acquisitions/composite_purchase_order.json index 621fbcc33..f5e6a3d7b 100644 --- a/src/test/resources/edifact/acquisitions/composite_purchase_order.json +++ b/src/test/resources/edifact/acquisitions/composite_purchase_order.json @@ -86,7 +86,12 @@ "vendorDetail": { "instructions": "", "vendorAccount": "BRXXXXX-01", - "referenceNumbers": [] + "referenceNumbers": [ + { + "refNumber": "ORD1000", + "refNumberType": "Vendor order reference number" + } + ] }, "metadata": { "createdDate": "2021-12-28T08:17:02.171+00:00", diff --git a/src/test/resources/edifact/acquisitions/comprehensive_composite_purchase_order.json b/src/test/resources/edifact/acquisitions/comprehensive_composite_purchase_order.json index 1e40c4d3c..46c7f7365 100644 --- a/src/test/resources/edifact/acquisitions/comprehensive_composite_purchase_order.json +++ b/src/test/resources/edifact/acquisitions/comprehensive_composite_purchase_order.json @@ -147,7 +147,12 @@ "vendorDetail": { "instructions": "", "vendorAccount": "BRXXXXX-01", - "referenceNumbers": [] + "referenceNumbers": [ + { + "refNumber": "ORD1001", + "refNumberType": "Vendor order reference number" + } + ] }, "metadata": { "createdDate": "2021-12-28T08:17:02.171+00:00", @@ -251,6 +256,10 @@ "refNumber": "1234", "refNumberType": "Vendor title number", "vendorDetailsSource": "OrderLine" + }, + { + "refNumber": "ORD1002", + "refNumberType": "Vendor order reference number" } ] }, diff --git a/src/test/resources/edifact/acquisitions/edifact_claims_result.edi b/src/test/resources/edifact/acquisitions/edifact_claims_result.edi new file mode 100644 index 000000000..13f2fab02 --- /dev/null +++ b/src/test/resources/edifact/acquisitions/edifact_claims_result.edi @@ -0,0 +1,109 @@ +UNA:+.? ' +UNB+UNOC:3+901494200:014+0142948:31B+ddmmyy:hhmm+{fileId}' +UNH+10000+ORDERS:D:96A:UN:EAN008' +BGM+220+10000+9' +DTM+137::102' +NAD+BY+901494200::014++Bockenheimer Landstr. 134-13' +NAD+SU+0142948::31B' +RFF+API:BRXXXXX-01' +CUX+2:USD:9' +LIN+1++9783319643991:EN' +PIA+5+9783319643991:IS' +IMD+L+009+:::Moutinho, Luiz' +IMD+L+050+:::Futures, biometrics and neuroscience research Luiz Moutinho, Mladen So' +IMD+L+050+:::kele, editors' +IMD+L+080+:::S1?:C1?:E1' +IMD+L+080+:::S2?:C2' +IMD+L+109+:::Palgrave Macmillan' +QTY+21:1' +PRI+AAF:1.8' +PRI+AAB:USD 2.00' +CUX+2:USD:9' +RFF+LI:10000-1' +RFF+BFN:USHIST' +RFF+SNA:ORD1000' +LOC+7+KU/CC/DI/M::92' +UNS+S' +CNT+1:1' +CNT+2:1' +UNT+27+10000' +UNH+10001+ORDERS:D:96A:UN:EAN008' +BGM+224+10001+9' +DTM+137::102' +NAD+BY+901494200::014++Bockenheimer Landstr. 134-13' +NAD+SU+0142948::31B' +RFF+API:BRXXXXX-01' +CUX+2:USD:9' +LIN+1++9783319643991:EN' +PIA+5+9783319643991:IM' +IMD+L+009+:::Moutinho, Luiz' +IMD+L+050+:::Futures, biometrics and neuroscience research Luiz Moutinho, Mladen So' +IMD+L+050+:::kele, editors' +IMD+L+080+:::S3?:E3' +IMD+L+109+:::Palgrave Macmillan' +IMD+L+170+:::[2017]' +QTY+21:1' +PRI+AAF:1.8' +PRI+AAB:USD 2.00' +CUX+2:USD:9' +RFF+LI:10001-1' +RFF+BFN:USHIST' +RFF+BFN:USHIST2' +RFF+BFN:USHIST3' +RFF+BFN:USHIST4' +RFF+BFN:USHIST5' +RFF+BFN:USHIST6' +RFF+BFN:USHIST7' +RFF+BFN:USHIST8' +RFF+BFN:USHIST9' +RFF+SNA:ORD1001' +LOC+7+KU/CC/DI/M::92' +LIN+2++9783319643991:EN' +PIA+5+9783319643991:IB' +PIA+1+OTA-1031 Otá Records:MF' +IMD+L+009+:::Sosa, Omar' +IMD+L+009+:::Keita, Seckou, 1977-' +IMD+L+050+:::Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmo' +IMD+L+050+:::d tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim' +IMD+L+050+::: veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ' +IMD+L+050+:::ea commodo consequat. Duis aute irure dolor in reprehenderit in volupt' +IMD+L+050+:::ate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint ' +IMD+L+050+:::occaecat cupidatat non proident, sunt in culpa qui officia deserunt mo' +IMD+L+050+:::llit anim id est laborum.' +IMD+L+080+:::C4?:E4' +IMD+L+109+:::Otá Records, ' +IMD+L+180+:::Book' +QTY+21:3' +PRI+AAF:1.5' +PRI+AAB:USD 1.65' +CUX+2:USD:9' +FTX+LIN++::+Test vendor instruction text.' +RFF+LI:10001-2' +RFF+BFN:USHIST' +RFF+BFN:AFRICAHIST?:Elec' +RFF+SNA:ORD1002' +RFF+SLI:1234' +LOC+7+KU/CC/DI/M::92' +LOC+7+KU/CC/DI/M::92' +UNS+S' +CNT+1:4' +CNT+2:2' +UNT+62+10001' +UNH+10002+ORDERS:D:96A:UN:EAN008' +BGM+220+10002+9' +DTM+137::102' +NAD+BY+901494200::014++Bockenheimer Landstr. 134-13' +NAD+SU+0142948::31B' +CUX+2:USD:9' +LIN+1++:' +IMD+L+050+:::empty' +IMD+L+080+:::S5?:E5' +IMD+L+080+:::C5' +QTY+21:0' +CUX+2:USD:9' +RFF+LI:10002-1' +UNS+S' +CNT+1:0' +CNT+2:1' +UNT+17+10002' +UNZ+3+{fileId}' diff --git a/src/test/resources/edifact/acquisitions/edifact_orders_result.edi b/src/test/resources/edifact/acquisitions/edifact_orders_result.edi index 14572e280..a9fb8d228 100644 --- a/src/test/resources/edifact/acquisitions/edifact_orders_result.edi +++ b/src/test/resources/edifact/acquisitions/edifact_orders_result.edi @@ -19,11 +19,12 @@ PRI+AAB:USD 2.00' CUX+2:USD:9' RFF+LI:10000-1' RFF+BFN:USHIST' +RFF+SLI:ORD1000' LOC+7+KU/CC/DI/M::92' UNS+S' CNT+1:1' CNT+2:1' -UNT+24+10000' +UNT+25+10000' UNH+10001+ORDERS:D:96A:UN:EAN008' BGM+224+10001+9' DTM+137::102' @@ -76,12 +77,13 @@ RFF+LI:10001-2' RFF+BFN:USHIST' RFF+BFN:AFRICAHIST?:Elec' RFF+SLI:1234' +RFF+SLI:ORD1002' LOC+7+KU/CC/DI/M::92' LOC+7+KU/CC/DI/M::92' UNS+S' CNT+1:4' CNT+2:2' -UNT+58+10001' +UNT+59+10001' UNH+10002+ORDERS:D:96A:UN:EAN008' BGM+220+10002+9' DTM+137::102' diff --git a/src/test/resources/edifact/acquisitions/pieces_collection.json b/src/test/resources/edifact/acquisitions/pieces_collection.json new file mode 100644 index 000000000..1564e88b3 --- /dev/null +++ b/src/test/resources/edifact/acquisitions/pieces_collection.json @@ -0,0 +1,38 @@ +{ + "pieces": [ + { + "id": "5e317dc2-deeb-4429-b2a1-91e5cd0fd5f7", + "displaySummary": "Tutorial Volume 5", + "comment": "Special Edition", + "format": "Electronic", + "itemId": "522a501a-56b5-48d9-b28a-3a8f02482d97", + "bindItemId": "779b89f7-6c0f-42d0-bf50-4b0e30958d2d", + "locationId": "53cf956f-c1df-410b-8bea-27f712cca7c0", + "poLineId": "d471d766-8dbb-4609-999a-02681dea6c22", + "titleId": "9a665b22-9fe5-4c95-b4ee-837a5433c95d", + "receivingTenantId": "diku", + "displayOnHolding": false, + "displayToPublic": false, + "enumeration": "Volume 1", + "chronology": "2021", + "barcode": "0987654111", + "accessionNumber": "2015.1", + "callNumber": "BF2050 .M335 1999", + "discoverySuppress": false, + "copyNumber": "94753", + "receivingStatus": "Late", + "supplement": true, + "isBound": true, + "receiptDate": "2018-10-11T00:00:00.000Z", + "claimingInterval": 0, + "internalNote": "Internal note for Send Claim action", + "externalNote": "External note for Send Claim action to share with Vendor", + "statusUpdatedDate": "2018-10-11T00:00:00.000Z", + "metadata": { + "createdDate": "2018-08-19T00:00:00.000+0000", + "createdByUserId": "28d1057c-d137-11e8-a8d5-f2801f1b9fd1" + } + } + + ] +} diff --git a/src/test/resources/edifact/acquisitions/pieces_collection_mixed.json b/src/test/resources/edifact/acquisitions/pieces_collection_mixed.json new file mode 100644 index 000000000..8ab9d88a2 --- /dev/null +++ b/src/test/resources/edifact/acquisitions/pieces_collection_mixed.json @@ -0,0 +1,235 @@ +{ + "pieces": [ + { + "id": "5e317dc2-deeb-4429-b2a1-91e5cd0fd5f1", + "poLineId": "f7cebba9-900d-4e46-ac96-9dfdb6da214e", + "receivingStatus": "Late", + "displaySummary": "S1", + "chronology": "C1", + "enumeration": "E1", + "comment": "Special Edition", + "format": "Electronic", + "itemId": "522a501a-56b5-48d9-b28a-3a8f02482d97", + "bindItemId": "779b89f7-6c0f-42d0-bf50-4b0e30958d2d", + "locationId": "53cf956f-c1df-410b-8bea-27f712cca7c0", + "titleId": "9a665b22-9fe5-4c95-b4ee-837a5433c95d", + "receivingTenantId": "diku", + "displayOnHolding": false, + "displayToPublic": false, + "barcode": "0987654111", + "accessionNumber": "2015.1", + "callNumber": "BF2050 .M335 1999", + "discoverySuppress": false, + "copyNumber": "94753", + "supplement": true, + "isBound": true, + "receiptDate": "2018-10-11T00:00:00.000Z", + "claimingInterval": 0, + "internalNote": "Internal note for Send Claim action", + "externalNote": "External note for Send Claim action to share with Vendor", + "statusUpdatedDate": "2018-10-11T00:00:00.000Z", + "metadata": { + "createdDate": "2018-08-19T00:00:00.000+0000", + "createdByUserId": "28d1057c-d137-11e8-a8d5-f2801f1b9fd1" + } + }, + { + "id": "5e317dc2-deeb-4429-b2a1-91e5cd0fd5f2", + "poLineId": "f7cebba9-900d-4e46-ac96-9dfdb6da214e", + "receivingStatus": "Late", + "displaySummary": "S2", + "chronology": "C2", + "enumeration": "", + "comment": "Special Edition", + "format": "Electronic", + "itemId": "522a501a-56b5-48d9-b28a-3a8f02482d97", + "bindItemId": "779b89f7-6c0f-42d0-bf50-4b0e30958d2d", + "locationId": "53cf956f-c1df-410b-8bea-27f712cca7c0", + "titleId": "9a665b22-9fe5-4c95-b4ee-837a5433c95d", + "receivingTenantId": "diku", + "displayOnHolding": false, + "displayToPublic": false, + "barcode": "0987654111", + "accessionNumber": "2015.1", + "callNumber": "BF2050 .M335 1999", + "discoverySuppress": false, + "copyNumber": "94753", + "supplement": true, + "isBound": true, + "receiptDate": "2018-10-11T00:00:00.000Z", + "claimingInterval": 0, + "internalNote": "Internal note for Send Claim action", + "externalNote": "External note for Send Claim action to share with Vendor", + "statusUpdatedDate": "2018-10-11T00:00:00.000Z", + "metadata": { + "createdDate": "2018-08-19T00:00:00.000+0000", + "createdByUserId": "28d1057c-d137-11e8-a8d5-f2801f1b9fd1" + } + }, + { + "id": "5e317dc2-deeb-4429-b2a1-91e5cd0fd5f3", + "poLineId": "81b6503d-1f50-404d-a663-794b7f726ffd", + "receivingStatus": "Late", + "displaySummary": "S3", + "chronology": "", + "enumeration": "E3", + "comment": "Special Edition", + "format": "Electronic", + "itemId": "522a501a-56b5-48d9-b28a-3a8f02482d97", + "bindItemId": "779b89f7-6c0f-42d0-bf50-4b0e30958d2d", + "locationId": "53cf956f-c1df-410b-8bea-27f712cca7c0", + "titleId": "9a665b22-9fe5-4c95-b4ee-837a5433c95d", + "receivingTenantId": "diku", + "displayOnHolding": false, + "displayToPublic": false, + "barcode": "0987654111", + "accessionNumber": "2015.1", + "callNumber": "BF2050 .M335 1999", + "discoverySuppress": false, + "copyNumber": "94753", + "supplement": true, + "isBound": true, + "receiptDate": "2018-10-11T00:00:00.000Z", + "claimingInterval": 0, + "internalNote": "Internal note for Send Claim action", + "externalNote": "External note for Send Claim action to share with Vendor", + "statusUpdatedDate": "2018-10-11T00:00:00.000Z", + "metadata": { + "createdDate": "2018-08-19T00:00:00.000+0000", + "createdByUserId": "28d1057c-d137-11e8-a8d5-f2801f1b9fd1" + } + }, + { + "id": "5e317dc2-deeb-4429-b2a1-91e5cd0fd5f3", + "poLineId": "c8d7a6a8-89b9-4711-9e1e-694b3e5ada72", + "receivingStatus": "Late", + "displaySummary": null, + "chronology": "C4", + "enumeration": "E4", + "comment": "Special Edition", + "format": "Electronic", + "itemId": "522a501a-56b5-48d9-b28a-3a8f02482d97", + "bindItemId": "779b89f7-6c0f-42d0-bf50-4b0e30958d2d", + "locationId": "53cf956f-c1df-410b-8bea-27f712cca7c0", + "titleId": "9a665b22-9fe5-4c95-b4ee-837a5433c95d", + "receivingTenantId": "diku", + "displayOnHolding": false, + "displayToPublic": false, + "barcode": "0987654111", + "accessionNumber": "2015.1", + "callNumber": "BF2050 .M335 1999", + "discoverySuppress": false, + "copyNumber": "94753", + "supplement": true, + "isBound": true, + "receiptDate": "2018-10-11T00:00:00.000Z", + "claimingInterval": 0, + "internalNote": "Internal note for Send Claim action", + "externalNote": "External note for Send Claim action to share with Vendor", + "statusUpdatedDate": "2018-10-11T00:00:00.000Z", + "metadata": { + "createdDate": "2018-08-19T00:00:00.000+0000", + "createdByUserId": "28d1057c-d137-11e8-a8d5-f2801f1b9fd1" + } + }, + { + "id": "5e317dc2-deeb-4429-b2a1-91e5cd0fd5f4", + "poLineId": "31af5b5c-251e-4582-9896-2c9cef360284", + "receivingStatus": "Late", + "displaySummary": "S5", + "chronology": null, + "enumeration": "E5", + "comment": "Special Edition", + "format": "Electronic", + "itemId": "522a501a-56b5-48d9-b28a-3a8f02482d97", + "bindItemId": "779b89f7-6c0f-42d0-bf50-4b0e30958d2d", + "locationId": "53cf956f-c1df-410b-8bea-27f712cca7c0", + "titleId": "9a665b22-9fe5-4c95-b4ee-837a5433c95d", + "receivingTenantId": "diku", + "displayOnHolding": false, + "displayToPublic": false, + "barcode": "0987654111", + "accessionNumber": "2015.1", + "callNumber": "BF2050 .M335 1999", + "discoverySuppress": false, + "copyNumber": "94753", + "supplement": true, + "isBound": true, + "receiptDate": "2018-10-11T00:00:00.000Z", + "claimingInterval": 0, + "internalNote": "Internal note for Send Claim action", + "externalNote": "External note for Send Claim action to share with Vendor", + "statusUpdatedDate": "2018-10-11T00:00:00.000Z", + "metadata": { + "createdDate": "2018-08-19T00:00:00.000+0000", + "createdByUserId": "28d1057c-d137-11e8-a8d5-f2801f1b9fd1" + } + }, + { + "id": "5e317dc2-deeb-4429-b2a1-91e5cd0fd5f4", + "poLineId": "31af5b5c-251e-4582-9896-2c9cef360284", + "receivingStatus": "Late", + "displaySummary": null, + "chronology": "C5", + "enumeration": null, + "comment": "Special Edition", + "format": "Electronic", + "itemId": "522a501a-56b5-48d9-b28a-3a8f02482d97", + "bindItemId": "779b89f7-6c0f-42d0-bf50-4b0e30958d2d", + "locationId": "53cf956f-c1df-410b-8bea-27f712cca7c0", + "titleId": "9a665b22-9fe5-4c95-b4ee-837a5433c95d", + "receivingTenantId": "diku", + "displayOnHolding": false, + "displayToPublic": false, + "barcode": "0987654111", + "accessionNumber": "2015.1", + "callNumber": "BF2050 .M335 1999", + "discoverySuppress": false, + "copyNumber": "94753", + "supplement": true, + "isBound": true, + "receiptDate": "2018-10-11T00:00:00.000Z", + "claimingInterval": 0, + "internalNote": "Internal note for Send Claim action", + "externalNote": "External note for Send Claim action to share with Vendor", + "statusUpdatedDate": "2018-10-11T00:00:00.000Z", + "metadata": { + "createdDate": "2018-08-19T00:00:00.000+0000", + "createdByUserId": "28d1057c-d137-11e8-a8d5-f2801f1b9fd1" + } + }, + { + "id": "5e317dc2-deeb-4429-b2a1-91e5cd0fd5f4", + "poLineId": "31af5b5c-251e-4582-9896-2c9cef360284", + "receivingStatus": "Late", + "displaySummary": null, + "chronology": null, + "enumeration": null, + "comment": "Special Edition", + "format": "Electronic", + "itemId": "522a501a-56b5-48d9-b28a-3a8f02482d97", + "bindItemId": "779b89f7-6c0f-42d0-bf50-4b0e30958d2d", + "locationId": "53cf956f-c1df-410b-8bea-27f712cca7c0", + "titleId": "9a665b22-9fe5-4c95-b4ee-837a5433c95d", + "receivingTenantId": "diku", + "displayOnHolding": false, + "displayToPublic": false, + "barcode": "0987654111", + "accessionNumber": "2015.1", + "callNumber": "BF2050 .M335 1999", + "discoverySuppress": false, + "copyNumber": "94753", + "supplement": true, + "isBound": true, + "receiptDate": "2018-10-11T00:00:00.000Z", + "claimingInterval": 0, + "internalNote": "Internal note for Send Claim action", + "externalNote": "External note for Send Claim action to share with Vendor", + "statusUpdatedDate": "2018-10-11T00:00:00.000Z", + "metadata": { + "createdDate": "2018-08-19T00:00:00.000+0000", + "createdByUserId": "28d1057c-d137-11e8-a8d5-f2801f1b9fd1" + } + } + ] +}