88import org .apache .logging .log4j .Logger ;
99import org .folio .circulation .domain .Loan ;
1010import 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 ;
1115import 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 ;
1219import org .folio .circulation .infrastructure .storage .inventory .ItemRepository ;
1320import org .folio .circulation .infrastructure .storage .loans .LoanPolicyRepository ;
1421import org .folio .circulation .infrastructure .storage .loans .LoanRepository ;
1926import org .folio .circulation .services .EventPublisher ;
2027import org .folio .circulation .storage .ItemByBarcodeInStorageFinder ;
2128import org .folio .circulation .storage .SingleOpenLoanForItemInStorageFinder ;
22- import org .folio .circulation .support .BadRequestFailure ;
2329import org .folio .circulation .support .Clients ;
24- import org .folio .circulation .support .HttpFailure ;
2530import org .folio .circulation .support .RouteRegistration ;
2631import org .folio .circulation .support .http .OkapiPermissions ;
2732import org .folio .circulation .support .http .server .HttpResponse ;
2833import org .folio .circulation .support .http .server .JsonHttpResponse ;
2934import org .folio .circulation .support .http .server .WebContext ;
3035import org .folio .circulation .support .results .Result ;
36+ import org .folio .circulation .support .utils .ClockUtil ;
3137
3238import java .lang .invoke .MethodHandles ;
39+ import java .time .ZonedDateTime ;
3340import java .util .concurrent .CompletableFuture ;
34- import java .util .function .Supplier ;
3541
3642import 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 ;
3847import static org .folio .circulation .resources .handlers .error .CirculationErrorType .FAILED_TO_FIND_SINGLE_OPEN_LOAN ;
3948
4049public class HoldByBarcodeResource extends Resource {
@@ -60,31 +69,33 @@ private void markHeld(RoutingContext routingContext) {
6069 final var userRepository = new UserRepository (clients );
6170 final var loanRepository = new LoanRepository (clients , itemRepository , userRepository );
6271 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 );
6375 final EventPublisher eventPublisher = new EventPublisher (webContext ,clients );
6476
6577 JsonObject requestBodyAsJson = routingContext .body ().asJsonObject ();
66- Result <HoldByBarcodeRequest > requestResult = HoldByBarcodeRequest .buildRequestFrom (requestBodyAsJson );
6778
68- requestResult
79+ HoldByBarcodeRequest . buildRequestFrom ( requestBodyAsJson )
6980 .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 ())))
7790 .thenCompose (loanResult -> loanResult .after (
7891 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 ))
8193 .thenAccept (webContext ::writeResultToHttpResponse );
8294 }
8395
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 ) {
8899 final ItemByBarcodeInStorageFinder itemFinder =
89100 new ItemByBarcodeInStorageFinder (itemRepository );
90101
@@ -93,36 +104,62 @@ protected CompletableFuture<Result<Loan>> findLoan(HoldByBarcodeRequest request,
93104
94105 return itemFinder .findItemByBarcode (request .getItemBarcode ())
95106 .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 ))
97109 );
98110 }
99111
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 ));
102120 }
103121
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 ));
106125 }
107126
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+ }
110145 }
111146
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 )));
114155 }
115156
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 ());
120159 }
121160
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 ());
126163 }
127164
128165 private HttpResponse toResponse (JsonObject body ) {
0 commit comments