8
8
import org .apache .logging .log4j .Logger ;
9
9
import org .folio .circulation .domain .Loan ;
10
10
import org .folio .circulation .domain .LoanAction ;
11
+ import org .folio .circulation .domain .TimePeriod ;
12
+ import org .folio .circulation .domain .policy .ExpirationDateManagement ;
13
+ import org .folio .circulation .domain .policy .Period ;
14
+ import org .folio .circulation .domain .policy .library .ClosedLibraryStrategy ;
11
15
import org .folio .circulation .domain .representations .logs .LogEventType ;
16
+ import org .folio .circulation .infrastructure .storage .CalendarRepository ;
17
+ import org .folio .circulation .infrastructure .storage .ServicePointRepository ;
18
+ import org .folio .circulation .infrastructure .storage .SettingsRepository ;
12
19
import org .folio .circulation .infrastructure .storage .inventory .ItemRepository ;
13
20
import org .folio .circulation .infrastructure .storage .loans .LoanPolicyRepository ;
14
21
import org .folio .circulation .infrastructure .storage .loans .LoanRepository ;
19
26
import org .folio .circulation .services .EventPublisher ;
20
27
import org .folio .circulation .storage .ItemByBarcodeInStorageFinder ;
21
28
import org .folio .circulation .storage .SingleOpenLoanForItemInStorageFinder ;
22
- import org .folio .circulation .support .BadRequestFailure ;
23
29
import org .folio .circulation .support .Clients ;
24
- import org .folio .circulation .support .HttpFailure ;
25
30
import org .folio .circulation .support .RouteRegistration ;
26
31
import org .folio .circulation .support .http .OkapiPermissions ;
27
32
import org .folio .circulation .support .http .server .HttpResponse ;
28
33
import org .folio .circulation .support .http .server .JsonHttpResponse ;
29
34
import org .folio .circulation .support .http .server .WebContext ;
30
35
import org .folio .circulation .support .results .Result ;
36
+ import org .folio .circulation .support .utils .ClockUtil ;
31
37
32
38
import java .lang .invoke .MethodHandles ;
39
+ import java .time .ZonedDateTime ;
33
40
import java .util .concurrent .CompletableFuture ;
34
- import java .util .function .Supplier ;
35
41
36
42
import static java .lang .String .format ;
37
- import static org .folio .circulation .domain .representations .LoanProperties .USAGE_STATUS_HELD ;
43
+ import static org .folio .circulation .domain .policy .library .ClosedLibraryStrategyUtils .determineClosedLibraryStrategyForHoldShelfExpirationDate ;
44
+ import static org .folio .circulation .domain .representations .LoanProperties .*;
45
+ import static org .folio .circulation .resources .foruseatlocation .HoldByBarcodeRequest .loanIsNotForUseAtLocationFailure ;
46
+ import static org .folio .circulation .resources .foruseatlocation .HoldByBarcodeRequest .noOpenLoanFailure ;
38
47
import static org .folio .circulation .resources .handlers .error .CirculationErrorType .FAILED_TO_FIND_SINGLE_OPEN_LOAN ;
39
48
40
49
public class HoldByBarcodeResource extends Resource {
@@ -60,31 +69,33 @@ private void markHeld(RoutingContext routingContext) {
60
69
final var userRepository = new UserRepository (clients );
61
70
final var loanRepository = new LoanRepository (clients , itemRepository , userRepository );
62
71
final var loanPolicyRepository = new LoanPolicyRepository (clients );
72
+ final var servicePointRepository = new ServicePointRepository (clients );
73
+ final var settingsRepository = new SettingsRepository (clients );
74
+ final var calendarRepository = new CalendarRepository (clients );
63
75
final EventPublisher eventPublisher = new EventPublisher (webContext ,clients );
64
76
65
77
JsonObject requestBodyAsJson = routingContext .body ().asJsonObject ();
66
- Result <HoldByBarcodeRequest > requestResult = HoldByBarcodeRequest .buildRequestFrom (requestBodyAsJson );
67
78
68
- requestResult
79
+ HoldByBarcodeRequest . buildRequestFrom ( requestBodyAsJson )
69
80
.after (request -> findLoan (request , loanRepository , itemRepository , userRepository , errorHandler ))
70
- .thenApply (loan -> failWhenOpenLoanNotFoundForItem (loan , requestResult .value ()))
71
- .thenApply (loan -> failWhenOpenLoanIsNotForUseAtLocation (loan , requestResult .value ()))
72
- .thenCompose (loanPolicyRepository ::findPolicyForLoan )
73
- .thenApply (loanResult -> loanResult .map (loan -> loan .changeStatusOfUsageAtLocation (USAGE_STATUS_HELD )))
74
- .thenApply (loanResult -> loanResult .map (loan -> loan .withAction (LoanAction .HELD_FOR_USE_AT_LOCATION )))
75
- .thenCompose (loanResult -> loanResult .after (
76
- loan -> loanRepository .updateLoan (loanResult .value ())))
81
+ .thenApply (HoldByBarcodeResource ::failWhenOpenLoanNotFoundForItem )
82
+ .thenApply (HoldByBarcodeResource ::failWhenOpenLoanIsNotForUseAtLocation )
83
+ .thenCompose (request -> request .after (req -> findPolicy (req , loanPolicyRepository )))
84
+ .thenCompose (request -> request .after (req -> findServicePoint (req , servicePointRepository )))
85
+ .thenCompose (request -> request .after (req -> findTenantTimeZone (req , settingsRepository )))
86
+ .thenCompose (request -> request .after (req -> findHoldShelfExpirationDate (req , calendarRepository )))
87
+ .thenApply (this ::setStatusToHeldWithExpirationDate )
88
+ .thenApply (this ::setActionHeld )
89
+ .thenCompose (request -> request .after (req -> loanRepository .updateLoan (req .getLoan ())))
77
90
.thenCompose (loanResult -> loanResult .after (
78
91
loan -> eventPublisher .publishUsageAtLocationEvent (loan , LogEventType .LOAN )))
79
- .thenApply (loanResult -> loanResult .map (Loan ::asJson ))
80
- .thenApply (loanAsJsonResult -> loanAsJsonResult .map (this ::toResponse ))
92
+ .thenApply (loanResult -> loanResult .map (Loan ::asJson ).map (this ::toResponse ))
81
93
.thenAccept (webContext ::writeResultToHttpResponse );
82
94
}
83
95
84
- protected CompletableFuture <Result <Loan >> findLoan (HoldByBarcodeRequest request ,
85
- LoanRepository loanRepository , ItemRepository itemRepository , UserRepository userRepository ,
86
- CirculationErrorHandler errorHandler ) {
87
-
96
+ protected CompletableFuture <Result <HoldByBarcodeRequest >> findLoan (HoldByBarcodeRequest request ,
97
+ LoanRepository loanRepository , ItemRepository itemRepository , UserRepository userRepository ,
98
+ CirculationErrorHandler errorHandler ) {
88
99
final ItemByBarcodeInStorageFinder itemFinder =
89
100
new ItemByBarcodeInStorageFinder (itemRepository );
90
101
@@ -93,36 +104,62 @@ protected CompletableFuture<Result<Loan>> findLoan(HoldByBarcodeRequest request,
93
104
94
105
return itemFinder .findItemByBarcode (request .getItemBarcode ())
95
106
.thenCompose (itemResult -> itemResult .after (loanFinder ::findSingleOpenLoan )
96
- .thenApply (r -> errorHandler .handleValidationResult (r , FAILED_TO_FIND_SINGLE_OPEN_LOAN , (Loan ) null ))
107
+ .thenApply (loanResult -> loanResult .map (request ::withLoan ))
108
+ .thenApply (r -> errorHandler .handleValidationResult (r , FAILED_TO_FIND_SINGLE_OPEN_LOAN , request ))
97
109
);
98
110
}
99
111
100
- private static Result <Loan > failWhenOpenLoanNotFoundForItem (Result <Loan > loanResult , HoldByBarcodeRequest request ) {
101
- return loanResult .failWhen (HoldByBarcodeResource ::loanIsNull , loan -> noOpenLoanFailure (request ).get ());
112
+ protected CompletableFuture <Result <HoldByBarcodeRequest >> findPolicy (HoldByBarcodeRequest request , LoanPolicyRepository loanPolicies ) {
113
+ return loanPolicies .findPolicyForLoan (request .getLoan ())
114
+ .thenApply (loanResult -> loanResult .map (request ::withLoan ));
115
+ }
116
+
117
+ protected CompletableFuture <Result <HoldByBarcodeRequest >> findServicePoint (HoldByBarcodeRequest request , ServicePointRepository servicePoints ) {
118
+ return servicePoints .getServicePointById (request .getLoan ().getCheckoutServicePointId ())
119
+ .thenApply (servicePoint -> servicePoint .map (request ::withServicePoint ));
102
120
}
103
121
104
- private Result <Loan > failWhenOpenLoanIsNotForUseAtLocation (Result <Loan > loanResult , HoldByBarcodeRequest request ) {
105
- return loanResult .failWhen (HoldByBarcodeResource ::loanIsNotForUseAtLocation , loan -> loanIsNotForUseAtLocationFailure (request ).get ());
122
+ protected CompletableFuture <Result <HoldByBarcodeRequest >> findTenantTimeZone (HoldByBarcodeRequest request , SettingsRepository settings ) {
123
+ return settings .lookupTimeZoneSettings ()
124
+ .thenApply (zoneId -> zoneId .map (request ::withTenantTimeZone ));
106
125
}
107
126
108
- private static Result <Boolean > loanIsNull (Loan loan ) {
109
- return Result .succeeded (loan == null );
127
+ protected CompletableFuture <Result <HoldByBarcodeRequest >> findHoldShelfExpirationDate (HoldByBarcodeRequest request , CalendarRepository calendars ) {
128
+ Loan loan = request .getLoan ();
129
+ Period expiry = loan .getLoanPolicy ().getHoldShelfExpiryPeriodForUseAtLocation ();
130
+ if (expiry == null ) {
131
+ log .warn ("No hold shelf expiry period for use at location defined in loan policy {}" , loan .getLoanPolicy ().getName ());
132
+ return Result .ofAsync (request );
133
+ } else {
134
+ final ZonedDateTime baseExpirationDate = expiry .plusDate (ClockUtil .getZonedDateTime ());
135
+ TimePeriod timePeriod = loan .getLoanPolicy ().getHoldShelfExpiryTimePeriodForUseAtLocation ();
136
+ ExpirationDateManagement expirationDateManagement = request .getServicePoint ().getHoldShelfClosedLibraryDateManagement ();
137
+ ClosedLibraryStrategy strategy = determineClosedLibraryStrategyForHoldShelfExpirationDate (
138
+ expirationDateManagement , baseExpirationDate , request .getTenantTimeZone (), timePeriod );
139
+
140
+ return calendars .lookupOpeningDays (baseExpirationDate .withZoneSameInstant (request .getTenantTimeZone ()).toLocalDate (),
141
+ request .getServicePoint ().getId ())
142
+ .thenApply (adjacentOpeningDaysResult -> strategy .calculateDueDate (baseExpirationDate , adjacentOpeningDaysResult .value ()))
143
+ .thenApply (dateTime -> dateTime .map (request ::withHoldShelfExpirationDate ));
144
+ }
110
145
}
111
146
112
- private static Result <Boolean > loanIsNotForUseAtLocation (Loan loan ) {
113
- return Result .succeeded (!loan .isForUseAtLocation ());
147
+ private Result <HoldByBarcodeRequest > setStatusToHeldWithExpirationDate (Result <HoldByBarcodeRequest > request ) {
148
+ return request .map (
149
+ req -> req .withLoan (req .getLoan ().changeStatusOfUsageAtLocation (USAGE_STATUS_HELD , req .getHoldShelfExpirationDate ())));
150
+ }
151
+
152
+
153
+ private Result <HoldByBarcodeRequest > setActionHeld (Result <HoldByBarcodeRequest > request ) {
154
+ return request .map (req -> req .withLoan (req .getLoan ().withAction (LoanAction .HELD_FOR_USE_AT_LOCATION )));
114
155
}
115
156
116
- private static Supplier <HttpFailure > noOpenLoanFailure (HoldByBarcodeRequest request ) {
117
- String message = "No open loan found for the item barcode." ;
118
- log .warn (message );
119
- return () -> new BadRequestFailure (format (message + " (%s)" , request .getItemBarcode ()));
157
+ private static Result <HoldByBarcodeRequest > failWhenOpenLoanNotFoundForItem (Result <HoldByBarcodeRequest > request ) {
158
+ return request .failWhen (HoldByBarcodeRequest ::loanIsNull , req -> noOpenLoanFailure (req ).get ());
120
159
}
121
160
122
- private static Supplier <HttpFailure > loanIsNotForUseAtLocationFailure (HoldByBarcodeRequest request ) {
123
- String message = "The loan is open but is not for use at location." ;
124
- log .warn (message );
125
- return () -> new BadRequestFailure (format (message + ", item barcode (%s)" , request .getItemBarcode ()));
161
+ private static Result <HoldByBarcodeRequest > failWhenOpenLoanIsNotForUseAtLocation (Result <HoldByBarcodeRequest > request ) {
162
+ return request .failWhen (HoldByBarcodeRequest ::loanIsNotForUseAtLocation , req -> loanIsNotForUseAtLocationFailure (req ).get ());
126
163
}
127
164
128
165
private HttpResponse toResponse (JsonObject body ) {
0 commit comments