Skip to content

feat(copies): per-copy labels + loan/return by code + camera scanner (#238)#243

Merged
fabiodalez-dev merged 5 commits into
mainfrom
feat/copy-tracking-labels-scan
Jul 9, 2026
Merged

feat(copies): per-copy labels + loan/return by code + camera scanner (#238)#243
fabiodalez-dev merged 5 commits into
mainfrom
feat/copy-tracking-labels-scan

Conversation

@fabiodalez-dev

@fabiodalez-dev fabiodalez-dev commented Jul 9, 2026

Copy link
Copy Markdown
Owner

Implements discussion #238 requests 2 and 3 for physical-copy tracking. Builds on the model that already exists (copie.numero_inventario UNIQUE, prestiti.copia_id) — no schema change.

Phase 1 — labels + loan/return by copy code (a39354d4)

  • Per-copy labels: GET /admin/books/{id}/copy-labels-pdf prints one label per copy, each with its numero_inventario as text + a Code128 barcode (the book-level label is unchanged). "Print copy labels" button on the copies card.
  • Copy resolver: GET /admin/copies/by-code?code=…{found, copy_id, libro_id, titolo, stato, available}.
  • Loan by copy: the loan form takes an optional copy code; store() resolves it (FOR UPDATE), validates book + availability, and pins that exact copia_id. Empty = auto-assign as before (backward compatible).
  • Return by copy: POST /admin/loans/return-by-code finds the active loan for a code and returns it via the existing processReturn path (no duplicated logic).

Verified live: by-code resolves real + missing codes; the labels PDF renders (200, application/pdf) and contains the copy code.

Phase 2 — in-browser camera scanner (e508db41)

  • A Scan button next to both copy-code inputs opens a zxing-wasm camera modal, decodes the label barcode, and fills the input (never auto-submits; stops all camera tracks on close).
  • The .wasm is served same-origin (webpack asset/resource) so CSP connect-src self holds; script-src gains wasm-unsafe-eval (minimal wasm directive), synced in .htaccess + index.php. Built bundle + wasm committed (release ships committed assets).

Verified headless: buttons bind, modal opens, wasm loads (200 application/wasm), zero CSP/WebAssembly errors, clean teardown. Live camera decode needs a real device (https/localhost) — cannot run headless.

Soft-delete scoped, admin routes English literals, strings in __() across it/en/de/fr, prepared statements. PHPStan L5 clean.

Remaining for #238: request #1 (subtitle in search) already shipped in #240.

Summary by CodeRabbit

  • Nuove Funzionalità

    • Aggiunta la ricerca di copie tramite codice inventario.
    • Introdotta la stampa di etichette PDF per ogni copia fisica.
    • Aggiunta la restituzione rapida dei prestiti tramite codice copia.
    • Inserito il supporto alla scansione barcode da fotocamera per compilare i campi rapidamente.
  • Miglioramenti

    • Aggiornate le schermate di prestiti e libri con nuovi pulsanti, campi e messaggi di supporto.
    • Aggiunte traduzioni e testi UI in più lingue per i nuovi flussi.
    • Migliorata la compatibilità browser per la scansione e la gestione delle risorse web.

… copy code

Phase 1 of physical-copy tracking (discussion #238 requests 2a + 2b). Builds on the
existing model (copie.numero_inventario UNIQUE, prestiti.copia_id) — no schema change.

2a — Per-copy labels: GET /admin/books/{id}/copy-labels-pdf prints one label per
non-deleted copy, each carrying that copy's numero_inventario as text + a Code128
barcode (the label barcode payload is now parameterizable; the book-level label
endpoint is unchanged). "Print copy labels" button added to the copies card.

2b — Loan/return against a specific copy:
- GET /admin/copies/by-code?code=<numero_inventario> resolves a copy to
  {found, copy_id, libro_id, titolo, stato, available}.
- The loan-create form takes an optional copy code; PrestitiController::store()
  resolves it (FOR UPDATE), validates it belongs to the book and is available, and
  pins that exact copia_id instead of auto-assigning. Empty code = auto-assign as
  before (backward compatible).
- POST /admin/loans/return-by-code finds the single active loan for a scanned/typed
  code and returns it through the existing processReturn path (no duplicated logic).

The camera scanner that fills these code fields is Phase 2. Verified end-to-end
against the dev install: by-code resolves real + missing codes, the copy-labels PDF
renders (200, application/pdf) and contains the copy's numero_inventario. Soft-delete
scoped, admin routes English literals, strings in __() across it/en/de/fr, prepared
statements. php -l + PHPStan L5 clean.
… codes

Phase 2 of physical-copy tracking (discussion #238 request 3). Adds a "Scan" button
next to the copy-code inputs from Phase 1 (loan-create form + return-by-code form)
that opens a camera modal, decodes the Code128 barcode printed on the per-copy label,
and fills the input — closing the gap between the copy having a code and using it
without a hardware scanner.

- frontend/js/copy-scanner.js: reusable scanner modal on zxing-wasm. Decodes
  Code128/EAN/Code39/QR, fills the target input (never auto-submits), and stops all
  MediaStream tracks on success/cancel/Esc/error (no leaked camera). Graceful
  fallback messages for permission-denied / no-camera / unsupported; manual typing
  stays fully usable.
- zxing-wasm's .wasm is served SAME-ORIGIN (webpack asset/resource) so the CSP
  connect-src 'self' isn't violated — the default CDN wasm URL is superseded. CSP
  script-src gains 'wasm-unsafe-eval' (the minimal directive to compile wasm; not
  general unsafe-eval), kept in sync in public/.htaccess and public/index.php.
- Scan buttons target #copy_code (loan) and #return_copy_code (return). i18n across
  it/en/de/fr. Built bundle + wasm committed (create-release ships committed assets).

Verified in a headless browser: buttons bind, modal opens, self-hosted wasm loads
(200 application/wasm), zero CSP/WebAssembly errors, clean teardown. Live camera
decode requires a real device/camera (https or localhost) — cannot run headless.
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@fabiodalez-dev, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 22 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 28556dfc-6f9d-47d6-9cd9-e96db99a3599

📥 Commits

Reviewing files that changed from the base of the PR and between e508db4 and 7fedefd.

📒 Files selected for processing (9)
  • app/Controllers/CopyController.php
  • app/Controllers/LibriController.php
  • app/Controllers/PrestitiController.php
  • app/Views/partials/copy-scanner-i18n.php
  • app/Views/prestiti/crea_prestito.php
  • app/Views/prestiti/index.php
  • frontend/js/copy-scanner.js
  • public/assets/copy-scanner.bundle.js
  • tests/loan-overlap.spec.js
📝 Walkthrough

Walkthrough

Introdotta gestione copie tramite codice inventario: nuovo endpoint CopyController::byCode, assegnazione forzata copia in PrestitiController::store(), endpoint returnByCode per restituzione rapida, stampa etichette PDF per copia (generateCopyLabelsPDF), scanner barcode client-side (zxing-wasm), nuove rotte, viste, traduzioni multilingua, CSP aggiornata e utility CSS.

Changes

Gestione codice copia/inventario e scanner barcode

Layer / File(s) Summary
Risoluzione copia via codice
app/Controllers/CopyController.php, app/Routes/web.php
Nuovo metodo byCode risolve una copia da query code, calcola disponibilità e risponde in JSON; nuova rotta admin protetta.
Assegnazione forzata copia nei prestiti
app/Controllers/PrestitiController.php
In store(), input copy_code risolve/lock la copia, verifica proprietà/stato/sovrapposizioni; selezione automatica condizionata all'assenza di copia forzata.
Restituzione rapida per codice copia
app/Controllers/PrestitiController.php, app/Routes/web.php, app/Views/prestiti/index.php, app/Views/prestiti/crea_prestito.php
Nuovo returnByCode() individua prestito attivo via numero_inventario e delega a processReturn(); query details() include processed_by_name; nuova rotta e form UI con campi errore dedicati.
Stampa etichette PDF per copia
app/Controllers/LibriController.php, app/Routes/web.php, app/Views/libri/scheda_libro.php
Nuovo generateCopyLabelsPDF genera una pagina per copia con barcode e override testuale; renderer portrait/landscape aggiornati; nuova rotta e link UI.
Scanner barcode client-side
frontend/js/copy-scanner.js, frontend/package.json, frontend/webpack.config.js, public/assets/copy-scanner.bundle.js, public/.htaccess, public/index.php
Modulo scanBarcode() con zxing-wasm apre modal camera, decodifica e compila input target; build webpack aggiornata; CSP consente wasm-unsafe-eval e frame-src about:.
Traduzioni e stili di supporto
locale/de_DE.json, locale/en_US.json, locale/fr_FR.json, locale/it_IT.json, public/assets/main.css
Nuove stringhe i18n per codice copia/scanner/etichette; nuove classi CSS utility per layout.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.92% which is insufficient. The required threshold is 60.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Il titolo è conciso e descrive chiaramente le modifiche principali: etichette per copia, prestito/restituzione per codice e scanner camera.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/copy-tracking-labels-scan

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: 5

🤖 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/CopyController.php`:
- Around line 87-102: The “copy held” SQL predicate is duplicated in
CopyController, including the availability check in this block and the same
logic in updateCopy, which risks future drift. Extract the shared query into a
private helper like isCopyHeld(mysqli $db, int $copyId): bool, have the current
availability logic call that helper, and reuse it from updateCopy so the
predicate stays single-sourced.

In `@app/Controllers/LibriController.php`:
- Around line 2694-2825: `generateCopyLabelsPDF` duplicates the same label setup
logic already present in `generateLabelPDF`, inflating complexity and making
both methods harder to maintain. Extract the repeated settings lookup,
collocazione resolution, and author string building into shared private helpers
such as `resolveLabelSettings()`, `resolveCollocazione()`, and
`buildAuthorString()`, then reuse them from both `generateCopyLabelsPDF` and
`generateLabelPDF` so the label generation flow stays the same but the
duplicated blocks are removed.

In `@app/Controllers/PrestitiController.php`:
- Around line 254-313: The `copyCode` lookup in `PrestitiController::create` is
taking an unnecessary lock on `libri` via the JOIN, which can create lock-order
deadlocks. Remove the `JOIN libri` from the `SELECT ... FOR UPDATE` used to
fetch the copy, and read only from `copie` in that query. Keep the existing
`libro_id` comparison and availability checks in place, since those already
validate the selected book and its state.

In `@app/Views/prestiti/index.php`:
- Around line 774-788: The window.copyScannerI18n block is duplicated in this
view and in crea_prestito.php, so the same scanner translation strings can drift
if only one copy is updated. Move the shared i18n object into a single reusable
partial or shared config include, and have both views render that common source
instead of keeping separate inline copies. Use the existing
window.copyScannerI18n setup and the copy-scanner.bundle.js inclusion as the
place to centralize the shared strings.

In `@frontend/js/copy-scanner.js`:
- Around line 203-228: The camera stream is not released when video.play() fails
after a successful getUserMedia() call in copy-scanner.js. Update the promise
chain around navigator.mediaDevices.getUserMedia() and video.play() so that any
failure after stream assignment stops all tracks on stream before showing the
error message; keep the existing settled guard, and make sure the cleanup
happens in the video.play() failure path as well as in the cancellation path.
🪄 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: 577da0e5-5284-41d7-924f-4e49a2e30571

📥 Commits

Reviewing files that changed from the base of the PR and between 0449169 and e508db4.

⛔ Files ignored due to path filters (2)
  • frontend/package-lock.json is excluded by !**/package-lock.json
  • public/assets/df0ae38955633a7ef478.wasm is excluded by !**/*.wasm
📒 Files selected for processing (18)
  • app/Controllers/CopyController.php
  • app/Controllers/LibriController.php
  • app/Controllers/PrestitiController.php
  • app/Routes/web.php
  • app/Views/libri/scheda_libro.php
  • app/Views/prestiti/crea_prestito.php
  • app/Views/prestiti/index.php
  • frontend/js/copy-scanner.js
  • frontend/package.json
  • frontend/webpack.config.js
  • locale/de_DE.json
  • locale/en_US.json
  • locale/fr_FR.json
  • locale/it_IT.json
  • public/.htaccess
  • public/assets/copy-scanner.bundle.js
  • public/assets/main.css
  • public/index.php

Comment thread app/Controllers/CopyController.php
Comment thread app/Controllers/LibriController.php
Comment thread app/Controllers/PrestitiController.php
Comment thread app/Views/prestiti/index.php
Comment thread frontend/js/copy-scanner.js
@fabiodalez-dev fabiodalez-dev merged commit f22c923 into main Jul 9, 2026
6 checks passed
@fabiodalez-dev fabiodalez-dev deleted the feat/copy-tracking-labels-scan branch July 9, 2026 17:46
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