From 882cc7628a2d3dcf2fceafb15747d58863e8d19b Mon Sep 17 00:00:00 2001 From: Saba-Zedginidze-EPAM <148070844+Saba-Zedginidze-EPAM@users.noreply.github.com> Date: Thu, 19 Dec 2024 13:28:08 +0400 Subject: [PATCH] [MODEXPW-529] Create CSV file for claims (#609) * [MODEXPW-529] Rename and restructure converters and mapper * [MODEXPW-529] Rename converters * [MODEXPW-529] Add csv mapper and converter * [MODEXPW-529] Use new mapper together with old one in tasklets * [MODEXPW-529] Simplify existing acq tests to speed up builds * [MODEXPW-529] Simplify edifact mapper test * [MODEXPW-529] Add CsvMapperTest (WIP) * [MODEXPW-529] Fix failing tests * [MODEXPW-529] Move tests to appropriate packages * [MODEXPW-529] Finish CsvMapper test, todo 2 field extractions * [MODEXPW-529] Fix sonar issues * [MODEXPW-529] Add title extraction logic * [MODEXPW-529] Add quantity calculation logic * [MODEXPW-529] Update pointer * [MODEXPW-529] Fix sonar issue * [MODEXPW-529] Improve grouping logic * [MODEXPW-529] Update pointer and rename export config --- folio-export-common | 2 +- .../config/AcquisitionExportConfig.java | 44 +++++++ .../config/EdifactPurchaseOrderConfig.java | 34 ------ .../jobs/MapToEdifactClaimsTasklet.java | 25 +++- .../jobs/MapToEdifactOrdersTasklet.java | 20 +++- .../edifact/jobs/MapToEdifactTasklet.java | 13 ++- .../edifact/mapper/CsvMapper.java | 74 ++++++++++++ .../EdifactMapper.java} | 68 ++++++----- .../edifact/mapper/ExportResourceMapper.java | 16 +++ .../converter/AbstractCsvConverter.java | 43 +++++++ .../mapper/converter/ClaimCsvConverter.java | 21 ++++ .../mapper/converter/ClaimCsvFields.java | 35 ++++++ .../converter/CompOrderEdiConverter.java} | 12 +- .../converter/CompPoLineEdiConverter.java} | 24 ++-- .../mapper/converter/ExtractableField.java | 9 ++ .../edifact/services/OrdersService.java | 10 ++ .../edifact/utils/ExportUtils.java | 42 +++++++ .../folio/dew/client/OrdersStorageClient.java | 5 + .../acquisitions/edifact/ClaimCsvEntry.java | 31 +++++ ...actExportHolder.java => ExportHolder.java} | 2 +- .../java/org/folio/dew/utils/CsvHelper.java | 19 ++++ .../resources/swagger.api/order-export.yaml | 2 + .../java/org/folio/dew/BaseBatchTest.java | 2 +- .../jobs/MapToEdifactClaimsTaskletTest.java | 2 +- .../jobs/MapToEdifactOrderTaskletTest.java | 7 +- .../jobs/MapToEdifactTaskletAbstractTest.java | 6 +- .../edifact/mapper/CsvMapperTest.java | 99 ++++++++++++++++ .../EdifactMapperTest.java} | 107 +++++------------- .../ConfigurationServiceTest.java | 3 +- .../ExpenseClassServiceTest.java | 3 +- .../{ => services}/HoldingServiceTest.java | 26 +++-- .../IdentifierTypeServiceTest.java | 27 +++-- .../{ => services}/LocationServiceTest.java | 3 +- .../MaterialTypeServiceTest.java | 3 +- .../{ => services}/OrderServiceTest.java | 3 +- .../acquisitions/csv_claims_result.csv | 8 ++ .../acquisitions/edifact_claims_result.edi | 7 +- ...minimalistic_composite_purchase_order.json | 1 + .../acquisitions/pieces_collection.json | 1 - .../acquisitions/pieces_collection_mixed.json | 41 ++++++- .../edifact/edifactOrdersExport.json | 1 + 41 files changed, 665 insertions(+), 236 deletions(-) create mode 100644 src/main/java/org/folio/dew/batch/acquisitions/edifact/config/AcquisitionExportConfig.java delete mode 100644 src/main/java/org/folio/dew/batch/acquisitions/edifact/config/EdifactPurchaseOrderConfig.java create mode 100644 src/main/java/org/folio/dew/batch/acquisitions/edifact/mapper/CsvMapper.java rename src/main/java/org/folio/dew/batch/acquisitions/edifact/{PurchaseOrdersToEdifactMapper.java => mapper/EdifactMapper.java} (58%) create mode 100644 src/main/java/org/folio/dew/batch/acquisitions/edifact/mapper/ExportResourceMapper.java create mode 100644 src/main/java/org/folio/dew/batch/acquisitions/edifact/mapper/converter/AbstractCsvConverter.java create mode 100644 src/main/java/org/folio/dew/batch/acquisitions/edifact/mapper/converter/ClaimCsvConverter.java create mode 100644 src/main/java/org/folio/dew/batch/acquisitions/edifact/mapper/converter/ClaimCsvFields.java rename src/main/java/org/folio/dew/batch/acquisitions/edifact/{CompositePOConverter.java => mapper/converter/CompOrderEdiConverter.java} (94%) rename src/main/java/org/folio/dew/batch/acquisitions/edifact/{CompositePOLineConverter.java => mapper/converter/CompPoLineEdiConverter.java} (95%) create mode 100644 src/main/java/org/folio/dew/batch/acquisitions/edifact/mapper/converter/ExtractableField.java create mode 100644 src/main/java/org/folio/dew/batch/acquisitions/edifact/utils/ExportUtils.java create mode 100644 src/main/java/org/folio/dew/domain/dto/acquisitions/edifact/ClaimCsvEntry.java rename src/main/java/org/folio/dew/domain/dto/acquisitions/edifact/{EdifactExportHolder.java => ExportHolder.java} (65%) create mode 100644 src/test/java/org/folio/dew/batch/acquisitions/edifact/mapper/CsvMapperTest.java rename src/test/java/org/folio/dew/batch/acquisitions/edifact/{MappingOrdersToEdifactTest.java => mapper/EdifactMapperTest.java} (62%) rename src/test/java/org/folio/dew/batch/acquisitions/edifact/{ => services}/ConfigurationServiceTest.java (90%) rename src/test/java/org/folio/dew/batch/acquisitions/edifact/{ => services}/ExpenseClassServiceTest.java (84%) rename src/test/java/org/folio/dew/batch/acquisitions/edifact/{ => services}/HoldingServiceTest.java (78%) rename src/test/java/org/folio/dew/batch/acquisitions/edifact/{ => services}/IdentifierTypeServiceTest.java (56%) rename src/test/java/org/folio/dew/batch/acquisitions/edifact/{ => services}/LocationServiceTest.java (80%) rename src/test/java/org/folio/dew/batch/acquisitions/edifact/{ => services}/MaterialTypeServiceTest.java (80%) rename src/test/java/org/folio/dew/batch/acquisitions/edifact/{ => services}/OrderServiceTest.java (96%) create mode 100644 src/test/resources/edifact/acquisitions/csv_claims_result.csv diff --git a/folio-export-common b/folio-export-common index 839deafc2..7e5cbf2f3 160000 --- a/folio-export-common +++ b/folio-export-common @@ -1 +1 @@ -Subproject commit 839deafc288914ba7bcc58a8c598b369f4af466c +Subproject commit 7e5cbf2f3f8188f01d7e0da16d51a2819632b848 diff --git a/src/main/java/org/folio/dew/batch/acquisitions/edifact/config/AcquisitionExportConfig.java b/src/main/java/org/folio/dew/batch/acquisitions/edifact/config/AcquisitionExportConfig.java new file mode 100644 index 000000000..206b256c4 --- /dev/null +++ b/src/main/java/org/folio/dew/batch/acquisitions/edifact/config/AcquisitionExportConfig.java @@ -0,0 +1,44 @@ +package org.folio.dew.batch.acquisitions.edifact.config; + +import org.folio.dew.batch.acquisitions.edifact.mapper.CsvMapper; +import org.folio.dew.batch.acquisitions.edifact.mapper.ExportResourceMapper; +import org.folio.dew.batch.acquisitions.edifact.mapper.converter.CompOrderEdiConverter; +import org.folio.dew.batch.acquisitions.edifact.mapper.converter.CompPoLineEdiConverter; +import org.folio.dew.batch.acquisitions.edifact.mapper.EdifactMapper; +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.batch.acquisitions.edifact.services.OrdersService; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ComponentScan({ "org.folio.dew.batch.acquisitions.edifact" }) +public class AcquisitionExportConfig { + + @Bean + CompPoLineEdiConverter compositePOLineConverter(IdentifierTypeService identifierTypeService, MaterialTypeService materialTypeService, + ExpenseClassService expenseClassService, LocationService locationService, HoldingService holdingService) { + return new CompPoLineEdiConverter(identifierTypeService, materialTypeService, expenseClassService, locationService, holdingService); + } + + @Bean + CompOrderEdiConverter compositePurchaseOrderConverter(CompPoLineEdiConverter compPoLineEdiConverter, ConfigurationService configurationService) { + return new CompOrderEdiConverter(compPoLineEdiConverter, configurationService); + } + + @Bean + ExportResourceMapper edifactMapper(CompOrderEdiConverter compOrderEdiConverter) { + return new EdifactMapper(compOrderEdiConverter); + } + + @Bean + ExportResourceMapper csvMapper(OrdersService ordersService) { + return new CsvMapper(ordersService); + } + +} 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 deleted file mode 100644 index f7dbbf7c2..000000000 --- a/src/main/java/org/folio/dew/batch/acquisitions/edifact/config/EdifactPurchaseOrderConfig.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.folio.dew.batch.acquisitions.edifact.config; - -import org.folio.dew.batch.acquisitions.edifact.CompositePOConverter; -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; - -@Configuration -@ComponentScan({ "org.folio.dew.batch.acquisitions.edifact" }) -public class EdifactPurchaseOrderConfig { - @Bean - CompositePOLineConverter compositePOLineConverter(IdentifierTypeService identifierTypeService, MaterialTypeService materialTypeService, - ExpenseClassService expenseClassService, LocationService locationService, HoldingService holdingService) { - return new CompositePOLineConverter(identifierTypeService, materialTypeService, expenseClassService, locationService, holdingService); - } - - @Bean - CompositePOConverter compositePurchaseOrderConverter(CompositePOLineConverter compositePOLineConverter, ConfigurationService configurationService) { - return new CompositePOConverter(compositePOLineConverter, configurationService); - } - - @Bean - PurchaseOrdersToEdifactMapper mappingOrdersToEdifact(CompositePOConverter compositePOConverter) { - return new PurchaseOrdersToEdifactMapper(compositePOConverter); - } -} 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 index a45ffa5f1..23c87ad54 100644 --- 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 @@ -6,11 +6,11 @@ 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.mapper.ExportResourceMapper; 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.domain.dto.acquisitions.edifact.ExportHolder; import org.folio.dew.error.NotFoundException; import org.springframework.batch.core.configuration.annotation.StepScope; import org.springframework.batch.core.scope.context.ChunkContext; @@ -23,12 +23,25 @@ public class MapToEdifactClaimsTasklet extends MapToEdifactTasklet { public static final String CLAIM_PIECE_IDS = "claimPieceIds"; + private final ExportResourceMapper edifactMapper; + private final ExportResourceMapper csvMapper; public MapToEdifactClaimsTasklet(ObjectMapper ediObjectMapper, OrdersService ordersService, - PurchaseOrdersToEdifactMapper purchaseOrdersToEdifactMapper) { - super(ediObjectMapper, ordersService, purchaseOrdersToEdifactMapper); + ExportResourceMapper edifactMapper, ExportResourceMapper csvMapper) { + super(ediObjectMapper, ordersService); + this.edifactMapper = edifactMapper; + this.csvMapper = csvMapper; } + @Override + protected ExportResourceMapper getExportResourceMapper(VendorEdiOrdersExportConfig ediOrdersExportConfig) { + return switch (ediOrdersExportConfig.getFileFormat()) { + case EDI -> edifactMapper; + case CSV -> csvMapper; + }; + } + + @Override protected List getExportConfigMissingFields(VendorEdiOrdersExportConfig ediOrdersExportConfig) { return CollectionUtils.isEmpty(ediOrdersExportConfig.getClaimPieceIds()) ? List.of(CLAIM_PIECE_IDS) @@ -36,7 +49,7 @@ protected List getExportConfigMissingFields(VendorEdiOrdersExportConfig } @Override - protected EdifactExportHolder buildEdifactExportHolder(ChunkContext chunkContext, VendorEdiOrdersExportConfig ediExportConfig, Map jobParameters) { + protected ExportHolder buildEdifactExportHolder(ChunkContext chunkContext, VendorEdiOrdersExportConfig ediExportConfig, Map jobParameters) { var pieces = ordersService.getPiecesByIdsAndReceivingStatus(ediExportConfig.getClaimPieceIds(), Piece.ReceivingStatusEnum.LATE); if (pieces.isEmpty()) { throw new NotFoundException(Piece.class); @@ -44,7 +57,7 @@ protected EdifactExportHolder buildEdifactExportHolder(ChunkContext chunkContext var poLineQuery = convertIdsToCqlQuery(pieces.stream().map(Piece::getPoLineId).toList()); var compOrders = getCompositeOrders(poLineQuery); - return new EdifactExportHolder(compOrders, pieces); + return new ExportHolder(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 index f78df6a0b..0aee36e78 100644 --- 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 @@ -13,13 +13,13 @@ 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.mapper.ExportResourceMapper; 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.folio.dew.domain.dto.acquisitions.edifact.ExportHolder; import org.springframework.batch.core.configuration.annotation.StepScope; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.stereotype.Component; @@ -34,23 +34,31 @@ public class MapToEdifactOrdersTasklet extends MapToEdifactTasklet { private final DataExportSpringClient dataExportSpringClient; + private final ExportResourceMapper edifactMapper; public MapToEdifactOrdersTasklet(ObjectMapper ediObjectMapper, OrdersService ordersService, DataExportSpringClient dataExportSpringClient, - PurchaseOrdersToEdifactMapper purchaseOrdersToEdifactMapper) { - super(ediObjectMapper, ordersService, purchaseOrdersToEdifactMapper); + ExportResourceMapper edifactMapper) { + super(ediObjectMapper, ordersService); + this.edifactMapper = edifactMapper; this.dataExportSpringClient = dataExportSpringClient; } + @Override protected List getExportConfigMissingFields(VendorEdiOrdersExportConfig ediOrdersExportConfig) { return List.of(); } @Override - protected EdifactExportHolder buildEdifactExportHolder(ChunkContext chunkContext, VendorEdiOrdersExportConfig ediExportConfig, Map jobParameters) { + protected ExportHolder buildEdifactExportHolder(ChunkContext chunkContext, VendorEdiOrdersExportConfig ediExportConfig, Map jobParameters) { var poLineQuery = getPoLineQuery(ediExportConfig); var compOrders = getCompositeOrders(poLineQuery); - return new EdifactExportHolder(compOrders, List.of()); + return new ExportHolder(compOrders, List.of()); + } + + @Override + protected ExportResourceMapper getExportResourceMapper(VendorEdiOrdersExportConfig ediOrdersExportConfig) { + return edifactMapper; } protected String getPoLineQuery(VendorEdiOrdersExportConfig ediConfig) { 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 d5fcd6b16..7f956074f 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 @@ -11,9 +11,9 @@ 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.mapper.ExportResourceMapper; import org.folio.dew.batch.acquisitions.edifact.services.OrdersService; import org.folio.dew.domain.dto.CompositePoLine; import org.folio.dew.domain.dto.CompositePurchaseOrder; @@ -21,7 +21,7 @@ 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.domain.dto.acquisitions.edifact.ExportHolder; import org.folio.dew.error.NotFoundException; import org.springframework.batch.core.StepContribution; import org.springframework.batch.core.scope.context.ChunkContext; @@ -41,7 +41,6 @@ public abstract class MapToEdifactTasklet implements Tasklet { private final ObjectMapper ediObjectMapper; protected final OrdersService ordersService; - private final PurchaseOrdersToEdifactMapper purchaseOrdersToEdifactMapper; @Override public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { @@ -54,7 +53,7 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon persistPoLineIds(chunkContext, holder.orders()); String jobName = jobParameters.get(JobParameterNames.JOB_NAME).toString(); - var edifactStringResult = purchaseOrdersToEdifactMapper.convertOrdersToEdifact(holder.orders(), holder.pieces(), ediExportConfig, jobName); + var edifactStringResult = getExportResourceMapper(ediExportConfig).convertForExport(holder.orders(), holder.pieces(), ediExportConfig, jobName); // save edifact file content in memory ExecutionContextUtils.addToJobExecutionContext(chunkContext.getStepContext().getStepExecution(), "edifactOrderAsString", edifactStringResult, ""); @@ -122,9 +121,11 @@ private T convertTo(Object value, Class c) { } } + protected abstract ExportResourceMapper getExportResourceMapper(VendorEdiOrdersExportConfig ediOrdersExportConfig); + protected abstract List getExportConfigMissingFields(VendorEdiOrdersExportConfig ediOrdersExportConfig); - protected abstract EdifactExportHolder buildEdifactExportHolder(ChunkContext chunkContext, VendorEdiOrdersExportConfig ediExportConfig, - Map jobParameters) throws JsonProcessingException, EDIStreamException; + protected abstract ExportHolder buildEdifactExportHolder(ChunkContext chunkContext, VendorEdiOrdersExportConfig ediExportConfig, + Map jobParameters) throws JsonProcessingException, EDIStreamException; } diff --git a/src/main/java/org/folio/dew/batch/acquisitions/edifact/mapper/CsvMapper.java b/src/main/java/org/folio/dew/batch/acquisitions/edifact/mapper/CsvMapper.java new file mode 100644 index 000000000..888a71bde --- /dev/null +++ b/src/main/java/org/folio/dew/batch/acquisitions/edifact/mapper/CsvMapper.java @@ -0,0 +1,74 @@ +package org.folio.dew.batch.acquisitions.edifact.mapper; + +import static java.util.stream.Collectors.groupingBy; +import static org.folio.dew.utils.Constants.LINE_BREAK; + +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.tuple.Pair; +import org.folio.dew.batch.acquisitions.edifact.mapper.converter.ClaimCsvConverter; +import org.folio.dew.batch.acquisitions.edifact.services.OrdersService; +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.VendorEdiOrdersExportConfig; +import org.folio.dew.domain.dto.acquisitions.edifact.ClaimCsvEntry; + +public class CsvMapper implements ExportResourceMapper { + + private final OrdersService ordersService; + + public CsvMapper(OrdersService ordersService) { + this.ordersService = ordersService; + } + + @Override + public String convertForExport(List compPOs, List pieces, VendorEdiOrdersExportConfig ediExportConfig, String jobName) { + var claimCsvConverter = new ClaimCsvConverter(); + var csvResult = new StringBuilder(claimCsvConverter.getCsvHeaders()).append(LINE_BREAK); + getClaimEntries(compPOs, pieces).stream() + .map(claimCsvConverter::convertEntryToCsv) + .map(line -> line.concat(LINE_BREAK)) + .forEachOrdered(csvResult::append); + return csvResult.toString(); + } + + private List getClaimEntries(List orders, List pieces) { + // Map each PoLine ID to its corresponding Pieces + var poLineIdToPieces = pieces.stream().collect(groupingBy(Piece::getPoLineId)); + // Map each PoLine ID to its corresponding PoLine + var poLineIdToPoLine = orders.stream().flatMap(order -> order.getCompositePoLines().stream()) + .collect(Collectors.toMap(CompositePoLine::getId, Function.identity())); + // Map each Piece ID to its corresponding Title + var pieceIdToTitle = poLineIdToPieces.entrySet().stream() + .flatMap(entry -> entry.getValue().stream() + .map(piece -> Pair.of(piece.getId(), getTitleById(poLineIdToPoLine.get(entry.getKey()), piece)))) // Pair of Piece ID and Title + .collect(Collectors.toMap(Pair::getLeft, Pair::getRight)); + + // Key extractor for grouping pieces by: Po Line Number, Display Summary, Chronology, Enumeration, and Title + Function keyExtractor = piece -> + new ClaimCsvEntry(poLineIdToPoLine.get(piece.getPoLineId()), piece, pieceIdToTitle.get(piece.getId()), 0); + + // Group pieces by the previously defined key (Overridden equals and hashCode methods in ClaimCsvEntry) + // Only a single piece from each group is used, as they share all necessary attributes + Map claimedPieces = pieces.stream() + .collect(Collectors.groupingBy(keyExtractor, Collectors.counting())); + + // Return a list of ClaimCsvEntry objects, each representing a group of claimed pieces + return claimedPieces.entrySet().stream() + .map(entry -> entry.getKey().withQuantity(entry.getValue())) + .sorted(Comparator.comparing(o -> o.compositePoLine().getPoLineNumber())) + .toList(); + } + + private String getTitleById(CompositePoLine poLine, Piece piece) { + return Boolean.TRUE.equals(poLine.getIsPackage()) + ? ordersService.getTitleById(piece.getTitleId()).getTitle() + : poLine.getTitleOrPackage(); + } + +} diff --git a/src/main/java/org/folio/dew/batch/acquisitions/edifact/PurchaseOrdersToEdifactMapper.java b/src/main/java/org/folio/dew/batch/acquisitions/edifact/mapper/EdifactMapper.java similarity index 58% rename from src/main/java/org/folio/dew/batch/acquisitions/edifact/PurchaseOrdersToEdifactMapper.java rename to src/main/java/org/folio/dew/batch/acquisitions/edifact/mapper/EdifactMapper.java index 3d4443cd0..a08c23fbc 100644 --- a/src/main/java/org/folio/dew/batch/acquisitions/edifact/PurchaseOrdersToEdifactMapper.java +++ b/src/main/java/org/folio/dew/batch/acquisitions/edifact/mapper/EdifactMapper.java @@ -1,4 +1,4 @@ -package org.folio.dew.batch.acquisitions.edifact; +package org.folio.dew.batch.acquisitions.edifact.mapper; import static java.util.stream.Collectors.groupingBy; @@ -8,6 +8,7 @@ import java.util.List; import org.apache.commons.lang3.StringUtils; +import org.folio.dew.batch.acquisitions.edifact.mapper.converter.CompOrderEdiConverter; import org.folio.dew.domain.dto.CompositePurchaseOrder; import io.xlate.edi.stream.EDIOutputFactory; @@ -18,51 +19,46 @@ import org.folio.dew.domain.dto.VendorEdiOrdersExportConfig; import org.folio.dew.domain.dto.acquisitions.edifact.EdiFileConfig; -public class PurchaseOrdersToEdifactMapper { - private final CompositePOConverter compositePOConverter; +public class EdifactMapper implements ExportResourceMapper { + private final CompOrderEdiConverter compOrderEdiConverter; - public PurchaseOrdersToEdifactMapper(CompositePOConverter compositePOConverter) { - this.compositePOConverter = compositePOConverter; + public EdifactMapper(CompOrderEdiConverter compOrderEdiConverter) { + this.compOrderEdiConverter = compOrderEdiConverter; } - 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 { + public String convertForExport(List compPOs, List pieces, VendorEdiOrdersExportConfig ediExportConfig, String jobName) throws EDIStreamException { ByteArrayOutputStream stream = new ByteArrayOutputStream(); EDIOutputFactory factory = EDIOutputFactory.newFactory(); factory.setProperty(EDIOutputFactory.PRETTY_PRINT, true); - EDIStreamWriter writer = factory.createEDIStreamWriter(stream); - - // Count of messages (one message per purchase order) - int messageCount = 0; - writer.startInterchange(); - writeStartFile(writer); - - EdiFileConfig ediFileConfig = new EdiFileConfig(); - ediFileConfig.setFileId(StringUtils.right(jobName, 14)); - ediFileConfig.setLibEdiCode(ediExportConfig.getEdiConfig().getLibEdiCode()); - ediFileConfig.setLibEdiType(ediExportConfig.getEdiConfig().getLibEdiType().getValue().substring(0, 3)); - ediFileConfig.setVendorEdiCode(ediExportConfig.getEdiConfig().getVendorEdiCode()); - ediFileConfig.setVendorEdiType(ediExportConfig.getEdiConfig().getVendorEdiType().getValue().substring(0, 3)); - - writeInterchangeHeader(writer, ediFileConfig); - - var poLineIdToPieces = pieces.stream().collect(groupingBy(Piece::getPoLineId)); - // Purchase orders - for (CompositePurchaseOrder compPO : compPOs) { - compositePOConverter.convertPOtoEdifact(writer, compPO, poLineIdToPieces, ediFileConfig); - messageCount++; + try (EDIStreamWriter writer = factory.createEDIStreamWriter(stream)) { + // Count of messages (one message per purchase order) + int messageCount = 0; + writer.startInterchange(); + writeStartFile(writer); + + EdiFileConfig ediFileConfig = new EdiFileConfig(); + ediFileConfig.setFileId(StringUtils.right(jobName, 14)); + ediFileConfig.setLibEdiCode(ediExportConfig.getEdiConfig().getLibEdiCode()); + ediFileConfig.setLibEdiType(ediExportConfig.getEdiConfig().getLibEdiType().getValue().substring(0, 3)); + ediFileConfig.setVendorEdiCode(ediExportConfig.getEdiConfig().getVendorEdiCode()); + ediFileConfig.setVendorEdiType(ediExportConfig.getEdiConfig().getVendorEdiType().getValue().substring(0, 3)); + + writeInterchangeHeader(writer, ediFileConfig); + + var poLineIdToPieces = pieces.stream().collect(groupingBy(Piece::getPoLineId)); + // Purchase orders + for (CompositePurchaseOrder compPO : compPOs) { + compOrderEdiConverter.convertPOtoEdifact(writer, compPO, poLineIdToPieces, ediFileConfig); + messageCount++; + } + + writeInterchangeFooter(writer, ediFileConfig.getFileId(), messageCount); + writer.endInterchange(); + return stream.toString(); } - writeInterchangeFooter(writer, ediFileConfig.getFileId(), messageCount); - writer.endInterchange(); - writer.close(); - - return stream.toString(); } // Start of file - Can contain multiple order messages diff --git a/src/main/java/org/folio/dew/batch/acquisitions/edifact/mapper/ExportResourceMapper.java b/src/main/java/org/folio/dew/batch/acquisitions/edifact/mapper/ExportResourceMapper.java new file mode 100644 index 000000000..3cc4397ba --- /dev/null +++ b/src/main/java/org/folio/dew/batch/acquisitions/edifact/mapper/ExportResourceMapper.java @@ -0,0 +1,16 @@ +package org.folio.dew.batch.acquisitions.edifact.mapper; + +import java.util.List; + +import org.folio.dew.domain.dto.CompositePurchaseOrder; +import org.folio.dew.domain.dto.Piece; +import org.folio.dew.domain.dto.VendorEdiOrdersExportConfig; + +import io.xlate.edi.stream.EDIStreamException; + +public interface ExportResourceMapper { + + String convertForExport(List compPOs, List pieces, + VendorEdiOrdersExportConfig ediExportConfig, String jobName) throws EDIStreamException; + +} diff --git a/src/main/java/org/folio/dew/batch/acquisitions/edifact/mapper/converter/AbstractCsvConverter.java b/src/main/java/org/folio/dew/batch/acquisitions/edifact/mapper/converter/AbstractCsvConverter.java new file mode 100644 index 000000000..80f13bc15 --- /dev/null +++ b/src/main/java/org/folio/dew/batch/acquisitions/edifact/mapper/converter/AbstractCsvConverter.java @@ -0,0 +1,43 @@ +package org.folio.dew.batch.acquisitions.edifact.mapper.converter; + +import static org.folio.dew.utils.Constants.COMMA; +import static org.folio.dew.utils.Constants.QUOTE; + +import java.util.Arrays; +import java.util.Optional; + +import org.folio.dew.utils.CsvHelper; +import org.springframework.batch.item.file.transform.DelimitedLineAggregator; + +public abstract class AbstractCsvConverter { + + private final DelimitedLineAggregator lineAggregator; + private final String delimiter; + + protected AbstractCsvConverter() { + this(COMMA); + } + + protected AbstractCsvConverter(String delimiter) { + this.delimiter = delimiter; + this.lineAggregator = new DelimitedLineAggregator<>(); + lineAggregator.setDelimiter(delimiter); + } + + public String getCsvHeaders() { + return lineAggregator.doAggregate(getHeaders()); + } + + public String convertEntryToCsv(T entry) { + return lineAggregator.doAggregate(Arrays.stream(getFields()) + .map(field -> field.extract(entry)) + .map(value -> Optional.ofNullable(value).orElse("")) + .map(field -> CsvHelper.escapeDelimiter(field, delimiter, QUOTE)) + .toArray()); + } + + protected abstract ExtractableField[] getFields(); + + protected abstract String[] getHeaders(); + +} diff --git a/src/main/java/org/folio/dew/batch/acquisitions/edifact/mapper/converter/ClaimCsvConverter.java b/src/main/java/org/folio/dew/batch/acquisitions/edifact/mapper/converter/ClaimCsvConverter.java new file mode 100644 index 000000000..e3c6941d6 --- /dev/null +++ b/src/main/java/org/folio/dew/batch/acquisitions/edifact/mapper/converter/ClaimCsvConverter.java @@ -0,0 +1,21 @@ +package org.folio.dew.batch.acquisitions.edifact.mapper.converter; + +import java.util.Arrays; + +import org.folio.dew.domain.dto.acquisitions.edifact.ClaimCsvEntry; + +public class ClaimCsvConverter extends AbstractCsvConverter { + + @Override + protected ExtractableField[] getFields() { + return ClaimCsvFields.values(); + } + + @Override + protected String[] getHeaders() { + return Arrays.stream(ClaimCsvFields.values()) + .map(ClaimCsvFields::getName) + .toArray(String[]::new); + } + +} diff --git a/src/main/java/org/folio/dew/batch/acquisitions/edifact/mapper/converter/ClaimCsvFields.java b/src/main/java/org/folio/dew/batch/acquisitions/edifact/mapper/converter/ClaimCsvFields.java new file mode 100644 index 000000000..1ea87c6f3 --- /dev/null +++ b/src/main/java/org/folio/dew/batch/acquisitions/edifact/mapper/converter/ClaimCsvFields.java @@ -0,0 +1,35 @@ +package org.folio.dew.batch.acquisitions.edifact.mapper.converter; + +import static org.folio.dew.batch.acquisitions.edifact.utils.ExportUtils.getVendorAccountNumber; +import static org.folio.dew.batch.acquisitions.edifact.utils.ExportUtils.getVendorOrderNumber; + +import java.util.function.Function; + +import org.folio.dew.domain.dto.acquisitions.edifact.ClaimCsvEntry; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +public enum ClaimCsvFields implements ExtractableField { + + POL_NUMBER("POL number", entry -> entry.compositePoLine().getPoLineNumber()), + ORDER_NUMBER("Vendor order number", entry -> getVendorOrderNumber(entry.compositePoLine())), + ACCOUNT_NUMBER("Account number", entry -> getVendorAccountNumber(entry.compositePoLine())), + TITLE("Title from piece", ClaimCsvEntry::title), + DISPLAY_SUMMARY("Display summary", entry -> entry.piece().getDisplaySummary()), + CHRONOLOGY("Chronology", entry -> entry.piece().getChronology()), + ENUMERATION("Enumeration", entry -> entry.piece().getEnumeration()), + QUANTITY("Quantity", entry -> String.valueOf(entry.quantity())); + + @Getter + private final String name; + private final Function extractor; + + + @Override + public String extract(ClaimCsvEntry entry) { + return extractor.apply(entry); + } + +} diff --git a/src/main/java/org/folio/dew/batch/acquisitions/edifact/CompositePOConverter.java b/src/main/java/org/folio/dew/batch/acquisitions/edifact/mapper/converter/CompOrderEdiConverter.java similarity index 94% rename from src/main/java/org/folio/dew/batch/acquisitions/edifact/CompositePOConverter.java rename to src/main/java/org/folio/dew/batch/acquisitions/edifact/mapper/converter/CompOrderEdiConverter.java index 0b7e8b92a..8b8641b21 100644 --- a/src/main/java/org/folio/dew/batch/acquisitions/edifact/CompositePOConverter.java +++ b/src/main/java/org/folio/dew/batch/acquisitions/edifact/mapper/converter/CompOrderEdiConverter.java @@ -1,4 +1,4 @@ -package org.folio.dew.batch.acquisitions.edifact; +package org.folio.dew.batch.acquisitions.edifact.mapper.converter; import io.xlate.edi.stream.EDIStreamException; import io.xlate.edi.stream.EDIStreamWriter; @@ -16,15 +16,15 @@ import java.util.List; import java.util.Map; -public class CompositePOConverter { +public class CompOrderEdiConverter { private static final String RUSH_ORDER = "224"; private static final String NOT_RUSH_ORDER = "220"; - private final CompositePOLineConverter compositePOLineConverter; + private final CompPoLineEdiConverter compPoLineEdiConverter; private final ConfigurationService configurationService; - public CompositePOConverter(CompositePOLineConverter compositePOLineConverter, ConfigurationService configurationService) { - this.compositePOLineConverter = compositePOLineConverter; + public CompOrderEdiConverter(CompPoLineEdiConverter compPoLineEdiConverter, ConfigurationService configurationService) { + this.compPoLineEdiConverter = compPoLineEdiConverter; this.configurationService = configurationService; } @@ -70,7 +70,7 @@ public void convertPOtoEdifact(EDIStreamWriter writer, CompositePurchaseOrder co for (CompositePoLine poLine : compPO.getCompositePoLines()) { int quantityOrdered = getPoLineQuantityOrdered(poLine); var pieces = poLineToPieces.getOrDefault(poLine.getId(), List.of()); - int segments = compositePOLineConverter.convertPOLine(poLine, pieces, writer, ++totalNumberOfLineItems, quantityOrdered); + int segments = compPoLineEdiConverter.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/mapper/converter/CompPoLineEdiConverter.java similarity index 95% rename from src/main/java/org/folio/dew/batch/acquisitions/edifact/CompositePOLineConverter.java rename to src/main/java/org/folio/dew/batch/acquisitions/edifact/mapper/converter/CompPoLineEdiConverter.java index cb09bac3e..d3ecc1e50 100644 --- a/src/main/java/org/folio/dew/batch/acquisitions/edifact/CompositePOLineConverter.java +++ b/src/main/java/org/folio/dew/batch/acquisitions/edifact/mapper/converter/CompPoLineEdiConverter.java @@ -1,6 +1,7 @@ -package org.folio.dew.batch.acquisitions.edifact; +package org.folio.dew.batch.acquisitions.edifact.mapper.converter; -import static org.folio.dew.domain.dto.ReferenceNumberItem.RefNumberTypeEnum.ORDER_REFERENCE_NUMBER; +import static org.folio.dew.batch.acquisitions.edifact.utils.ExportUtils.getVendorOrderNumber; +import static org.folio.dew.batch.acquisitions.edifact.utils.ExportUtils.getVendorReferenceNumbers; import javax.money.CurrencyUnit; import javax.money.Monetary; @@ -25,7 +26,6 @@ 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 java.math.BigDecimal; @@ -33,9 +33,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Optional; -public class CompositePOLineConverter { +public class CompPoLineEdiConverter { private static final int MAX_CHARS_PER_LINE = 70; private static final int MAX_NUMBER_OF_REFS = 10; private static final String PRODUCT_ID_FUNCTION_CODE_MAIN_PRODUCT_IDNTIFICATION = "5"; @@ -57,8 +56,8 @@ public class CompositePOLineConverter { private final LocationService locationService; private final HoldingService holdingService; - public CompositePOLineConverter(IdentifierTypeService identifierTypeService, MaterialTypeService materialTypeService, - ExpenseClassService expenseClassService, LocationService locationService, HoldingService holdingService) { + public CompPoLineEdiConverter(IdentifierTypeService identifierTypeService, MaterialTypeService materialTypeService, + ExpenseClassService expenseClassService, LocationService locationService, HoldingService holdingService) { this.identifierTypeService = identifierTypeService; this.materialTypeService = materialTypeService; this.expenseClassService = expenseClassService; @@ -512,17 +511,8 @@ 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); + var vendorOrderNumber = getVendorOrderNumber(referenceNumberItems); referenceNumberItems.remove(vendorOrderNumber); return vendorOrderNumber; } diff --git a/src/main/java/org/folio/dew/batch/acquisitions/edifact/mapper/converter/ExtractableField.java b/src/main/java/org/folio/dew/batch/acquisitions/edifact/mapper/converter/ExtractableField.java new file mode 100644 index 000000000..e8652c83c --- /dev/null +++ b/src/main/java/org/folio/dew/batch/acquisitions/edifact/mapper/converter/ExtractableField.java @@ -0,0 +1,9 @@ +package org.folio.dew.batch.acquisitions.edifact.mapper.converter; + +public interface ExtractableField { + + String getName(); + + R extract(T item); + +} 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 fe0ba2d2f..a9ce66d87 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 @@ -4,10 +4,12 @@ import static org.folio.dew.utils.QueryUtils.convertIdsToCqlQuery; import org.folio.dew.client.OrdersStorageClient; +import org.folio.dew.domain.dto.OrdersTitle; 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.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import lombok.RequiredArgsConstructor; @@ -71,4 +73,12 @@ public List getPiecesByIdsAndReceivingStatus(List pieceIds, Piece return pieces; } + @Cacheable("titleIds") + public OrdersTitle getTitleById(String titleId) { + log.debug("getTitleById: Fetching title: {}", titleId); + var title = ordersStorageClient.getTitleById(titleId); + log.debug("getTitleById:: Fetched title: {}", title); + return title; + } + } diff --git a/src/main/java/org/folio/dew/batch/acquisitions/edifact/utils/ExportUtils.java b/src/main/java/org/folio/dew/batch/acquisitions/edifact/utils/ExportUtils.java new file mode 100644 index 000000000..03b05d55d --- /dev/null +++ b/src/main/java/org/folio/dew/batch/acquisitions/edifact/utils/ExportUtils.java @@ -0,0 +1,42 @@ +package org.folio.dew.batch.acquisitions.edifact.utils; + +import static org.folio.dew.domain.dto.ReferenceNumberItem.RefNumberTypeEnum.ORDER_REFERENCE_NUMBER; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.folio.dew.domain.dto.CompositePoLine; +import org.folio.dew.domain.dto.ReferenceNumberItem; +import org.folio.dew.domain.dto.VendorDetail; + +public class ExportUtils { + + private ExportUtils() { } + + public static List getVendorReferenceNumbers(CompositePoLine poLine) { + return Optional.ofNullable(poLine.getVendorDetail()) + .map(VendorDetail::getReferenceNumbers) + .orElse(new ArrayList<>()); + } + + public static ReferenceNumberItem getVendorOrderNumber(List referenceNumberItems) { + return Optional.ofNullable(referenceNumberItems).orElse(List.of()).stream() + .filter(r -> r.getRefNumberType() == ORDER_REFERENCE_NUMBER) + .findFirst() + .orElse(null); + } + + public static String getVendorOrderNumber(CompositePoLine poLine) { + return Optional.ofNullable(getVendorOrderNumber(getVendorReferenceNumbers(poLine))) + .map(ReferenceNumberItem::getRefNumber) + .orElse(null); + } + + public static String getVendorAccountNumber(CompositePoLine poLine) { + return Optional.ofNullable(poLine.getVendorDetail()) + .map(VendorDetail::getVendorAccount) + .orElse(null); + } + +} diff --git a/src/main/java/org/folio/dew/client/OrdersStorageClient.java b/src/main/java/org/folio/dew/client/OrdersStorageClient.java index a77edbf96..a3c6ddff8 100644 --- a/src/main/java/org/folio/dew/client/OrdersStorageClient.java +++ b/src/main/java/org/folio/dew/client/OrdersStorageClient.java @@ -1,11 +1,13 @@ package org.folio.dew.client; +import org.folio.dew.domain.dto.OrdersTitle; 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; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestParam; @FeignClient(name = "orders-storage") @@ -32,4 +34,7 @@ PieceCollection getPiecesByQuery( @RequestParam("limit") int limit ); + @GetMapping(value = "/titles/{id}", produces = MediaType.APPLICATION_JSON_VALUE) + OrdersTitle getTitleById(@PathVariable("id") String id); + } diff --git a/src/main/java/org/folio/dew/domain/dto/acquisitions/edifact/ClaimCsvEntry.java b/src/main/java/org/folio/dew/domain/dto/acquisitions/edifact/ClaimCsvEntry.java new file mode 100644 index 000000000..ef5d48d94 --- /dev/null +++ b/src/main/java/org/folio/dew/domain/dto/acquisitions/edifact/ClaimCsvEntry.java @@ -0,0 +1,31 @@ +package org.folio.dew.domain.dto.acquisitions.edifact; + +import java.util.Objects; + +import org.folio.dew.domain.dto.CompositePoLine; +import org.folio.dew.domain.dto.Piece; + +public record ClaimCsvEntry(CompositePoLine compositePoLine, Piece piece, String title, long quantity) { + + public ClaimCsvEntry withQuantity(long quantity) { + return new ClaimCsvEntry(compositePoLine, piece, title, quantity); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ClaimCsvEntry that = (ClaimCsvEntry) o; + return Objects.equals(compositePoLine.getPoLineNumber(), that.compositePoLine.getPoLineNumber()) + && Objects.equals(piece.getDisplaySummary(), that.piece.getDisplaySummary()) + && Objects.equals(piece.getChronology(), that.piece.getChronology()) + && Objects.equals(piece.getEnumeration(), that.piece.getEnumeration()) + && Objects.equals(title, that.title); + } + + @Override + public int hashCode() { + return Objects.hash(compositePoLine.getPoLineNumber(), piece.getDisplaySummary(), piece.getChronology(), piece.getEnumeration(), title); + } + +} 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/ExportHolder.java similarity index 65% rename from src/main/java/org/folio/dew/domain/dto/acquisitions/edifact/EdifactExportHolder.java rename to src/main/java/org/folio/dew/domain/dto/acquisitions/edifact/ExportHolder.java index 8108aed51..6fc6b79eb 100644 --- a/src/main/java/org/folio/dew/domain/dto/acquisitions/edifact/EdifactExportHolder.java +++ b/src/main/java/org/folio/dew/domain/dto/acquisitions/edifact/ExportHolder.java @@ -5,6 +5,6 @@ import org.folio.dew.domain.dto.CompositePurchaseOrder; import org.folio.dew.domain.dto.Piece; -public record EdifactExportHolder(List orders, List pieces) { +public record ExportHolder(List orders, List pieces) { } diff --git a/src/main/java/org/folio/dew/utils/CsvHelper.java b/src/main/java/org/folio/dew/utils/CsvHelper.java index c2d1aa3ae..724f73f99 100644 --- a/src/main/java/org/folio/dew/utils/CsvHelper.java +++ b/src/main/java/org/folio/dew/utils/CsvHelper.java @@ -1,6 +1,7 @@ package org.folio.dew.utils; import static org.folio.dew.utils.Constants.LINE_BREAK; +import static org.folio.dew.utils.Constants.LINE_BREAK_REPLACEMENT; import com.opencsv.bean.CsvToBeanBuilder; import com.opencsv.bean.StatefulBeanToCsvBuilder; @@ -8,6 +9,8 @@ import com.opencsv.exceptions.CsvRequiredFieldEmptyException; import lombok.experimental.UtilityClass; import lombok.extern.log4j.Log4j2; + +import org.apache.commons.lang3.StringUtils; import org.folio.dew.repository.BaseFilesStorage; import java.io.BufferedReader; @@ -87,4 +90,20 @@ public static long countLines(R storage, String pat return lines.count(); } } + + + /** + * Escapes delimiter in the value with escape character. If value contains delimiter, line break or escape character, + * it will be escaped - as instructed in RFC 4180 + * + * @param value value to process + * @param delimiter delimiter to escape + * @param escape escape character + * @return escaped value + */ + public static String escapeDelimiter(String value, String delimiter, String escape) { + return StringUtils.isNotBlank(value) && value.contains(delimiter) + ? escape + value.replace(escape, escape + escape).replace(LINE_BREAK, LINE_BREAK_REPLACEMENT) + escape + : value; + } } diff --git a/src/main/resources/swagger.api/order-export.yaml b/src/main/resources/swagger.api/order-export.yaml index 31ee4e1fa..062524990 100644 --- a/src/main/resources/swagger.api/order-export.yaml +++ b/src/main/resources/swagger.api/order-export.yaml @@ -65,3 +65,5 @@ components: $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' + ordersTitle: + $ref: '../../../../folio-export-common/schemas/acquisitions/mod-orders-storage/title.json#/OrdersTitle' diff --git a/src/test/java/org/folio/dew/BaseBatchTest.java b/src/test/java/org/folio/dew/BaseBatchTest.java index 6efc6f870..430fed85c 100644 --- a/src/test/java/org/folio/dew/BaseBatchTest.java +++ b/src/test/java/org/folio/dew/BaseBatchTest.java @@ -91,7 +91,7 @@ public abstract class BaseBatchTest { public static final int WIRE_MOCK_PORT = TestSocketUtils.findAvailableTcpPort(); public static WireMockServer wireMockServer; - public static PostgreSQLContainer postgreDBContainer = new PostgreSQLContainer<>("postgres:13"); + public static PostgreSQLContainer postgreDBContainer = new PostgreSQLContainer<>("postgres:16"); @Autowired public MockMvc mockMvc; 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 index 5f05296cb..7fcc88db0 100644 --- 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 @@ -66,7 +66,7 @@ void testEdifactClaimsExport() throws Exception { doReturn(poLines).when(ordersService).getPoLinesByQuery(poLineQuery); doReturn(orders).when(ordersService).getPurchaseOrdersByIds(anyList()); - doReturn("test1").when(purchaseOrdersToEdifactMapper).convertOrdersToEdifact(any(), any(), anyString()); + doReturn("test1").when(edifactMapper).convertForExport(any(), any(), any(), anyString()); var exportConfig = getEdifactExportConfig(SAMPLE_EDI_ORDERS_EXPORT, pieceIds); JobExecution jobExecution = testLauncher.launchStep(MAP_TO_EDIFACT_STEP, getJobParameters(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 index cbd9b14ba..0e94b744b 100644 --- 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 @@ -46,7 +46,6 @@ protected void 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 @@ -61,7 +60,7 @@ void testEdifactOrdersExport() throws Exception { " 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()); + doReturn("test1").when(edifactMapper).convertForExport(any(), any(), any(), anyString()); JobExecution jobExecution = testLauncher.launchStep(MAP_TO_EDIFACT_STEP, getJobParameters(getEdifactExportConfig(SAMPLE_EDI_ORDERS_EXPORT))); @@ -86,7 +85,7 @@ void testEdifactOrdersExportDefaultConfig() throws Exception { 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()); + doReturn("test1").when(edifactMapper).convertForExport(any(), any(), any(), anyString()); var exportConfig = getEdifactExportConfig(SAMPLE_EDI_ORDERS_EXPORT, true); JobExecution jobExecution = testLauncher.launchStep(MAP_TO_EDIFACT_STEP, getJobParameters(exportConfig)); @@ -112,7 +111,7 @@ void testEdifactOrdersExportDefaultConfigWithTwoExportConfigs() throws Exception 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()); + doReturn("test1").when(edifactMapper).convertForExport(any(), any(), any(), anyString()); var exportConfig = getEdifactExportConfig(SAMPLE_EDI_ORDERS_EXPORT, true); JobExecution jobExecution = testLauncher.launchStep(MAP_TO_EDIFACT_STEP, getJobParameters(exportConfig)); 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 index 00a9b0753..530d1c74d 100644 --- 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 @@ -14,7 +14,7 @@ import java.util.UUID; import org.folio.dew.BaseBatchTest; -import org.folio.dew.batch.acquisitions.edifact.PurchaseOrdersToEdifactMapper; +import org.folio.dew.batch.acquisitions.edifact.mapper.ExportResourceMapper; import org.folio.dew.batch.acquisitions.edifact.services.OrdersService; import org.folio.dew.client.DataExportSpringClient; import org.junit.jupiter.api.Test; @@ -26,6 +26,7 @@ import org.springframework.batch.core.JobParametersBuilder; 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 com.fasterxml.jackson.databind.ObjectMapper; @@ -46,7 +47,8 @@ abstract class MapToEdifactTaskletAbstractTest extends BaseBatchTest { @MockBean protected DataExportSpringClient dataExportSpringClient; @MockBean - protected PurchaseOrdersToEdifactMapper purchaseOrdersToEdifactMapper; + @Qualifier("edifactMapper") + protected ExportResourceMapper edifactMapper; @Autowired protected ObjectMapper objectMapper; protected Job edifactExportJob; diff --git a/src/test/java/org/folio/dew/batch/acquisitions/edifact/mapper/CsvMapperTest.java b/src/test/java/org/folio/dew/batch/acquisitions/edifact/mapper/CsvMapperTest.java new file mode 100644 index 000000000..0b59b0aa5 --- /dev/null +++ b/src/test/java/org/folio/dew/batch/acquisitions/edifact/mapper/CsvMapperTest.java @@ -0,0 +1,99 @@ +package org.folio.dew.batch.acquisitions.edifact.mapper; + +import static org.folio.dew.domain.dto.ExportType.CLAIMS; +import static org.folio.dew.utils.TestUtils.getMockData; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import lombok.extern.log4j.Log4j2; + +import org.folio.dew.batch.acquisitions.edifact.services.OrdersService; +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.OrdersTitle; +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.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.junit.jupiter.MockitoExtension; + +@Log4j2 +@ExtendWith(MockitoExtension.class) +class CsvMapperTest { + + private static final Map EXPORT_CSV_PATHS = Map.of( + CLAIMS, "edifact/acquisitions/csv_claims_result.csv" + ); + + private ObjectMapper objectMapper; + private ExportResourceMapper csvMapper; + + @Mock + private OrdersService ordersService; + + @BeforeEach + void setUp() { + csvMapper = new CsvMapper(ordersService); + objectMapper = new JacksonConfiguration().objectMapper(); + + when(ordersService.getTitleById(anyString())).thenReturn(new OrdersTitle().title("Test title")); + } + + @ParameterizedTest + @EnumSource(value = ExportType.class, names = {"CLAIMS"}) + void testConvertForExportCsv(ExportType type) throws Exception { + String jobName = "123456789012345"; + List compPOs = getTestOrdersFromJson(type); + List pieces = getTestPiecesFromJson(type); + + String csvOutput = csvMapper.convertForExport(compPOs, pieces, getTestEdiConfig(), jobName); + + assertFalse(csvOutput.isEmpty()); + validateCsvOutput(type, csvOutput); + + byte[] csvOutputBytes = csvOutput.getBytes(StandardCharsets.UTF_8); + assertNotNull(csvOutputBytes); + validateCsvOutput(type, new String(csvOutputBytes)); + } + + private VendorEdiOrdersExportConfig getTestEdiConfig() throws IOException { + return objectMapper.readValue(getMockData("edifact/acquisitions/vendorEdiOrdersExportConfig.json"), VendorEdiOrdersExportConfig.class); + } + + private List getTestOrdersFromJson(ExportType type) throws IOException { + List compPOs = new ArrayList<>(); + if (type == CLAIMS) { + 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)); + } + return compPOs; + } + + private List getTestPiecesFromJson(ExportType type) throws IOException { + return type == CLAIMS + ? objectMapper.readValue(getMockData("edifact/acquisitions/pieces_collection_mixed.json"), PieceCollection.class).getPieces() + : List.of(); + } + + private void validateCsvOutput(ExportType type, String csvOutput) throws IOException { + log.info("Generated CSV file:\n{}", csvOutput); + String csvExpected = getMockData(EXPORT_CSV_PATHS.get(type)); + assertEquals(csvExpected, csvOutput); + } + +} diff --git a/src/test/java/org/folio/dew/batch/acquisitions/edifact/MappingOrdersToEdifactTest.java b/src/test/java/org/folio/dew/batch/acquisitions/edifact/mapper/EdifactMapperTest.java similarity index 62% rename from src/test/java/org/folio/dew/batch/acquisitions/edifact/MappingOrdersToEdifactTest.java rename to src/test/java/org/folio/dew/batch/acquisitions/edifact/mapper/EdifactMapperTest.java index 2d92ef9ee..e079f5401 100644 --- a/src/test/java/org/folio/dew/batch/acquisitions/edifact/MappingOrdersToEdifactTest.java +++ b/src/test/java/org/folio/dew/batch/acquisitions/edifact/mapper/EdifactMapperTest.java @@ -1,7 +1,8 @@ -package org.folio.dew.batch.acquisitions.edifact; +package org.folio.dew.batch.acquisitions.edifact.mapper; import static org.folio.dew.domain.dto.ExportType.CLAIMS; import static org.folio.dew.domain.dto.ExportType.EDIFACT_ORDERS_EXPORT; +import static org.folio.dew.utils.TestUtils.getMockData; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -9,19 +10,19 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; -import java.io.InputStream; import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -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.batch.acquisitions.edifact.mapper.ExportResourceMapper; +import org.folio.dew.batch.acquisitions.edifact.mapper.converter.CompOrderEdiConverter; +import org.folio.dew.batch.acquisitions.edifact.mapper.converter.CompPoLineEdiConverter; +import org.folio.dew.batch.acquisitions.edifact.mapper.EdifactMapper; 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; @@ -39,12 +40,11 @@ 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; @Log4j2 @ExtendWith(MockitoExtension.class) -class MappingOrdersToEdifactTest { +class EdifactMapperTest { private static final Map EXPORT_EDI_PATHS = Map.of( EDIFACT_ORDERS_EXPORT,"edifact/acquisitions/edifact_orders_result.edi", @@ -52,7 +52,7 @@ class MappingOrdersToEdifactTest { ); private ObjectMapper objectMapper; - private PurchaseOrdersToEdifactMapper purchaseOrdersToEdifactMapper; + private ExportResourceMapper edifactMapper; @Mock private IdentifierTypeService identifierTypeService; @@ -69,10 +69,25 @@ class MappingOrdersToEdifactTest { @BeforeEach void setUp() { - var compositePOLineConverter = new CompositePOLineConverter(identifierTypeService, materialTypeService, expenseClassService, locationService, holdingService); - var compositePOConverter = new CompositePOConverter(compositePOLineConverter, configurationService); - purchaseOrdersToEdifactMapper = new PurchaseOrdersToEdifactMapper(compositePOConverter); + var compositePOLineConverter = new CompPoLineEdiConverter(identifierTypeService, materialTypeService, expenseClassService, locationService, holdingService); + var compositePOConverter = new CompOrderEdiConverter(compositePOLineConverter, configurationService); + edifactMapper = new EdifactMapper(compositePOConverter); objectMapper = new JacksonConfiguration().objectMapper(); + + when(identifierTypeService.getIdentifierTypeName("8261054f-be78-422d-bd51-4ed9f33c3422")) + .thenReturn("ISSN", "ISMN", "ISBN", "ISSN", "ISMN", "ISBN"); + when(identifierTypeService.getIdentifierTypeName(not(eq("8261054f-be78-422d-bd51-4ed9f33c3422")))) + .thenReturn("Publisher or distributor number"); + when(materialTypeService.getMaterialTypeName(anyString())) + .thenReturn("Book"); + when(expenseClassService.getExpenseClassCode(anyString())) + .thenReturn("Elec"); + when(locationService.getLocationCodeById(anyString())) + .thenReturn("KU/CC/DI/M"); + when(holdingService.getPermanentLocationByHoldingId(anyString())) + .thenReturn("fcd64ce1-6995-48f0-840e-89ffa2288371"); + when(configurationService.getAddressConfig(any())) + .thenReturn("Bockenheimer Landstr. 134-13"); } @ParameterizedTest @@ -83,42 +98,14 @@ void convertOrdersToEdifact(ExportType type) throws Exception { List compPOs = getTestOrdersFromJson(type); List pieces = getTestPiecesFromJson(type); - serviceMocks(); - - 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); - } + String ediOrder = edifactMapper.convertForExport(compPOs, pieces, getTestEdiConfig(), jobName); assertFalse(ediOrder.isEmpty()); validateEdifactOrders(type, ediOrder, fileIdExpected); - } - - @ParameterizedTest - @EnumSource(value = ExportType.class, names = {"EDIFACT_ORDERS_EXPORT", "CLAIMS"}) - void convertOrdersToEdifactByteArray(ExportType type) throws Exception { - String jobName = "12345"; - List compPOs = getTestOrdersFromJson(type); - List pieces = getTestPiecesFromJson(type); - serviceMocks(); - - 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); - validateEdifactOrders(type, new String(ediOrder), jobName); + byte[] ediOrderBytes = ediOrder.getBytes(StandardCharsets.UTF_8); + assertNotNull(ediOrderBytes); + validateEdifactOrders(type, new String(ediOrder), fileIdExpected); } private VendorEdiOrdersExportConfig getTestEdiConfig() throws IOException { @@ -138,44 +125,12 @@ private List getTestOrdersFromJson(ExportType type) thro 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) { - return IOUtils.toString(resourceAsStream, StandardCharsets.UTF_8); - } else { - StringBuilder sb = new StringBuilder(); - try (Stream lines = Files.lines(Paths.get(path))) { - lines.forEach(sb::append); - } - return sb.toString(); - } - } - } - - private void serviceMocks() { - Mockito.when(identifierTypeService.getIdentifierTypeName("8261054f-be78-422d-bd51-4ed9f33c3422")) - .thenReturn("ISSN", "ISMN", "ISBN", "ISSN", "ISMN", "ISBN"); - Mockito.when(identifierTypeService.getIdentifierTypeName(not(eq("8261054f-be78-422d-bd51-4ed9f33c3422")))) - .thenReturn("Publisher or distributor number"); - Mockito.when(materialTypeService.getMaterialTypeName(anyString())) - .thenReturn("Book"); - Mockito.when(expenseClassService.getExpenseClassCode(anyString())) - .thenReturn("Elec"); - Mockito.when(locationService.getLocationCodeById(anyString())) - .thenReturn("KU/CC/DI/M"); - Mockito.when(holdingService.getPermanentLocationByHoldingId(anyString())) - .thenReturn("fcd64ce1-6995-48f0-840e-89ffa2288371"); - Mockito.when(configurationService.getAddressConfig(any())) - .thenReturn("Bockenheimer Landstr. 134-13"); - } - 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); diff --git a/src/test/java/org/folio/dew/batch/acquisitions/edifact/ConfigurationServiceTest.java b/src/test/java/org/folio/dew/batch/acquisitions/edifact/services/ConfigurationServiceTest.java similarity index 90% rename from src/test/java/org/folio/dew/batch/acquisitions/edifact/ConfigurationServiceTest.java rename to src/test/java/org/folio/dew/batch/acquisitions/edifact/services/ConfigurationServiceTest.java index b92a0323b..6684b61d5 100644 --- a/src/test/java/org/folio/dew/batch/acquisitions/edifact/ConfigurationServiceTest.java +++ b/src/test/java/org/folio/dew/batch/acquisitions/edifact/services/ConfigurationServiceTest.java @@ -1,4 +1,4 @@ -package org.folio.dew.batch.acquisitions.edifact; +package org.folio.dew.batch.acquisitions.edifact.services; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -6,7 +6,6 @@ import java.util.stream.Stream; import org.folio.dew.BaseBatchTest; -import org.folio.dew.batch.acquisitions.edifact.services.ConfigurationService; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; diff --git a/src/test/java/org/folio/dew/batch/acquisitions/edifact/ExpenseClassServiceTest.java b/src/test/java/org/folio/dew/batch/acquisitions/edifact/services/ExpenseClassServiceTest.java similarity index 84% rename from src/test/java/org/folio/dew/batch/acquisitions/edifact/ExpenseClassServiceTest.java rename to src/test/java/org/folio/dew/batch/acquisitions/edifact/services/ExpenseClassServiceTest.java index bf3b3c3c5..bc65df2f1 100644 --- a/src/test/java/org/folio/dew/batch/acquisitions/edifact/ExpenseClassServiceTest.java +++ b/src/test/java/org/folio/dew/batch/acquisitions/edifact/services/ExpenseClassServiceTest.java @@ -1,9 +1,8 @@ -package org.folio.dew.batch.acquisitions.edifact; +package org.folio.dew.batch.acquisitions.edifact.services; import static org.junit.jupiter.api.Assertions.assertEquals; import org.folio.dew.BaseBatchTest; -import org.folio.dew.batch.acquisitions.edifact.services.ExpenseClassService; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; diff --git a/src/test/java/org/folio/dew/batch/acquisitions/edifact/HoldingServiceTest.java b/src/test/java/org/folio/dew/batch/acquisitions/edifact/services/HoldingServiceTest.java similarity index 78% rename from src/test/java/org/folio/dew/batch/acquisitions/edifact/HoldingServiceTest.java rename to src/test/java/org/folio/dew/batch/acquisitions/edifact/services/HoldingServiceTest.java index 7b7072a30..8208e7fde 100644 --- a/src/test/java/org/folio/dew/batch/acquisitions/edifact/HoldingServiceTest.java +++ b/src/test/java/org/folio/dew/batch/acquisitions/edifact/services/HoldingServiceTest.java @@ -1,26 +1,30 @@ -package org.folio.dew.batch.acquisitions.edifact; +package org.folio.dew.batch.acquisitions.edifact.services; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; -import org.folio.dew.BaseBatchTest; -import org.folio.dew.batch.acquisitions.edifact.services.HoldingService; +import com.fasterxml.jackson.databind.ObjectMapper; + import org.folio.dew.client.HoldingClient; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.mock.mockito.MockBean; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doReturn; +@ExtendWith(MockitoExtension.class) +class HoldingServiceTest { -class HoldingServiceTest extends BaseBatchTest { - @Autowired + @InjectMocks private HoldingService holdingService; - @MockBean + @Mock private HoldingClient client; - + @Spy + private ObjectMapper objectMapper; @Test void getPermanentLocationIdFromJson() throws JsonProcessingException { @@ -39,7 +43,7 @@ void getInstanceIdByHoldingShouldReturnCorrectInstanceId() throws JsonProcessing } @Test - void getInstanceIdByHoldingShouldReturnCorrectEmptyStringWhenNull() throws JsonProcessingException { + void getInstanceIdByHoldingShouldReturnCorrectEmptyStringWhenNull() { String actual = holdingService.getInstanceIdByHolding(null); assertTrue(actual.isEmpty()); } diff --git a/src/test/java/org/folio/dew/batch/acquisitions/edifact/IdentifierTypeServiceTest.java b/src/test/java/org/folio/dew/batch/acquisitions/edifact/services/IdentifierTypeServiceTest.java similarity index 56% rename from src/test/java/org/folio/dew/batch/acquisitions/edifact/IdentifierTypeServiceTest.java rename to src/test/java/org/folio/dew/batch/acquisitions/edifact/services/IdentifierTypeServiceTest.java index 890a3db63..307e9685f 100644 --- a/src/test/java/org/folio/dew/batch/acquisitions/edifact/IdentifierTypeServiceTest.java +++ b/src/test/java/org/folio/dew/batch/acquisitions/edifact/services/IdentifierTypeServiceTest.java @@ -1,24 +1,29 @@ -package org.folio.dew.batch.acquisitions.edifact; +package org.folio.dew.batch.acquisitions.edifact.services; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; -import org.folio.dew.BaseBatchTest; -import org.folio.dew.batch.acquisitions.edifact.services.IdentifierTypeService; import org.folio.dew.client.IdentifierTypeClient; import org.junit.jupiter.api.Test; -import org.mockito.Mockito; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.mock.mockito.MockBean; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +@ExtendWith(MockitoExtension.class) +class IdentifierTypeServiceTest { -class IdentifierTypeServiceTest extends BaseBatchTest { - @Autowired + @InjectMocks private IdentifierTypeService identifierTypeService; - @MockBean + @Mock private IdentifierTypeClient client; + @Spy + private ObjectMapper objectMapper; @Test void getIdentifierTypeName() { @@ -28,8 +33,8 @@ void getIdentifierTypeName() { @Test void getIdentifierTypeNameFromJson() throws JsonProcessingException { - Mockito.when(client.getIdentifierType(anyString())) - .thenReturn(objectMapper.readTree("{\"name\": \"ISSN\"}")); + var identifierTypeJson = objectMapper.readTree("{\"name\": \"ISSN\"}"); + when(client.getIdentifierType(anyString())).thenReturn(identifierTypeJson); String identifierTypeName = identifierTypeService.getIdentifierTypeName("913300b2-03ed-469a-8179-c1092c991227"); assertEquals("ISSN", identifierTypeName); } diff --git a/src/test/java/org/folio/dew/batch/acquisitions/edifact/LocationServiceTest.java b/src/test/java/org/folio/dew/batch/acquisitions/edifact/services/LocationServiceTest.java similarity index 80% rename from src/test/java/org/folio/dew/batch/acquisitions/edifact/LocationServiceTest.java rename to src/test/java/org/folio/dew/batch/acquisitions/edifact/services/LocationServiceTest.java index e46f247e7..be6a89c40 100644 --- a/src/test/java/org/folio/dew/batch/acquisitions/edifact/LocationServiceTest.java +++ b/src/test/java/org/folio/dew/batch/acquisitions/edifact/services/LocationServiceTest.java @@ -1,9 +1,8 @@ -package org.folio.dew.batch.acquisitions.edifact; +package org.folio.dew.batch.acquisitions.edifact.services; import static org.junit.jupiter.api.Assertions.assertEquals; import org.folio.dew.BaseBatchTest; -import org.folio.dew.batch.acquisitions.edifact.services.LocationService; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; diff --git a/src/test/java/org/folio/dew/batch/acquisitions/edifact/MaterialTypeServiceTest.java b/src/test/java/org/folio/dew/batch/acquisitions/edifact/services/MaterialTypeServiceTest.java similarity index 80% rename from src/test/java/org/folio/dew/batch/acquisitions/edifact/MaterialTypeServiceTest.java rename to src/test/java/org/folio/dew/batch/acquisitions/edifact/services/MaterialTypeServiceTest.java index e1ef6de32..7b919d490 100644 --- a/src/test/java/org/folio/dew/batch/acquisitions/edifact/MaterialTypeServiceTest.java +++ b/src/test/java/org/folio/dew/batch/acquisitions/edifact/services/MaterialTypeServiceTest.java @@ -1,9 +1,8 @@ -package org.folio.dew.batch.acquisitions.edifact; +package org.folio.dew.batch.acquisitions.edifact.services; import static org.junit.jupiter.api.Assertions.assertEquals; import org.folio.dew.BaseBatchTest; -import org.folio.dew.batch.acquisitions.edifact.services.MaterialTypeService; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; diff --git a/src/test/java/org/folio/dew/batch/acquisitions/edifact/OrderServiceTest.java b/src/test/java/org/folio/dew/batch/acquisitions/edifact/services/OrderServiceTest.java similarity index 96% rename from src/test/java/org/folio/dew/batch/acquisitions/edifact/OrderServiceTest.java rename to src/test/java/org/folio/dew/batch/acquisitions/edifact/services/OrderServiceTest.java index a00df7761..7a38d5dfc 100644 --- a/src/test/java/org/folio/dew/batch/acquisitions/edifact/OrderServiceTest.java +++ b/src/test/java/org/folio/dew/batch/acquisitions/edifact/services/OrderServiceTest.java @@ -1,6 +1,5 @@ -package org.folio.dew.batch.acquisitions.edifact; +package org.folio.dew.batch.acquisitions.edifact.services; -import org.folio.dew.batch.acquisitions.edifact.services.OrdersService; import org.folio.dew.client.OrdersStorageClient; import org.folio.dew.domain.dto.PoLine; import org.folio.dew.domain.dto.PoLineCollection; diff --git a/src/test/resources/edifact/acquisitions/csv_claims_result.csv b/src/test/resources/edifact/acquisitions/csv_claims_result.csv new file mode 100644 index 000000000..fbb06e066 --- /dev/null +++ b/src/test/resources/edifact/acquisitions/csv_claims_result.csv @@ -0,0 +1,8 @@ +POL number,Vendor order number,Account number,Title from piece,Display summary,Chronology,Enumeration,Quantity +10000-1,ORD1000,BRXXXXX-01,"Futures, biometrics and neuroscience research Luiz Moutinho, Mladen Sokele, editors",S1,C1,E1,3 +10000-1,ORD1000,BRXXXXX-01,"Futures, biometrics and neuroscience research Luiz Moutinho, Mladen Sokele, editors",S2,C2,,1 +10001-1,ORD1001,BRXXXXX-01,"Futures, biometrics and neuroscience research Luiz Moutinho, Mladen Sokele, editors",S3,,E3,2 +10001-2,ORD1002,BRXXXXX-01,"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",,C4,E4,1 +10002-1,,,Test title,S5,,E5,1 +10002-1,,,Test title,,C5,,1 +10002-1,,,Test title,,,,1 diff --git a/src/test/resources/edifact/acquisitions/edifact_claims_result.edi b/src/test/resources/edifact/acquisitions/edifact_claims_result.edi index 13f2fab02..e8abe94dc 100644 --- a/src/test/resources/edifact/acquisitions/edifact_claims_result.edi +++ b/src/test/resources/edifact/acquisitions/edifact_claims_result.edi @@ -13,6 +13,8 @@ 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+:::S1?:C1?:E1' +IMD+L+080+:::S1?:C1?:E1' IMD+L+080+:::S2?:C2' IMD+L+109+:::Palgrave Macmillan' QTY+21:1' @@ -26,7 +28,7 @@ LOC+7+KU/CC/DI/M::92' UNS+S' CNT+1:1' CNT+2:1' -UNT+27+10000' +UNT+29+10000' UNH+10001+ORDERS:D:96A:UN:EAN008' BGM+224+10001+9' DTM+137::102' @@ -40,6 +42,7 @@ 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+080+:::S3?:E3' IMD+L+109+:::Palgrave Macmillan' IMD+L+170+:::[2017]' QTY+21:1' @@ -88,7 +91,7 @@ LOC+7+KU/CC/DI/M::92' UNS+S' CNT+1:4' CNT+2:2' -UNT+62+10001' +UNT+63+10001' UNH+10002+ORDERS:D:96A:UN:EAN008' BGM+220+10002+9' DTM+137::102' diff --git a/src/test/resources/edifact/acquisitions/minimalistic_composite_purchase_order.json b/src/test/resources/edifact/acquisitions/minimalistic_composite_purchase_order.json index a16f59fe8..49acc7a06 100644 --- a/src/test/resources/edifact/acquisitions/minimalistic_composite_purchase_order.json +++ b/src/test/resources/edifact/acquisitions/minimalistic_composite_purchase_order.json @@ -8,6 +8,7 @@ "cost": { "currency": "USD" }, + "isPackage": true, "titleOrPackage": "empty", "metadata": { "createdDate": "2021-12-28T08:17:02.171+00:00", diff --git a/src/test/resources/edifact/acquisitions/pieces_collection.json b/src/test/resources/edifact/acquisitions/pieces_collection.json index 1564e88b3..6f6cf59e4 100644 --- a/src/test/resources/edifact/acquisitions/pieces_collection.json +++ b/src/test/resources/edifact/acquisitions/pieces_collection.json @@ -33,6 +33,5 @@ "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 index 8ab9d88a2..10400d154 100644 --- a/src/test/resources/edifact/acquisitions/pieces_collection_mixed.json +++ b/src/test/resources/edifact/acquisitions/pieces_collection_mixed.json @@ -33,6 +33,28 @@ "createdByUserId": "28d1057c-d137-11e8-a8d5-f2801f1b9fd1" } }, + { + "id": "5e317dc2-deeb-4429-b2a1-91e5cd0fd5a1", + "poLineId": "f7cebba9-900d-4e46-ac96-9dfdb6da214e", + "receivingStatus": "Late", + "displaySummary": "S1", + "chronology": "C1", + "enumeration": "E1", + "itemId": "522a501a-56b5-48d9-b28a-3a8f02482d97", + "locationId": "53cf956f-c1df-410b-8bea-27f712cca7c0", + "titleId": "9a665b22-9fe5-4c95-b4ee-837a5433c95d" + }, + { + "id": "5e317dc2-deeb-4429-b2a1-91e5cd0fd5b1", + "poLineId": "f7cebba9-900d-4e46-ac96-9dfdb6da214e", + "receivingStatus": "Late", + "displaySummary": "S1", + "chronology": "C1", + "enumeration": "E1", + "itemId": "522a501a-56b5-48d9-b28a-3a8f02482d97", + "locationId": "53cf956f-c1df-410b-8bea-27f712cca7c0", + "titleId": "9a665b22-9fe5-4c95-b4ee-837a5433c95d" + }, { "id": "5e317dc2-deeb-4429-b2a1-91e5cd0fd5f2", "poLineId": "f7cebba9-900d-4e46-ac96-9dfdb6da214e", @@ -100,7 +122,18 @@ } }, { - "id": "5e317dc2-deeb-4429-b2a1-91e5cd0fd5f3", + "id": "5e317dc2-deeb-4429-b2a1-91e5cd0fd5a3", + "poLineId": "81b6503d-1f50-404d-a663-794b7f726ffd", + "receivingStatus": "Late", + "displaySummary": "S3", + "chronology": "", + "enumeration": "E3", + "itemId": "522a501a-56b5-48d9-b28a-3a8f02482d97", + "locationId": "53cf956f-c1df-410b-8bea-27f712cca7c0", + "titleId": "9a665b22-9fe5-4c95-b4ee-837a5433c95d" + }, + { + "id": "5e317dc2-deeb-4429-b2a1-91e5cd0fd5f4", "poLineId": "c8d7a6a8-89b9-4711-9e1e-694b3e5ada72", "receivingStatus": "Late", "displaySummary": null, @@ -133,7 +166,7 @@ } }, { - "id": "5e317dc2-deeb-4429-b2a1-91e5cd0fd5f4", + "id": "5e317dc2-deeb-4429-b2a1-91e5cd0fd5f5", "poLineId": "31af5b5c-251e-4582-9896-2c9cef360284", "receivingStatus": "Late", "displaySummary": "S5", @@ -166,7 +199,7 @@ } }, { - "id": "5e317dc2-deeb-4429-b2a1-91e5cd0fd5f4", + "id": "5e317dc2-deeb-4429-b2a1-91e5cd0fd5f6", "poLineId": "31af5b5c-251e-4582-9896-2c9cef360284", "receivingStatus": "Late", "displaySummary": null, @@ -199,7 +232,7 @@ } }, { - "id": "5e317dc2-deeb-4429-b2a1-91e5cd0fd5f4", + "id": "5e317dc2-deeb-4429-b2a1-91e5cd0fd5f7", "poLineId": "31af5b5c-251e-4582-9896-2c9cef360284", "receivingStatus": "Late", "displaySummary": null, diff --git a/src/test/resources/edifact/edifactOrdersExport.json b/src/test/resources/edifact/edifactOrdersExport.json index 4e35e0fe7..4ac93e09c 100644 --- a/src/test/resources/edifact/edifactOrdersExport.json +++ b/src/test/resources/edifact/edifactOrdersExport.json @@ -2,6 +2,7 @@ "vendorId": "d0fb5aa0-cdf1-11e8-a8d5-f2801f1b9fd1", "configName": "EDIFACT 1", "configDescription": "EDI configuration for testing export", + "fileFormat": "EDI", "ediConfig": { "accountNoList": [ "BRXXXXX-01"