From af8a1f5cac12537e5ad0a09437ebba947f6e6df8 Mon Sep 17 00:00:00 2001 From: fabiodalez-dev Date: Thu, 25 Jun 2026 00:12:29 +0200 Subject: [PATCH] fix(mobile-api): today's date on an available book is an immediate loan, not a reservation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The app's 'Request loan' on an available title sends today's date (its date picker pre-selects the first free day = today), but the backend only did an immediate loan when desired_date was ABSENT — so the available-now flow silently became a reservation instead of a pending loan/admin-approval request. Treat today-or-empty + a free copy as an immediate loan; a FUTURE date stays a reservation. This fixes the already-deployed APK with no app change. New reusable E2E (mobile-api-behaviors #26) asserts today+available -> type=loan. Validated: 27/27 mobile-api-behaviors, PHPStan L5. --- .../src/Controllers/ActionsController.php | 8 +++++-- tests/mobile-api-behaviors.spec.js | 21 +++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/storage/plugins/mobile-api/src/Controllers/ActionsController.php b/storage/plugins/mobile-api/src/Controllers/ActionsController.php index d725b63d..e65162b4 100644 --- a/storage/plugins/mobile-api/src/Controllers/ActionsController.php +++ b/storage/plugins/mobile-api/src/Controllers/ActionsController.php @@ -210,9 +210,13 @@ public function requestReservation(Request $request, ResponseInterface $response // Decide loan vs reservation using the SAME availability gate the web // uses (ReservationManager), so the two surfaces agree. Immediate loan - // only when the user asked for "now" (no date) and a copy is free. + // when the user asked for "now" — either no date, OR today's date with + // a copy free. The app's "Request loan" on an available title sends + // today (its date picker pre-selects the first free day = today), so + // without the today case that flow wrongly became a reservation + // instead of a pending loan. A FUTURE date is always a reservation. $immediate = false; - if ($desired === '') { + if ($desired === '' || $desired === date('Y-m-d')) { $manager = new \App\Controllers\ReservationManager($this->db); $immediate = $manager->isBookAvailableForImmediateLoan($bookId, null, null, $userId); } diff --git a/tests/mobile-api-behaviors.spec.js b/tests/mobile-api-behaviors.spec.js index 672e26a0..fd437796 100644 --- a/tests/mobile-api-behaviors.spec.js +++ b/tests/mobile-api-behaviors.spec.js @@ -600,4 +600,25 @@ test.describe('Reservations', () => { clearBorrowerReservations(); } }); + + test('26) {available book, desired_date=today} → immediate loan (type=loan), not a reservation', async ({ request }) => { + // The app's "Request loan" on an AVAILABLE title sends today's date (its + // date picker pre-selects the first free day = today). The backend must + // treat today + a free copy as an immediate pending loan, not a + // reservation — otherwise the available-now flow silently queues instead + // of requesting a loan. A FUTURE date stays a reservation (test 22). + clearBorrowerReservations(); + try { + const book = pickAvailableBook(); + const res = await call(request, 'POST', '/reservations', { + token: ctx.userToken, + body: { book_id: book, desired_date: todayYmd() }, + }); + expect(res.status(), 'created').toBe(201); + const j = await jsonOf(res); + expect(j?.data?.type, 'today + available → immediate loan').toBe('loan'); + } finally { + clearBorrowerReservations(); + } + }); });