Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CIRC-1556 Close declared lost loan (Lost and paid status) for actual cost #1159

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
46ed0c6
CIRC-1556 When actual cost is selected, close the loan only if it has…
Mykyta-Varenyk Jun 29, 2022
11a57a2
CIRC-1556 Create actual cost expiration timer
Mykyta-Varenyk Jun 30, 2022
b7b6f3d
Merge branch 'master' of https://github.com/folio-org/mod-circulation…
Mykyta-Varenyk Jun 30, 2022
03d72da
Fix conflicts
Mykyta-Varenyk Jun 30, 2022
4097f26
CIRC-1556 Fix comments
Mykyta-Varenyk Jul 4, 2022
f3ff403
CIRC-1556 Refactor
Mykyta-Varenyk Jul 4, 2022
3e2b0dc
CIRC-1556 Add module permissions
Mykyta-Varenyk Jul 4, 2022
7353375
CIRC-1556 refactoring
roman-barannyk Jul 6, 2022
b600334
CIRC-1556 fix code smells
roman-barannyk Jul 6, 2022
574d5de
CIRC-1556 Extend ItemRelatedRecord
alexanderkurash Jul 6, 2022
55acdb5
CIRC-1556 refactoring according to code review
roman-barannyk Jul 7, 2022
508e5bf
Merge branch 'CIRC-1556' of github.com:folio-org/mod-circulation into…
roman-barannyk Jul 7, 2022
69f0cad
CIRC-1556 refactoring according to code review
roman-barannyk Jul 7, 2022
382f457
CIRC-1556 fix code smell
roman-barannyk Jul 7, 2022
8274392
CIRC-1556 fix code smell
roman-barannyk Jul 7, 2022
b202b6e
CIRC-1557 Refactor close loan service
alexanderkurash Jul 7, 2022
6205170
CIRC-1557 refactoring after code review
roman-barannyk Jul 12, 2022
ad237f3
CIRC-1557 remove code smells
roman-barannyk Jul 12, 2022
01e300c
CIRC-1557 remove unused imports
roman-barannyk Jul 12, 2022
2b0ddf6
CIRC-1557 refactoring after code review
roman-barannyk Jul 12, 2022
0f3057c
CIRC-1557 remove unused import
roman-barannyk Jul 12, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions descriptors/ModuleDescriptor-template.json
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,24 @@
"unit": "minute",
"delay": "3"
},
{
"methods": [
"POST"
],
"pathPattern": "/circulation/actual-cost-expiration-by-timeout",
"modulePermissions": [
"circulation-storage.loans.item.put",
"inventory-storage.items.item.put",
"actual-cost-record-storage.actual-cost-records.collection.get",
"accounts.collection.get",
"circulation.internal.fetch-items",
"lost-item-fees-policies.collection.get",
"circulation-storage.loans.collection.get",
"pubsub.publish.post"
],
"unit": "minute",
"delay": "20"
},
{
"methods": [
"POST"
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
<log4j2.version>2.17.1</log4j2.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<lombok.version>1.18.16</lombok.version>
<lombok.version>1.18.22</lombok.version>
<spring.version>5.2.22.RELEASE</spring.version>
<maven.site.plugin.version>3.9.1</maven.site.plugin.version>
</properties>
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/org/folio/circulation/CirculationVerticle.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.folio.circulation.resources.DeclareLostResource;
import org.folio.circulation.resources.DueDateNotRealTimeScheduledNoticeProcessingResource;
import org.folio.circulation.resources.EndPatronActionSessionResource;
import org.folio.circulation.resources.ExpiredActualCostProcessingResource;
import org.folio.circulation.resources.ExpiredSessionProcessingResource;
import org.folio.circulation.resources.FeeFineScheduledNoticeProcessingResource;
import org.folio.circulation.resources.ItemsInTransitResource;
Expand Down Expand Up @@ -138,6 +139,7 @@ public void start(Promise<Void> startFuture) {
startFuture.fail(result.cause());
}
});
new ExpiredActualCostProcessingResource(client).register(router);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class ActualCostRecord {
private String userBarcode;
private String loanId;
private ItemLossType itemLossType;
private String dateOfLoss;
private ZonedDateTime dateOfLoss;
private String title;
private Collection<Identifier> identifiers;
private String itemBarcode;
Expand All @@ -31,4 +31,5 @@ public class ActualCostRecord {
private String feeFineTypeId;
private String feeFineType;
private ZonedDateTime creationDate;
private ZonedDateTime expirationDate;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@

public interface ItemRelatedRecord {
String getItemId();
ItemRelatedRecord withItem(Item item);
}
34 changes: 22 additions & 12 deletions src/main/java/org/folio/circulation/domain/Loan.java
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ public class Loan implements ItemRelatedRecord, UserRelatedRecord {

private final Policies policies;
private final Collection<Account> accounts;
private final ActualCostRecord actualCostRecord;

public static Loan from(JsonObject representation) {
defaultStatusAndAction(representation);
Expand All @@ -100,7 +101,7 @@ public static Loan from(JsonObject representation) {

return new Loan(representation, null, null, null, null, null,
getDateTimeProperty(representation, DUE_DATE), getDateTimeProperty(representation, DUE_DATE),
new Policies(loanPolicy, overdueFinePolicy, lostItemPolicy), emptyList());
new Policies(loanPolicy, overdueFinePolicy, lostItemPolicy), emptyList(), null);
}

public JsonObject asJson() {
Expand Down Expand Up @@ -272,7 +273,7 @@ public Item getItem() {

public Loan replaceRepresentation(JsonObject newRepresentation) {
return new Loan(newRepresentation, item, user, proxy, checkinServicePoint,
checkoutServicePoint, originalDueDate, previousDueDate, policies, accounts);
checkoutServicePoint, originalDueDate, previousDueDate, policies, accounts, actualCostRecord);
}

public Loan withItem(Item newItem) {
Expand All @@ -283,7 +284,7 @@ public Loan withItem(Item newItem) {
}

return new Loan(newRepresentation, newItem, user, proxy, checkinServicePoint,
checkoutServicePoint, originalDueDate, previousDueDate, policies, accounts);
checkoutServicePoint, originalDueDate, previousDueDate, policies, accounts, actualCostRecord);
}

public User getUser() {
Expand All @@ -298,7 +299,16 @@ public Loan withUser(User newUser) {
}

return new Loan(newRepresentation, item, newUser, proxy, checkinServicePoint,
checkoutServicePoint, originalDueDate, previousDueDate, policies, accounts);
checkoutServicePoint, originalDueDate, previousDueDate, policies, accounts, actualCostRecord);
}

public Loan withActualCostRecord(ActualCostRecord actualCostRecord) {
return new Loan(representation, item, user, proxy, checkinServicePoint, checkoutServicePoint,
originalDueDate, previousDueDate, policies, accounts, actualCostRecord);
}

public ActualCostRecord getActualCostRecord() {
return actualCostRecord;
}

public Loan withPatronGroupAtCheckout(PatronGroup patronGroup) {
Expand All @@ -325,46 +335,46 @@ Loan withProxy(User newProxy) {
}

return new Loan(newRepresentation, item, user, newProxy, checkinServicePoint,
checkoutServicePoint, originalDueDate, previousDueDate, policies, accounts);
checkoutServicePoint, originalDueDate, previousDueDate, policies, accounts, actualCostRecord);
}

public Loan withCheckinServicePoint(ServicePoint newCheckinServicePoint) {
return new Loan(representation, item, user, proxy, newCheckinServicePoint,
checkoutServicePoint, originalDueDate, previousDueDate, policies, accounts);
checkoutServicePoint, originalDueDate, previousDueDate, policies, accounts, actualCostRecord);
}

public Loan withCheckoutServicePoint(ServicePoint newCheckoutServicePoint) {
return new Loan(representation, item, user, proxy, checkinServicePoint,
newCheckoutServicePoint, originalDueDate, previousDueDate, policies, accounts);
newCheckoutServicePoint, originalDueDate, previousDueDate, policies, accounts, actualCostRecord);
}

public Loan withAccounts(Collection<Account> newAccounts) {
return new Loan(representation, item, user, proxy, checkinServicePoint,
checkoutServicePoint, originalDueDate, previousDueDate, policies, newAccounts);
checkoutServicePoint, originalDueDate, previousDueDate, policies, newAccounts, actualCostRecord);
}

public Loan withLoanPolicy(LoanPolicy newLoanPolicy) {
requireNonNull(newLoanPolicy, "newLoanPolicy cannot be null");

return new Loan(representation, item, user, proxy, checkinServicePoint,
checkoutServicePoint, originalDueDate, previousDueDate,
policies.withLoanPolicy(newLoanPolicy), accounts);
policies.withLoanPolicy(newLoanPolicy), accounts, actualCostRecord);
}

public Loan withOverdueFinePolicy(OverdueFinePolicy newOverdueFinePolicy) {
requireNonNull(newOverdueFinePolicy, "newOverdueFinePolicy cannot be null");

return new Loan(representation, item, user, proxy, checkinServicePoint,
checkoutServicePoint, originalDueDate, previousDueDate,
policies.withOverdueFinePolicy(newOverdueFinePolicy), accounts);
policies.withOverdueFinePolicy(newOverdueFinePolicy), accounts, actualCostRecord);
}

public Loan withLostItemPolicy(LostItemPolicy newLostItemPolicy) {
requireNonNull(newLostItemPolicy, "newLostItemPolicy cannot be null");

return new Loan(representation, item, user, proxy, checkinServicePoint,
checkoutServicePoint, originalDueDate, previousDueDate,
policies.withLostItemPolicy(newLostItemPolicy), accounts);
policies.withLostItemPolicy(newLostItemPolicy), accounts, actualCostRecord);
}

public String getLoanPolicyId() {
Expand Down Expand Up @@ -625,7 +635,7 @@ public void closeLoanAsLostAndPaid() {
public Loan copy() {
final JsonObject representationCopy = representation.copy();
return new Loan(representationCopy, item, user, proxy, checkinServicePoint,
checkoutServicePoint, originalDueDate, previousDueDate, policies, accounts);
checkoutServicePoint, originalDueDate, previousDueDate, policies, accounts, actualCostRecord);
}

public Loan ageOverdueItemToLost(ZonedDateTime ageToLostDate) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public class LostItemPolicy extends Policy {
private final Period patronBilledAfterItemAgedToLostInterval;
private final Period recalledItemAgedToLostAfterOverdueInterval;
private final Period patronBilledAfterRecalledItemAgedToLostInterval;
private final Period lostItemChargeFeeFineInterval;
// There is no separate age to lost processing fee but there is a flag
// that turns on/off the fee, but we're modelling it as a separate fee
// to simplify logic.
Expand All @@ -45,7 +46,7 @@ private LostItemPolicy(String id, String name, AutomaticallyChargeableFee declar
Period itemAgedToLostAfterOverdueInterval, Period patronBilledAfterItemAgedToLostInterval,
Period recalledItemAgedToLostAfterOverdueInterval,
Period patronBilledAfterRecalledItemAgedToLostInterval,
AutomaticallyChargeableFee ageToLostProcessingFee) {
AutomaticallyChargeableFee ageToLostProcessingFee, Period lostItemChargeFeeFineInterval) {

super(id, name);
this.declareLostProcessingFee = declareLostProcessingFee;
Expand All @@ -60,6 +61,7 @@ private LostItemPolicy(String id, String name, AutomaticallyChargeableFee declar
this.patronBilledAfterRecalledItemAgedToLostInterval =
patronBilledAfterRecalledItemAgedToLostInterval;
this.ageToLostProcessingFee = ageToLostProcessingFee;
this.lostItemChargeFeeFineInterval = lostItemChargeFeeFineInterval;
}

public static LostItemPolicy from(JsonObject lostItemPolicy) {
Expand All @@ -76,7 +78,8 @@ public static LostItemPolicy from(JsonObject lostItemPolicy) {
getPeriodPropertyOrEmpty(lostItemPolicy, "patronBilledAfterAgedLost"),
getPeriodPropertyOrEmpty(lostItemPolicy, "recalledItemAgedLostOverdue"),
getPeriodPropertyOrEmpty(lostItemPolicy, "patronBilledAfterRecalledItemAgedLost"),
getProcessingFee(lostItemPolicy, "chargeAmountItemSystem")
getProcessingFee(lostItemPolicy, "chargeAmountItemSystem"),
getPeriodPropertyOrEmpty(lostItemPolicy, "lostItemChargeFeeFine")
);
}

Expand Down Expand Up @@ -162,6 +165,10 @@ public boolean canAgeLoanToLost(boolean isRecalled, ZonedDateTime loanDueDate) {
return periodShouldPassSinceOverdue.hasPassedSinceDateTillNow(loanDueDate);
}

public ZonedDateTime calculateFeeFineChargingPeriodExpirationDateTime(ZonedDateTime lostTime) {
return lostItemChargeFeeFineInterval.plusDate(lostTime);
}

public ZonedDateTime calculateDateTimeWhenPatronBilledForAgedToLost(
boolean isRecalled, ZonedDateTime ageToLostDate) {

Expand Down Expand Up @@ -189,7 +196,7 @@ private static class UnknownLostItemPolicy extends LostItemPolicy {
super(id, null, noAutomaticallyChargeableFee(), noAutomaticallyChargeableFee(),
noActualCostFee(), zeroDurationPeriod(), false, false,
zeroDurationPeriod(), zeroDurationPeriod(), zeroDurationPeriod(),
zeroDurationPeriod(), noAutomaticallyChargeableFee());
zeroDurationPeriod(), noAutomaticallyChargeableFee(), zeroDurationPeriod());
}
}
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,45 @@
package org.folio.circulation.infrastructure.storage;

import static java.util.concurrent.CompletableFuture.completedFuture;
import static java.util.function.Function.identity;
import static org.folio.circulation.support.fetching.MultipleCqlIndexValuesCriteria.byIndex;
import static org.folio.circulation.support.fetching.RecordFetching.findWithMultipleCqlIndexValues;
import static org.folio.circulation.support.http.ResponseMapping.forwardOnFailure;
import static org.folio.circulation.support.http.ResponseMapping.mapUsingJson;
import static org.folio.circulation.support.http.client.CqlQuery.exactMatch;
import static org.folio.circulation.support.http.client.PageLimit.one;
import static org.folio.circulation.support.results.Result.ofAsync;
import static org.folio.circulation.support.results.Result.succeeded;
import static org.folio.circulation.support.results.ResultBinding.mapResult;

import java.util.Collection;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

import org.folio.circulation.domain.ActualCostRecord;
import org.folio.circulation.domain.Loan;
import org.folio.circulation.domain.MultipleRecords;
import org.folio.circulation.storage.mappers.ActualCostRecordMapper;
import org.folio.circulation.support.Clients;
import org.folio.circulation.support.CollectionResourceClient;
import org.folio.circulation.support.fetching.CqlQueryFinder;
import org.folio.circulation.support.http.client.CqlQuery;
import org.folio.circulation.support.http.client.PageLimit;
import org.folio.circulation.support.http.client.Response;
import org.folio.circulation.support.http.client.ResponseInterpreter;
import org.folio.circulation.support.results.Result;

import io.vertx.core.json.JsonObject;

public class ActualCostRecordRepository {
private final CollectionResourceClient actualCostRecordStorageClient;

private static final String ACTUAL_COST_RECORDS = "actualCostRecords";
private static final String LOAN_ID_FIELD_NAME = "loanId";

public ActualCostRecordRepository(Clients clients) {
actualCostRecordStorageClient = clients.actualCostRecordsStorage();
}
Expand Down Expand Up @@ -52,6 +72,47 @@ public CompletableFuture<Result<ActualCostRecord>> getActualCostRecordByAccountI
}

private Result<MultipleRecords<ActualCostRecord>> mapResponseToActualCostRecords(Response response) {
return MultipleRecords.from(response, ActualCostRecordMapper::toDomain, "actualCostRecords");
return MultipleRecords.from(response, ActualCostRecordMapper::toDomain, ACTUAL_COST_RECORDS);
}

public CompletableFuture<Result<Loan>> findByLoan(Result<Loan> loanResult) {
return loanResult.after(loan -> createActualCostRecordCqlFinder().findByQuery(
exactMatch(LOAN_ID_FIELD_NAME, loan.getId()), one())
.thenApply(records -> records.map(MultipleRecords::firstOrNull))
.thenApply(mapResult(ActualCostRecordMapper::toDomain))
.thenApply(mapResult(loan::withActualCostRecord)));
Comment on lines +79 to +83
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

call fetchActualCostRecords with one loan

}

public CompletableFuture<Result<MultipleRecords<Loan>>> fetchActualCostRecords(
MultipleRecords<Loan> multipleLoans) {

if (multipleLoans.getRecords().isEmpty()) {
return completedFuture(succeeded(multipleLoans));
}

return buildLoanIdToActualCostRecordMap(multipleLoans.getRecords())
.thenApply(r -> r.map(actualCostRecordMap -> multipleLoans.mapRecords(
loan -> loan.withActualCostRecord(actualCostRecordMap.getOrDefault(loan.getId(), null)))));
}

private CompletableFuture<Result<Map<String, ActualCostRecord>>> buildLoanIdToActualCostRecordMap(
Collection<Loan> loans) {

final Set<String> loanIds = loans.stream()
.filter(Objects::nonNull)
.map(Loan::getId)
.filter(Objects::nonNull)
.collect(Collectors.toSet());

return findWithMultipleCqlIndexValues(actualCostRecordStorageClient,
ACTUAL_COST_RECORDS, ActualCostRecordMapper::toDomain)
.find(byIndex(LOAN_ID_FIELD_NAME, loanIds))
.thenApply(mapResult(r -> r.toMap(ActualCostRecord::getLoanId)));
Comment on lines +98 to +110
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move to one method

}

private CqlQueryFinder<JsonObject> createActualCostRecordCqlFinder() {
return new CqlQueryFinder<>(actualCostRecordStorageClient, ACTUAL_COST_RECORDS,
identity());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -256,9 +256,16 @@ private CompletableFuture<Result<Item>> fetchItemByBarcode(String barcode) {
}

public <T extends ItemRelatedRecord> CompletableFuture<Result<MultipleRecords<T>>>
fetchItemsWithHoldings(Result<MultipleRecords<T>> result, BiFunction<T, Item, T> includeItemMap) {
fetchItemsWithHoldings(Result<MultipleRecords<T>> result, BiFunction<T, Item, T> withItemMapper) {

return fetchItemsFor(result, includeItemMap, this::fetchItemsWithHoldingsRecords);
return fetchItemsFor(result, withItemMapper, this::fetchItems);
}

public <T extends ItemRelatedRecord> CompletableFuture<Result<MultipleRecords<T>>>
fetchItems(Result<MultipleRecords<T>> result) {

return fetchItemsFor(result, (itemRelatedRecord, item) -> (T) itemRelatedRecord.withItem(item),
this::fetchItems);
}

public <T extends ItemRelatedRecord> CompletableFuture<Result<MultipleRecords<T>>>
Expand Down Expand Up @@ -309,13 +316,6 @@ private CompletableFuture<Result<MultipleRecords<Item>>> fetchFor(
.thenComposeAsync(this::fetchItemsRelatedRecords);
}

private CompletableFuture<Result<MultipleRecords<Item>>> fetchItemsWithHoldingsRecords(
Collection<String> itemIds) {

return fetchItems(itemIds)
.thenComposeAsync(this::fetchHoldingsRecords);
}

public CompletableFuture<Result<Item>> fetchItemRelatedRecords(Result<Item> itemResult) {
return itemResult.combineAfter(this::fetchHoldingsRecord, Item::withHoldings)
.thenComposeAsync(combineAfter(this::fetchInstance, Item::withInstance))
Expand Down
Loading