diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json index 49dd4b03..38081033 100644 --- a/descriptors/ModuleDescriptor-template.json +++ b/descriptors/ModuleDescriptor-template.json @@ -10,6 +10,10 @@ "id": "circulation", "version": "14.4" }, + { + "id": "loan-policy-storage", + "version": "2.3" + }, { "id": "users", "version": "16.3" diff --git a/src/main/java/org/folio/dcb/client/feign/CirculationClient.java b/src/main/java/org/folio/dcb/client/feign/CirculationClient.java index 8b21366f..74c4a89c 100644 --- a/src/main/java/org/folio/dcb/client/feign/CirculationClient.java +++ b/src/main/java/org/folio/dcb/client/feign/CirculationClient.java @@ -3,12 +3,15 @@ import org.folio.dcb.domain.dto.CheckInRequest; import org.folio.dcb.domain.dto.CheckOutRequest; import org.folio.dcb.domain.dto.CirculationRequest; +import org.folio.dcb.domain.dto.LoanCollection; import org.folio.spring.config.FeignClientConfiguration; import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; @FeignClient(name = "circulation", configuration = FeignClientConfiguration.class) public interface CirculationClient { @@ -24,4 +27,7 @@ public interface CirculationClient { @PutMapping("/requests/{requestId}") CirculationRequest updateRequest(@PathVariable("requestId") String requestId, @RequestBody CirculationRequest circulationRequest); + + @GetMapping("/loans") + LoanCollection fetchLoanByQuery(@RequestParam("query") String query); } diff --git a/src/main/java/org/folio/dcb/client/feign/CirculationLoanPolicyStorageClient.java b/src/main/java/org/folio/dcb/client/feign/CirculationLoanPolicyStorageClient.java new file mode 100644 index 00000000..ec96923e --- /dev/null +++ b/src/main/java/org/folio/dcb/client/feign/CirculationLoanPolicyStorageClient.java @@ -0,0 +1,13 @@ +package org.folio.dcb.client.feign; + +import org.folio.dcb.domain.dto.LoanPolicyCollection; +import org.folio.spring.config.FeignClientConfiguration; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; + +@FeignClient(name = "loan-policy-storage", configuration = FeignClientConfiguration.class) +public interface CirculationLoanPolicyStorageClient { + @GetMapping("/loan-policies") + LoanPolicyCollection fetchLoanPolicyByQuery(@RequestParam("query") String query); +} diff --git a/src/main/java/org/folio/dcb/service/impl/TransactionsServiceImpl.java b/src/main/java/org/folio/dcb/service/impl/TransactionsServiceImpl.java index 050c3176..e7a0a873 100644 --- a/src/main/java/org/folio/dcb/service/impl/TransactionsServiceImpl.java +++ b/src/main/java/org/folio/dcb/service/impl/TransactionsServiceImpl.java @@ -2,8 +2,14 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; +import org.folio.dcb.client.feign.CirculationClient; +import org.folio.dcb.client.feign.CirculationLoanPolicyStorageClient; +import org.folio.dcb.domain.dto.DcbItem; import org.folio.dcb.domain.dto.DcbTransaction; import org.folio.dcb.domain.dto.DcbUpdateTransaction; +import org.folio.dcb.domain.dto.LoanCollection; +import org.folio.dcb.domain.dto.LoanPolicyCollection; +import org.folio.dcb.domain.dto.RenewalPolicy; import org.folio.dcb.domain.dto.TransactionStatus; import org.folio.dcb.domain.dto.TransactionStatusResponse; import org.folio.dcb.domain.dto.TransactionStatusResponseCollection; @@ -17,17 +23,23 @@ import org.folio.dcb.service.StatusProcessorService; import org.folio.dcb.service.TransactionsService; import org.folio.spring.exception.NotFoundException; +import org.folio.util.PercentCodec; +import org.folio.util.StringUtil; +import org.jetbrains.annotations.NotNull; 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; +import java.util.Optional; @Service @RequiredArgsConstructor @Log4j2 public class TransactionsServiceImpl implements TransactionsService { + private static final String CQL_AND = " AND "; @Qualifier("lendingLibraryService") private final LibraryService lendingLibraryService; @Qualifier("borrowingPickupLibraryService") @@ -41,6 +53,8 @@ public class TransactionsServiceImpl implements TransactionsService { private final TransactionMapper transactionMapper; private final TransactionAuditRepository transactionAuditRepository; private final BaseLibraryService baseLibraryService; + private final CirculationClient circulationClient; + private final CirculationLoanPolicyStorageClient circulationLoanPolicyStorageClient; @Override public TransactionStatusResponse createCirculationRequest(String dcbTransactionId, DcbTransaction dcbTransaction) { @@ -89,9 +103,46 @@ public TransactionStatusResponse getTransactionStatusById(String dcbTransactionI log.debug("getTransactionStatusById:: id {} ", dcbTransactionId); TransactionEntity transactionEntity = getTransactionEntityOrThrow(dcbTransactionId); - return generateTransactionStatusResponseFromTransactionEntity(transactionEntity); + Optional loanRenewalDetails = getLoanRenewalDetails(transactionEntity); + return generateTransactionStatusResponseFromTransactionEntity(transactionEntity, loanRenewalDetails); } + private Optional getLoanRenewalDetails(TransactionEntity transactionEntity) { + if (transactionEntity.getStatus() == TransactionStatus.StatusEnum.ITEM_CHECKED_OUT + && (transactionEntity.getRole() == DcbTransaction.RoleEnum.BORROWING_PICKUP + || transactionEntity.getRole() == DcbTransaction.RoleEnum.BORROWER)) { + String loanQuery = buildLoanQuery(transactionEntity); + LoanCollection loanCollection = circulationClient.fetchLoanByQuery(loanQuery); + if (loanCollection.getLoans().isEmpty()) { + return Optional.empty(); + } + + Integer loanRenewalCount = Integer.valueOf(loanCollection.getLoans().get(0).getRenewalCount()); + + String loanPolicyIdQuery = "id==" + StringUtil.cqlEncode(loanCollection.getLoans().get(0).getLoanPolicyId()); + LoanPolicyCollection loanPolicyCollection = + circulationLoanPolicyStorageClient.fetchLoanPolicyByQuery(PercentCodec.encode(loanPolicyIdQuery).toString()); + + Boolean isUnlimited = loanPolicyCollection.getLoanPolicies().get(0).getRenewalsPolicy().getUnlimited(); + Integer renewalMaxCount = Boolean.TRUE.equals(isUnlimited) ? -1 : + loanPolicyCollection.getLoanPolicies().get(0).getRenewalsPolicy().getNumberAllowed(); + + return Optional.of(new LoanRenewalDetails(loanRenewalCount, renewalMaxCount)); + } else { + return Optional.empty(); + } + } + + private static @NotNull String buildLoanQuery(TransactionEntity transactionEntity) { + String itemId = "itemId==" + StringUtil.cqlEncode(transactionEntity.getItemId()); + String statusOpen = "status.name==" + StringUtil.cqlEncode("OPEN"); + String isDCB = "isDcb==" + StringUtil.cqlEncode("true"); + String userId = "itemId==" + StringUtil.cqlEncode(transactionEntity.getPatronId()); + return PercentCodec.encode(itemId + CQL_AND + statusOpen + CQL_AND + isDCB + CQL_AND + userId).toString(); + } + + private record LoanRenewalDetails(Integer loanRenewalCount, Integer renewalMaxCount) {} + @Override public TransactionStatusResponseCollection getTransactionStatusList(OffsetDateTime fromDate, OffsetDateTime toDate, Integer pageNumber, Integer pageSize) { log.info("getTransactionStatusList:: fromDate {}, toDate {}, pageNumber {}, pageSize {}", @@ -124,13 +175,19 @@ public void updateTransactionDetails(String dcbTransactionId, DcbUpdateTransacti baseLibraryService.updateTransactionDetails(transactionEntity, dcbUpdateTransaction.getItem()); } - private TransactionStatusResponse generateTransactionStatusResponseFromTransactionEntity(TransactionEntity transactionEntity) { + private TransactionStatusResponse generateTransactionStatusResponseFromTransactionEntity(TransactionEntity transactionEntity, Optional loanRenewalDetails) { TransactionStatus.StatusEnum transactionStatus = transactionEntity.getStatus(); TransactionStatusResponse.StatusEnum transactionStatusResponseStatusEnum = TransactionStatusResponse.StatusEnum.fromValue(transactionStatus.getValue()); DcbTransaction.RoleEnum transactionRole = transactionEntity.getRole(); - + DcbItem dcbItem = loanRenewalDetails.map(loanDetails-> DcbItem.builder() + .renewalPolicy(RenewalPolicy.builder() + .renewalCount(loanDetails.loanRenewalCount()) + .renewalMaxCount(loanDetails.renewalMaxCount()) + .build()) + .build()).orElse(null); return TransactionStatusResponse.builder() .status(transactionStatusResponseStatusEnum) + .item(dcbItem) .role((TransactionStatusResponse.RoleEnum.fromValue(transactionRole.getValue()))) .build(); } diff --git a/src/main/resources/swagger.api/dcb_transaction.yaml b/src/main/resources/swagger.api/dcb_transaction.yaml index a64bc437..e0a1db27 100644 --- a/src/main/resources/swagger.api/dcb_transaction.yaml +++ b/src/main/resources/swagger.api/dcb_transaction.yaml @@ -245,6 +245,10 @@ components: $ref: 'schemas/UserGroup.yaml#/UserGroupCollection' UserCollection: $ref: 'schemas/User.yaml#/UserCollection' + LoanCollection: + $ref: 'schemas/Loan.yaml#/LoanCollection' + LoanPolicyCollection: + $ref: 'schemas/LoanPolicy.yaml#/LoanPolicyCollection' CirculationRequest: $ref: 'schemas/CirculationRequest.yaml#/CirculationRequest' CheckInRequest: diff --git a/src/main/resources/swagger.api/schemas/Loan.yaml b/src/main/resources/swagger.api/schemas/Loan.yaml new file mode 100644 index 00000000..e14ee5ea --- /dev/null +++ b/src/main/resources/swagger.api/schemas/Loan.yaml @@ -0,0 +1,42 @@ +Loan: + type: object + title: Loan + properties: + id: + description: A globally unique (UUID) identifier for the Loan + type: string + userId: + description: A globally unique (UUID) identifier for the user + type: string + itemId: + description: A globally unique (UUID) identifier for the item + type: string + renewalCount: + description: renewalCount of the loan + type: string + loanPolicyId: + description: A unique name belonging to a user. Typically used for login + type: string + status: + "$ref": "Loan.yaml#/status" +status: + type: object + properties: + name: + description: loan status + type: string + +LoanCollection: + type: object + properties: + loans: + type: array + description: "Loan collection" + items: + $ref: "Loan.yaml#/Loan" + totalRecords: + type: integer + additionalProperties: false + required: + - loans + - totalRecords diff --git a/src/main/resources/swagger.api/schemas/LoanPolicy.yaml b/src/main/resources/swagger.api/schemas/LoanPolicy.yaml new file mode 100644 index 00000000..46f8f29d --- /dev/null +++ b/src/main/resources/swagger.api/schemas/LoanPolicy.yaml @@ -0,0 +1,36 @@ +LoanPolicy: + type: object + title: LoanPolicy + properties: + id: + description: A globally unique (UUID) identifier for the LoanPolicy + type: string + name: + description: Policy Name + type: string + renewalsPolicy: + "$ref": "LoanPolicy.yaml#/renewalsPolicy" +renewalsPolicy: + type: object + properties: + unlimited: + description: unlimited + type: boolean + numberAllowed: + description: numberAllowed + type: integer + +LoanPolicyCollection: + type: object + properties: + loanPolicies: + type: array + description: "LoanPolicy collection" + items: + $ref: "LoanPolicy.yaml#/LoanPolicy" + totalRecords: + type: integer + additionalProperties: false + required: + - loanPolicies + - totalRecords diff --git a/src/main/resources/swagger.api/schemas/dcbItem.yaml b/src/main/resources/swagger.api/schemas/dcbItem.yaml index 6a4f0e87..9c0d9fdd 100644 --- a/src/main/resources/swagger.api/schemas/dcbItem.yaml +++ b/src/main/resources/swagger.api/schemas/dcbItem.yaml @@ -17,3 +17,15 @@ DcbItem: lendingLibraryCode: description: The code which identifies the lending library type: string + renewalPolicy: + "$ref": "dcbItem.yaml#/renewalPolicy" + +renewalPolicy: + type: object + properties: + renewalCount: + description: renewalCount of the loan + type: integer + renewalMaxCount: + description: renewalMaxCount of loan as per the loan policy configuration + type: integer