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 10 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
21 changes: 21 additions & 0 deletions descriptors/ModuleDescriptor-template.json
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,27 @@
"unit": "minute",
"delay": "3"
},
{
"methods": [
"POST"
],
"pathPattern": "/circulation/actual-cost-expiration-by-timeout",
"permissionsRequired": [
"pubsub.events.post"
],
roman-barannyk marked this conversation as resolved.
Show resolved Hide resolved
"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: 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 @@ -71,6 +72,7 @@ public void start(Promise<Void> startFuture) {

new CheckOutByBarcodeResource("/circulation/check-out-by-barcode", client).register(router);
new CheckInByBarcodeResource(client).register(router);
new ExpiredActualCostProcessingResource(client).register(router);
roman-barannyk marked this conversation as resolved.
Show resolved Hide resolved

new RenewByBarcodeResource(client).register(router);
new RenewByIdResource(client).register(router);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@ public class ActualCostRecord {
private String feeFineTypeId;
private String feeFineType;
private ZonedDateTime creationDate;
private String 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>>> findActualCostRecordsForLoans(
roman-barannyk marked this conversation as resolved.
Show resolved Hide resolved
MultipleRecords<Loan> multipleLoans) {

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

return getActualCostRecordsForLoans(multipleLoans.getRecords())
.thenApply(r -> r.map(accountMap -> multipleLoans.mapRecords(
roman-barannyk marked this conversation as resolved.
Show resolved Hide resolved
loan -> loan.withActualCostRecord(accountMap.getOrDefault(loan.getId(), null)))));
}

private CompletableFuture<Result<Map<String, ActualCostRecord>>> getActualCostRecordsForLoans(
roman-barannyk marked this conversation as resolved.
Show resolved Hide resolved
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)));
}

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

}
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public ItemRepository(Clients clients) {
new LoanTypeRepository(clients.loanTypesStorage()));
}

public CompletableFuture<Result<Item>> fetchFor(ItemRelatedRecord itemRelatedRecord) {
public CompletableFuture<Result<Item>> fetchItemsWithRelatedRecordsFor(ItemRelatedRecord itemRelatedRecord) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why? Can this be reverted?

if (itemRelatedRecord.getItemId() == null) {
return completedFuture(succeeded(Item.from(null)));
}
Expand Down Expand Up @@ -261,6 +261,13 @@ private CompletableFuture<Result<Item>> fetchItemByBarcode(String barcode) {
return fetchItemsFor(result, includeItemMap, this::fetchItemsWithHoldingsRecords);
}

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>>>
fetchItemsFor(Result<MultipleRecords<T>> result, BiFunction<T, Item, T> includeItemMap,
Function<Collection<String>, CompletableFuture<Result<MultipleRecords<Item>>>> fetcher) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ private CompletableFuture<Result<Loan>> refreshLoanRepresentation(Loan loan) {
}

private CompletableFuture<Result<Loan>> fetchItem(Result<Loan> result) {
return result.combineAfter(itemRepository::fetchFor, Loan::withItem);
return result.combineAfter(itemRepository::fetchItemsWithRelatedRecordsFor, Loan::withItem);
}

private CompletableFuture<Result<Loan>> fetchUser(Result<Loan> result) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ private CompletableFuture<Result<Boolean>> exists(String id) {

public CompletableFuture<Result<Request>> getById(String id) {
return getByIdWithoutItem(id)
.thenComposeAsync(result -> result.combineAfter(itemRepository::fetchFor,
.thenComposeAsync(result -> result.combineAfter(itemRepository::fetchItemsWithRelatedRecordsFor,
Request::withItem))
.thenComposeAsync(result -> result.combineAfter(instanceRepository::fetch,
Request::withInstance))
Expand Down
Loading