Skip to content

Commit

Permalink
MODDCB-98 Implement GET API for transaction updates with pagination (#77
Browse files Browse the repository at this point in the history
)

* MODDCB-98 Implement GET API for transaction updates with pagination

* MODDCB-98 change the datatype of date column to support offsetDateTime

* MODDCB-98 change the property name

* MODDCB-98 Fetch the record from audit table so that the response of the API is immutable

* MODDCB-98 Adding test case

* MODDCB-98 Refactoring code
  • Loading branch information
Vignesh-kalyanasundaram authored Apr 29, 2024
1 parent 85d9847 commit 05bc3ce
Show file tree
Hide file tree
Showing 18 changed files with 438 additions and 7 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ dcb API provides the following URLs:
| GET | /transactions/{dcbTransactionId}/status | dcb.transactions.get | Gets status of transaction based on transactionId |
| POST | /transactions/{dcbTransactionId} | dcb.transactions.post | create new transaction |
| PUT | /transactions/{dcbTransactionId}/status | dcb.transactions.put | Update the status of the transaction and it will trigger automatic action if needed |
| GET | /transactions/{dcbTransactionId}/status | dcb.transactions.collection.get | get list of transaction updated between a given query range |

## Installing and deployment

Expand Down
18 changes: 17 additions & 1 deletion descriptors/ModuleDescriptor-template.json
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,16 @@
"circulation.check-out-by-barcode.post",
"circulation.check-in-by-barcode.post"
]
},
{
"methods": [
"GET"
],
"pathPattern": "/transactions/status",
"permissionsRequired": [
"dcb.transactions.collection.get"
],
"modulePermissions": []
}
]
},
Expand Down Expand Up @@ -181,7 +191,8 @@
"subPermissions": [
"dcb.transactions.post",
"dcb.transactions.put",
"dcb.transactions.get"
"dcb.transactions.get",
"dcb.transactions.collection.get"
]
},
{
Expand All @@ -198,6 +209,11 @@
"permissionName": "dcb.transactions.put",
"displayName": "update transaction details",
"description": "update transaction details"
},
{
"permissionName": "dcb.transactions.collection.get",
"displayName": "get updated transaction detail list",
"description": "get list of transaction updated between a given query range"
}
],
"launchDescriptor": {
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@
<generateModelDocumentation>true</generateModelDocumentation>
<configOptions>
<generatedConstructorWithRequiredArgs>false</generatedConstructorWithRequiredArgs>
<dateLibrary>java</dateLibrary>
<dateLibrary>java8</dateLibrary>
<interfaceOnly>true</interfaceOnly>
<useSpringBoot3>true</useSpringBoot3>
<additionalModelTypeAnnotations>@lombok.Builder @lombok.NoArgsConstructor @lombok.AllArgsConstructor</additionalModelTypeAnnotations>
Expand Down
10 changes: 9 additions & 1 deletion src/main/java/org/folio/dcb/config/JpaAuditingConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@

import lombok.RequiredArgsConstructor;
import org.folio.spring.FolioExecutionContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.auditing.DateTimeProvider;
import org.springframework.data.domain.AuditorAware;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

import java.time.OffsetDateTime;
import java.util.Optional;
import java.util.UUID;

@Configuration
@EnableJpaAuditing(modifyOnCreate = false)
@EnableJpaAuditing(dateTimeProviderRef = "dateTimeProvider", modifyOnCreate = false)
@RequiredArgsConstructor
public class JpaAuditingConfig implements AuditorAware<UUID>{

Expand All @@ -21,4 +24,9 @@ public Optional<UUID> getCurrentAuditor() {
return Optional.ofNullable(folioExecutionContext.getUserId());
}

@Bean
public DateTimeProvider dateTimeProvider() {
return () -> Optional.of(OffsetDateTime.now());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
import lombok.extern.log4j.Log4j2;
import org.folio.dcb.domain.dto.DcbTransaction;
import org.folio.dcb.domain.dto.TransactionStatus;
import org.folio.dcb.domain.dto.TransactionStatusResponseCollection;
import org.folio.dcb.rest.resource.TransactionsApi;
import org.folio.dcb.domain.dto.TransactionStatusResponse;
import org.folio.dcb.service.TransactionAuditService;
import org.folio.dcb.service.TransactionsService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestController;
import java.time.OffsetDateTime;

@RestController
@Log4j2
Expand Down Expand Up @@ -63,4 +65,13 @@ public ResponseEntity<TransactionStatusResponse> updateTransactionStatus(String
return ResponseEntity.status(HttpStatus.OK)
.body(transactionStatusResponse);
}

@Override
public ResponseEntity<TransactionStatusResponseCollection> getTransactionStatusList(OffsetDateTime fromDate, OffsetDateTime toDate, Integer pageNumber, Integer pageSize) {
log.info("getTransactionStatusList:: fetching transaction lists with fromDate {}, toDate {}, pageNumber {}, pageSize {}",
fromDate, toDate, pageNumber, pageSize);
return ResponseEntity.status(HttpStatus.OK)
.body(transactionsService.getTransactionStatusList(fromDate, toDate, pageNumber, pageSize));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.util.UUID;

@Getter
Expand All @@ -26,7 +26,7 @@ public abstract class AuditableEntity {
@JsonIgnore
@CreatedDate
@Column(name = "created_date", nullable = false, updatable = false)
private LocalDateTime createdDate;
private OffsetDateTime createdDate;

@JsonIgnore
@CreatedBy
Expand All @@ -36,7 +36,7 @@ public abstract class AuditableEntity {
@JsonIgnore
@LastModifiedDate
@Column(name = "updated_date")
private LocalDateTime updatedDate;
private OffsetDateTime updatedDate;

@JsonIgnore
@LastModifiedBy
Expand Down
57 changes: 57 additions & 0 deletions src/main/java/org/folio/dcb/domain/mapper/TransactionMapper.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
package org.folio.dcb.domain.mapper;

import org.folio.dcb.domain.dto.DcbItem;
import org.folio.dcb.domain.dto.DcbPatron;
import org.folio.dcb.domain.dto.DcbPickup;
import org.folio.dcb.domain.dto.TransactionStatusResponseList;
import org.folio.dcb.domain.entity.TransactionAuditEntity;
import org.folio.dcb.domain.entity.TransactionEntity;
import org.folio.dcb.domain.dto.DcbTransaction;
import org.folio.dcb.utils.JsonUtils;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Component;
import java.util.List;

@Component
public class TransactionMapper {
Expand Down Expand Up @@ -34,4 +42,53 @@ public TransactionEntity mapToEntity(String transactionId, DcbTransaction dcbTra
.build();
}

public List<TransactionStatusResponseList> mapToDto(Page<TransactionAuditEntity> transactionAuditEntityPage) {
var transactionList = transactionAuditEntityPage.getContent();
return transactionList
.stream()
.map(transactionAuditEntity -> JsonUtils.jsonToObject(transactionAuditEntity.getAfter(), TransactionEntity.class))
.map(transactionEntity -> TransactionStatusResponseList
.builder()
.id(transactionEntity.getId())
.pickup(mapTransactionEntityToDcbPickup(transactionEntity))
.item(mapTransactionEntityToDcbItem(transactionEntity))
.patron(mapTransactionEntityToDcbPatron(transactionEntity))
.status(TransactionStatusResponseList.StatusEnum.fromValue
(transactionEntity.getStatus().getValue()))
.role(TransactionStatusResponseList.RoleEnum.fromValue
(transactionEntity.getRole().getValue()))
.build())
.toList();

}

public DcbItem mapTransactionEntityToDcbItem(TransactionEntity transactionEntity) {
return DcbItem
.builder()
.id(transactionEntity.getItemId())
.title(transactionEntity.getItemTitle())
.barcode(transactionEntity.getItemBarcode())
.materialType(transactionEntity.getMaterialType())
.lendingLibraryCode(transactionEntity.getLendingLibraryCode())
.build();
}

public DcbPatron mapTransactionEntityToDcbPatron(TransactionEntity transactionEntity) {
return DcbPatron
.builder()
.id(transactionEntity.getPatronId())
.group(transactionEntity.getPatronGroup())
.barcode(transactionEntity.getPatronBarcode())
.build();
}

public DcbPickup mapTransactionEntityToDcbPickup(TransactionEntity transactionEntity) {
return DcbPickup
.builder()
.servicePointId(transactionEntity.getServicePointId())
.servicePointName(transactionEntity.getServicePointName())
.libraryCode(transactionEntity.getPickupLibraryCode())
.build();
}

}
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package org.folio.dcb.repository;

import org.folio.dcb.domain.entity.TransactionAuditEntity;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.time.OffsetDateTime;
import java.util.Optional;

@Repository
Expand All @@ -15,4 +18,10 @@ public interface TransactionAuditRepository extends JpaRepository<TransactionAud
"WHERE transaction_id = :trnId " +
"and created_date = (SELECT MAX(created_date) FROM transactions_audit WHERE transaction_id = :trnId);", nativeQuery = true)
Optional<TransactionAuditEntity> findLatestTransactionAuditEntityByDcbTransactionId(@Param("trnId") String trnId);

@Query(value = "SELECT * FROM transactions_audit t WHERE t.created_date >= :fromDate AND t.created_date <= :toDate AND t.action = 'UPDATE'",
countQuery = "SELECT COUNT(*) FROM transactions_audit t WHERE t.created_date >= :fromDate AND t.created_date <= :toDate AND t.action = 'UPDATE'",
nativeQuery = true)
Page<TransactionAuditEntity> findUpdatedTransactionsByDateRange(OffsetDateTime fromDate, OffsetDateTime toDate, Pageable pageable);

}
4 changes: 4 additions & 0 deletions src/main/java/org/folio/dcb/service/TransactionsService.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import org.folio.dcb.domain.dto.DcbTransaction;
import org.folio.dcb.domain.dto.TransactionStatus;
import org.folio.dcb.domain.dto.TransactionStatusResponse;
import org.folio.dcb.domain.dto.TransactionStatusResponseCollection;
import java.time.OffsetDateTime;

public interface TransactionsService {
/**
Expand All @@ -14,4 +16,6 @@ public interface TransactionsService {
TransactionStatusResponse createCirculationRequest(String dcbTransactionId, DcbTransaction dcbTransaction);
TransactionStatusResponse updateTransactionStatus(String dcbTransactionId, TransactionStatus transactionStatus);
TransactionStatusResponse getTransactionStatusById(String dcbTransactionId);
TransactionStatusResponseCollection getTransactionStatusList(OffsetDateTime fromDate, OffsetDateTime toDate, Integer pageNumber, Integer pageSize);

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,22 @@
import org.folio.dcb.domain.dto.DcbTransaction;
import org.folio.dcb.domain.dto.TransactionStatus;
import org.folio.dcb.domain.dto.TransactionStatusResponse;
import org.folio.dcb.domain.dto.TransactionStatusResponseCollection;
import org.folio.dcb.domain.entity.TransactionEntity;
import org.folio.dcb.domain.mapper.TransactionMapper;
import org.folio.dcb.exception.ResourceAlreadyExistException;
import org.folio.dcb.exception.StatusException;
import org.folio.dcb.repository.TransactionAuditRepository;
import org.folio.dcb.repository.TransactionRepository;
import org.folio.dcb.service.LibraryService;
import org.folio.dcb.service.StatusProcessorService;
import org.folio.dcb.service.TransactionsService;
import org.folio.spring.exception.NotFoundException;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import java.time.OffsetDateTime;

@Service
@RequiredArgsConstructor
Expand All @@ -31,6 +37,8 @@ public class TransactionsServiceImpl implements TransactionsService {
private final LibraryService borrowingLibraryService;
private final TransactionRepository transactionRepository;
private final StatusProcessorService statusProcessorService;
private final TransactionMapper transactionMapper;
private final TransactionAuditRepository transactionAuditRepository;

@Override
public TransactionStatusResponse createCirculationRequest(String dcbTransactionId, DcbTransaction dcbTransaction) {
Expand Down Expand Up @@ -82,6 +90,25 @@ public TransactionStatusResponse getTransactionStatusById(String dcbTransactionI
return generateTransactionStatusResponseFromTransactionEntity(transactionEntity);
}

@Override
public TransactionStatusResponseCollection getTransactionStatusList(OffsetDateTime fromDate, OffsetDateTime toDate, Integer pageNumber, Integer pageSize) {
log.info("getTransactionStatusList:: fromDate {}, toDate {}, pageNumber {}, pageSize {}",
fromDate, toDate, pageNumber, pageSize);
var pageable = PageRequest.of(pageNumber, pageSize, Sort.by("created_Date"));
var transactionAuditEntityPage= transactionAuditRepository.findUpdatedTransactionsByDateRange(fromDate, toDate, pageable);
var transactionStatusResponseList= transactionMapper.mapToDto(transactionAuditEntityPage);
var totalRecords = (int)transactionAuditEntityPage.getTotalElements();
var maxPageNumber = pageSize >= totalRecords ? 0 : (int) Math.ceil((double) totalRecords / pageSize) - 1;
return TransactionStatusResponseCollection
.builder()
.transactions(transactionStatusResponseList)
.totalRecords(totalRecords)
.currentPageNumber(pageNumber)
.currentPageSize(pageSize)
.maximumPageNumber(maxPageNumber)
.build();
}

private TransactionStatusResponse generateTransactionStatusResponseFromTransactionEntity(TransactionEntity transactionEntity) {
TransactionStatus.StatusEnum transactionStatus = transactionEntity.getStatus();
TransactionStatusResponse.StatusEnum transactionStatusResponseStatusEnum = TransactionStatusResponse.StatusEnum.fromValue(transactionStatus.getValue());
Expand Down
23 changes: 23 additions & 0 deletions src/main/java/org/folio/dcb/utils/JsonUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.folio.dcb.utils;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.experimental.UtilityClass;
import lombok.extern.log4j.Log4j2;

@UtilityClass
@Log4j2
public class JsonUtils {
public static final String DESERIALIZATION_FAILURE = "Failed to deserialize json string";
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

public static <T> T jsonToObject(String jsonString, Class<T> classToDeserialize) {
try {
return OBJECT_MAPPER.readValue(jsonString, classToDeserialize);
} catch (JsonProcessingException ex) {
log.info(DESERIALIZATION_FAILURE + ex);
throw new IllegalArgumentException(DESERIALIZATION_FAILURE + ex.getMessage());
}
}

}
2 changes: 1 addition & 1 deletion src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ spring:
enabled: true
change-log: classpath:db/changelog/changelog-master.xml
jackson:
default-property-inclusion: non_empty
default-property-inclusion: non_null
deserialization:
fail-on-unknown-properties: false
accept-single-value-as-array: true
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/db/changelog/changelog-master.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@
<include file="changes/create-audit-table.xml" relativeToChangelogFile="true"/>
<include file="changes/add-request-to-transaction.xml" relativeToChangelogFile="true"/>
<include file="changes/set-optional-fields-nullable.xml" relativeToChangelogFile="true"/>
<include file="changes/alter-date-datatype.xml" relativeToChangelogFile="true"/>

</databaseChangeLog>
17 changes: 17 additions & 0 deletions src/main/resources/db/changelog/changes/alter-date-datatype.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd">

<changeSet id="alterDateColumnsType" author="vignesh">
<sql>
<![CDATA[
ALTER TABLE transactions ALTER COLUMN created_date TYPE timestamp with time zone;
ALTER TABLE transactions ALTER COLUMN updated_date TYPE timestamp with time zone;
ALTER TABLE transactions_audit ALTER COLUMN created_date TYPE timestamp with time zone;
]]>
</sql>
</changeSet>

</databaseChangeLog>
Loading

0 comments on commit 05bc3ce

Please sign in to comment.