Skip to content

fix(loans): promuovi la testa della coda quando le prenotazioni saturano le copie (#157)#199

Open
fabiodalez-dev wants to merge 1 commit into
mainfrom
fix/reservation-promotion-157
Open

fix(loans): promuovi la testa della coda quando le prenotazioni saturano le copie (#157)#199
fabiodalez-dev wants to merge 1 commit into
mainfrom
fix/reservation-promotion-157

Conversation

@fabiodalez-dev

@fabiodalez-dev fabiodalez-dev commented Jun 26, 2026

Copy link
Copy Markdown
Owner

Problema

La manutenzione prenotazioni (cron/full-maintenance.phpMaintenanceService::processScheduledReservations) non promuoveva nessuna prenotazione in prestito pendente quando una waitlist saturava il titolo: 1 copia + 2 prenotazioni attive → 0 promosse invece di 1.

Causa

Il gate di promozione (ReservationManager::processBookAvailabilityisDateRangeAvailableCapacityService::hasFreeCapacity) contava ogni prenotazione attiva sovrapposta come occupante una unità di capacità, escludendo solo quella in promozione. Con 1 copia e 2 prenotazioni, promuovendo la testa (queue_position=1) la seconda (queue_position=2) veniva comunque contata come occupante l'unica copia → occ == total → testa mai promossa. Entrambi i run convertivano 0; il test F.21b (#157) vedeva 0 pending invece di 1.

Fix

Il path di promozione ora passa la queue_position della prenotazione promossa, così il capacity gate ignora le prenotazioni attive con queue_position strettamente maggiore (le entry di coda a priorità inferiore, promosse nei run successivi man mano che le copie si liberano).

  • Le prenotazioni davanti in coda (posizione minore) contano ancora.
  • Una queue_position NULL (legacy) conta ancora (conservativo — mai overbook).
  • Il nuovo parametro è opzionale (default null): admin-creation gate e overbooked-auditor, che condividono CapacityService, restano invariati.

Verifica

  • PHPStan livello 5 pulito.
  • Repro col cron: run 1 → 1 pending, run 2 → resta 1 (niente doppia conversione).
  • E2E: loan-reservation-complete 26/26 (F.21b ora passa), loan-overlap 35/35 (anti-overbook intatto), loan-state-model 4/4.

Emerso eseguendo l'intera suite E2E; pre-esistente su main, indipendente dal PR Swagger #197.

Summary by CodeRabbit

  • Bug Fixes
    • Migliorata la gestione della disponibilità per le prenotazioni in lista d’attesa: in alcuni casi le promozioni potevano essere bloccate anche quando c’erano copie sufficienti.
    • La verifica della capienza ora considera meglio la posizione in coda, riducendo i blocchi non necessari durante l’avanzamento delle prenotazioni.
    • Più accurata la valutazione della disponibilità su intervalli di date, con risultati più coerenti nei casi di promozione dalla waitlist.

…be the copies (#157)

The reservation-maintenance cron failed to convert any reservation into a pending
loan when a fully-subscribed waitlist queued for a title — e.g. 1 copy + 2 active
reservations promoted 0 instead of 1.

Root cause: the promotion gate (ReservationManager::processBookAvailability →
isDateRangeAvailable → CapacityService::hasFreeCapacity) counted EVERY active
reservation overlapping the period as occupying a capacity unit, excluding only the
one being promoted. With 1 copy and 2 queued reservations, promoting the head (queue
position 1) still counted the trailing entry (position 2) as occupying the only copy,
so occ == total and the head was never promoted. Both runs converted 0; F.21b (#157)
saw 0 pending instead of 1.

Fix: the promotion path now passes the promoted reservation's queue_position so the
capacity gate ignores active reservations with a known queue_position strictly greater
than it — lower-priority waitlist entries promoted in later runs as copies free up.
Reservations AHEAD in the queue (lower position) still count, and a legacy NULL
queue_position still counts (conservative — never overbook). The new parameter defaults
to null, so the admin-creation gate and overbooked auditor sharing CapacityService are
unchanged.

Verified: PHPStan level 5 clean; cron repro now promotes exactly one head (run 1 → 1
pending, run 2 → still 1); E2E loan-reservation-complete 26/26 (F.21b passes),
loan-overlap 35/35 (overbook prevention intact), loan-state-model 4/4.
@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

La promozione di una prenotazione ora calcola la posizione di coda della testa e la passa al controllo di capacità. Il calcolo della disponibilità esclude le prenotazioni con queue_position successiva e continua a contare quelle senza posizione.

Changes

Promozione prenotazione e gate di capacità

Layer / File(s) Summary
Passaggio della testa coda
app/Controllers/ReservationManager.php
processBookAvailability() calcola headQueuePos, estende isDateRangeAvailable() e inoltra excludeReservationsAfterQueuePos alla verifica di capacità.
Propagazione dei parametri di capacità
app/Services/CapacityService.php
occupiedCount() e hasFreeCapacity() aggiungono excludeUserId e excludeReservationsAfterQueuePos e propagano i nuovi parametri nel calcolo dell’occupazione.
Filtro delle prenotazioni attive
app/Services/CapacityService.php
activeReservationIntervals() accetta excludeReservationsAfterQueuePos e applica il filtro SQL che esclude le prenotazioni con queue_position maggiore, mantenendo quelle NULL.

Effort di revisione

🎯 3 (Moderate) | ⏱️ ~20 minuti

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Il titolo descrive correttamente la promozione della testa della coda quando le prenotazioni saturano le copie.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 60.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/reservation-promotion-157

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/Controllers/ReservationManager.php`:
- Around line 196-199: In ReservationManager::promoteNextReservation, the queue
reordering path still uses nextReservation['queue_position'] directly, which
turns NULL into 0 and corrupts positions. Reuse the resolved queue position
already stored in $headQueuePos, and when it is null, treat the
promotion/reorder as starting from 1 instead of passing a zero value into
updateQueuePositions. Make the fix in the promotion flow around
isDateRangeAvailable and updateQueuePositions so the same validated queue
position is used consistently.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: bb211fa2-61d6-42d6-8158-d11bc58d2920

📥 Commits

Reviewing files that changed from the base of the PR and between eab030e and 7536cca.

📒 Files selected for processing (2)
  • app/Controllers/ReservationManager.php
  • app/Services/CapacityService.php

Comment on lines +196 to +199
$headQueuePos = isset($nextReservation['queue_position'])
? (int) $nextReservation['queue_position']
: null;
if ($this->isDateRangeAvailable($bookId, $startDate, $endDate, (int) $nextReservation['id'], $headQueuePos)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Gestisci anche il riordino quando queue_position è NULL.

Qui $headQueuePos può diventare null e la promozione può proseguire; più avanti però updateQueuePositions($bookId, (int) $nextReservation['queue_position']) trasforma NULL in 0, decrementando tutte le posizioni > 0 e creando code con posizione 0. Usa lo stesso valore risolto e riordina da 1 quando manca una posizione valida.

🐛 Fix proposto
-                $headQueuePos = isset($nextReservation['queue_position'])
+                $headQueuePos = isset($nextReservation['queue_position'])
                     ? (int) $nextReservation['queue_position']
                     : null;
                 if ($this->isDateRangeAvailable($bookId, $startDate, $endDate, (int) $nextReservation['id'], $headQueuePos)) {
@@
-                    $this->updateQueuePositions($bookId, (int) $nextReservation['queue_position']);
+                    if ($headQueuePos !== null) {
+                        $this->updateQueuePositions($bookId, $headQueuePos);
+                    } else {
+                        $this->reorderQueuePositions($bookId);
+                    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/Controllers/ReservationManager.php` around lines 196 - 199, In
ReservationManager::promoteNextReservation, the queue reordering path still uses
nextReservation['queue_position'] directly, which turns NULL into 0 and corrupts
positions. Reuse the resolved queue position already stored in $headQueuePos,
and when it is null, treat the promotion/reorder as starting from 1 instead of
passing a zero value into updateQueuePositions. Make the fix in the promotion
flow around isDateRangeAvailable and updateQueuePositions so the same validated
queue position is used consistently.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant