01 — Product Overview (Library Module)
- 1. Purpose
- 2. Business goals
- 3. User goals
- 4. Stakeholders
- 5. Why this exists
- 6. Dependencies
- 7. Success metrics
- 8. Edge cases (contract level)
- 9. Assumptions (module)
- 10. Open questions (module-grain; global ledger in 00-shared/12)
- 11. Glossary (this module)
StudyLyon — multi-tenant ERP / School Management API. This package designs the Library module client (Flutter, forward-looking spec) against the implemented NestJS backend. All endpoints, DTO fields, schemas, domain events, permissions, and wire contracts are derived directly from
src/modules/library/**,src/modules/rbac/permissions.constants.ts, anddocs/IMPLEMENTATION_PLAN.md. No feature is invented — anything not present in source is flagged(planned)/(proposed)/(forward-looking)in Assumptions & Open Questions.
Heads-up: per the PRD, the mobile client is out of Phase 1; this package is the forward-looking spec the client will be built against later. Everything below is best-effort UI design on top of the current backend surface.
Blueprint note: studylyon-blueprint/04-Modules/ has no dedicated Library doc;
the module is only sketched as "Books / Issue / Return / Fines" responsibilities
(MODULE_ARCHITECTURE.md:546-557) and appears in the fee-category list
(COLLECTIONS.md:2247). The code is the source of truth.
1. Purpose
Library manages the school's physical book collection and the borrow lifecycle:
- Catalog: books with title/author/ISBN, optional publisher/category/edition,
copy counts (
totalCopies/availableCopies), and a status (available|borrowed|damaged|lost). - Issue/return: staff hand a copy to a student with an explicit due date; returning closes the record, restores availability, and computes a fine when late.
- Fines: computed server-side at return time at a fixed daily rate, tracked as
fineStatus (pending|paid|waived), payable via a dedicated endpoint. - Borrow visibility: full history and active-borrow lists per student.
| Responsibility | Source |
|---|---|
| Book CRUD (create/list/search/get/patch/soft-delete) | library.controller.ts:27-59, library.service.ts:34-105 |
| Unique ISBN per tenant (409 on duplicate) | library.service.ts:35-40, book.schema.ts:51 |
| Issue book (availability + 5-book cap, decrement copies) | library.service.ts:107-150 |
Return book (restore copies, compute fine, BookReturned event) | library.service.ts:152-193 |
| Borrow history / active borrows per student | library.service.ts:195-201, borrow-record.repository.ts:21-43 |
| Pay fine | library.service.ts:203-209 |
| Daily fine rate = 5 units/day (hardcoded) | library.service.ts:25,211-217 |
Permissions vocabulary (books.read/create/update/delete/issue/return, fines.pay) | permissions.constants.ts:55-61 |
| Tenant scoping + soft-delete on every query | BaseRepository (base.repository.ts), all repos extend it |
2. Business goals
| Goal | Measure |
|---|---|
| No duplicate books | unique index {tenantId, isbn} (book.schema.ts:51) + service 409 (library.service.ts:37-39) |
| Never over-issue | availableCopies >= 1 checked before decrement (library.service.ts:109-111) |
| Never exceed student limit | max 5 active borrows enforced (library.service.ts:116-120) |
| Fines always computable | fineAmount = overdueDays × 5 at return (library.service.ts:211-217) |
| No orphaned deletes | book with active borrows cannot be deleted — 409 (library.service.ts:97-102) |
| Cross-tenant isolation | every query tenant-scoped via BaseRepository.scopedFilter |
| Audit trail | BookIssued / BookReturned domain events on the event bus (library.service.ts:141-148,180-191) |
3. User goals
- Librarian / staff: search the catalog; add, edit, delete books; issue and return copies; see overdue borrowers; collect fine payments.
- Student: browse/search the catalog; see which books are currently on loan to them and their history; know what's overdue.
- Parent: (read-only, via student) see the child's active loans and any fine.
- Org admin: catalog oversight, permission assignment (who may issue/return),
configuration of limits
(planned).
4. Stakeholders
Librarian/staff with books.* permissions, org admin (RBAC), students, parents,
the RBAC module (permissions.constants.ts), events/notifications pipeline, QA +
design + engineering.
5. Why this exists
A school library without tracking loses books and revenue. The backend enforces the hard invariants (ISBN uniqueness, availability, 5-book cap, tenant isolation, soft delete). The client's job is to present the catalog and loan state authoritatively, never guess server state, and treat every write (create, issue, return, fine pay) as server-confirmed.
6. Dependencies
| Dependency | Role | Source |
|---|---|---|
| Students module | studentId on borrow records (ref: 'Student') | borrow-record.schema.ts:25-26 |
| Users module | issuedBy actor (ref: 'User', currently never written) | borrow-record.schema.ts:28-29 |
| Event bus | BookIssued, BookReturned events | library.service.ts:141-148,180-191 |
| Files module | cover/attachment upload surface (not wired to books yet) | files.controller.ts:29-71 |
| RBAC | books.*, fines.pay permissions (enforced later (planned)) | permissions.constants.ts:55-61 |
7. Success metrics
- Catalog search (title/author/ISBN) round-trip < 2 s (regex search, indexed title/author).
- Duplicate-ISBN attempt handled as 409 100% of the time.
- Issue of an exhausted book always blocked (409), even under concurrent requests (see QA-1 in 14_QA_Checklist.md — read-then-write race today).
- Fine amount at return always equals
ceil(daysOverdue) × 5unless staff overrides. - Zero cross-tenant leaks (
BaseRepositoryscope).
8. Edge cases (contract level)
- Duplicate ISBN → 409
DUPLICATE_RESOURCE"Book with ISBN "..." already exists." (library.service.ts:37-39). - No copies available → 409 "No copies available for borrowing."
(
library.service.ts:109-111). - Student at 5 active loans → 409 "Student already has maximum number of borrowed
books." (
library.service.ts:116-120). - Delete book with active loans → 409 "Cannot delete book with active borrow
records." (
library.service.ts:99-101); delete is a soft delete (book.repository.tsviaBaseRepository.softDelete,library.service.ts:103). - Return a non-active record → 409 "Book was not actively borrowed."
(
library.service.ts:155-157). - Unknown book/record → 404
RESOURCE_NOT_FOUND(library.service.ts:80,92,103,154,170,207). - Invalid Mongo id →
CastError→ 400VALIDATION_ERROR"Invalid resource identifier." (http-exception.filter.ts). - Copy-count edits adjust
availableCopiesby the same delta, floored at 0 (library.service.ts:87-90). - Staff override fine — return DTO may set
fineAmountexplicitly (return-book.dto.ts:9-13); server usesdto.fineAmount ?? calculateFine(dueDate)(library.service.ts:159). fineStatuson time — records withfineAmount = 0keepfineStatus: undefined(library.service.ts:166);WAIVEDis never produced by any endpoint.
9. Assumptions (module)
- PRD: mobile client out of Phase 1 — forward-looking spec; backend remains the contracts authority.
- Endpoints are guarded only by
JwtAuthGuard(library.controller.ts:22) — no@Permissions()metadata anywhere;books.read/create/update/delete/issue/returnandfines.payexist inALL_PERMISSIONS(permissions.constants.ts:55-61) but are not enforced. Real RBAC is(planned)(OQ-1). - Overdue is not computed by the server.
BorrowStatus.OVERDUEand repositoryfindOverdue()exist (borrow-record.schema.ts:9-11,borrow-record.repository.ts:34-43) but no service endpoint/worker sets it. Overdue detection is client-derived today; a scheduled scan is(planned)(IMPLEMENTATION_PLAN.md:228) (OQ-2). issuedByis never populated — the schema has the field (borrow-record.schema.ts:28-29) butissueBookdoesn't set it (library.service.ts:132-139) (OQ-3).- Fines are units (plain
Number,library.service.ts:25rate = 5); no currency/format contract server-side. BookStatus.DAMAGED/LOSTandBorrowStatus.LOSTexist in enums but no endpoint transitions to them — damage/loss workflow(planned)(OQ-4).- No pagination on borrow endpoints (
library.service.ts:195-201) — client paging(proposed). - Search covers title/author/ISBN only (
book.repository.ts:26-30); category/ publisher/status filters must be client-side (OQ-5). - Catalog import/export is
(planned)(IMPLEMENTATION_PLAN.md:172,228); QR/barcode scanning is(forward-looking); analytics(proposed).
10. Open questions (module-grain; global ledger in 00-shared/12)
| # | Item | Impact |
|---|---|---|
| OQ-1 | No RBAC decorators on library endpoints; perms exist but unenforced | Role-gated UI waits for guard wiring |
| OQ-2 | No overdue worker — OVERDUE status and findOverdue() unused | Overdue badge derived client-side; server scan (planned) |
| OQ-3 | issuedBy never written on issue | "Issued by" attribution unavailable |
| OQ-4 | No damage/lost transitions, no renew/reserve endpoints | Those workflows (planned) |
| OQ-5 | Server search limited to title/author/ISBN; no status/category filters | Client-side filter only |
| OQ-6 | payFine doesn't guard status — can pay a fine on a still-active loan, or twice (idempotent overwrite) | Pay button gating is client responsibility |
| OQ-7 | Issue is read-then-write (availableCopies) — no atomic conditional update | Concurrent double-issue race (QA-1) |
| OQ-8 | No book-cover field on Book; files module exists separately | Cover upload (proposed) |
| OQ-9 | No me borrows endpoint — student view needs studentId from profile | GET /books/borrows/:studentId/active requires profile lookup |
11. Glossary (this module)
| Term | Meaning |
|---|---|
| Book | books doc: title, author, isbn, publisher?, category?, edition?, totalCopies, availableCopies, status `available |
| Borrow record | borrow_records doc: bookId, studentId, issuedBy?, borrowedAt, dueDate, returnedAt?, status `active |
| Issue | staff lends a copy: decrement availability, create ACTIVE record with due date |
| Return | close an ACTIVE record: restore availability, set RETURNED + returnedAt, compute fine |
| Fine | overdueDays × 5 units (library.service.ts:25,211-217), status `pending |
| Envelope | {success, message, data, meta?, timestamp, requestId} (response-envelope.interceptor.ts:44-52) |