借书/还书/续借/逾期管理

This commit is contained in:
Zzzz
2026-04-27 21:19:23 +08:00
parent 38b31ddbb9
commit 7502890a77
27 changed files with 2535 additions and 31 deletions
@@ -302,6 +302,131 @@ readers/form.jsp -> JDBC -> DELETE FROM readers using request parameters
readers/form.jsp -> ReaderManagementServlet -> ReaderService -> ReaderDao -> readers.status = inactive
```
## Scenario: Borrowing Circulation Management Slice
### 1. Scope / Trigger
- Trigger: borrowing circulation now spans `borrow_records`, book inventory,
reader eligibility, Servlet routes, service transactions, DAO locks, and JSP
management/history screens.
- Schema path: `src/main/resources/db/schema.sql`.
- JSP paths: `WEB-INF/jsp/borrowing/manage.jsp`,
`borrowing/form.jsp`, and `reader/loans.jsp`.
### 2. Signatures
- Entity signatures: `BorrowRecord` fields are `id`, `readerId`,
`readerIdentifier`, `readerName`, `bookId`, `bookIdentifier`, `bookTitle`,
`borrowedAt`, `dueAt`, nullable `returnedAt`, `renewalCount`, `status`,
`createdAt`, and `updatedAt`.
- Status signature: `BorrowRecordStatus` enum codes are `active` and
`returned`; overdue is derived from active, non-returned rows where
`due_at < CURRENT_TIMESTAMP`.
- Search signature: `BorrowRecordSearchCriteria(readerIdentifier,
bookIdentifier, statusCode)` where `statusCode` may be empty, `active`,
`returned`, or the derived filter `overdue`.
- DAO signatures: `BorrowRecordDao.search(criteria)`,
`findByReaderId(readerId)`, `findReaderByUserId(userId)`,
`findReaderByIdentifierForUpdate(connection, identifier)`,
`findBookByIdentifierForUpdate(connection, identifier)`,
`findByIdForUpdate(connection, id)`, `countActiveByReaderId(connection,
readerId)`, `create(connection, record)`, `decrementAvailableCopies(...)`,
`incrementAvailableCopies(...)`, `markReturned(...)`, and `renew(...)`.
- Service signatures: `BorrowingService.searchRecords(actor, criteria)`,
`borrowBook(actor, readerIdentifier, bookIdentifier)`,
`returnBook(actor, recordId)`, `renewLoan(actor, recordId)`, and
`listCurrentReaderHistory(actor)`, all returning `ServiceResult<T>`.
- Management routes: `GET /borrowing`, `GET /borrowing/new`,
`POST /borrowing/create`, `POST /borrowing/return`, and
`POST /borrowing/renew`.
- Reader route: `GET /reader/loans`.
- Protected permissions: `/borrowing*` requires `MANAGE_BORROWING`;
`/reader/loans` requires `BORROW_BOOKS` and the `reader` role because it
displays only the signed-in reader's own history.
- DB signature: `borrow_records(id, reader_id, book_id, borrowed_at, due_at,
returned_at, renewal_count, status, created_at, updated_at)`, with foreign
keys to `readers(id)` and `books(id)`, indexes on reader, book, status, and
due date, and checks for non-negative renewal count and supported statuses.
### 3. Contracts
- Borrow operations require an active reader and a borrowable book with
`BookStatus.AVAILABLE` and `available_copies > 0`.
- Reader active-loan count is rows with `status = active` and `returned_at IS
NULL`; overdue rows still count toward `max_borrow_count`.
- Borrowing creates one `borrow_records` row and decrements
`books.available_copies` in the same transaction.
- Returning an active loan sets `status = returned`, stores `returned_at`, and
increments `books.available_copies` with a cap at `total_copies` in the same
transaction.
- Renewing an active loan extends `due_at`, increments `renewal_count`, and is
limited to one renewal per MVP loan.
- `JdbcUtil.executeInTransaction` is the local transaction helper for
multi-table borrowing workflows. Services decide the workflow boundary; DAOs
own SQL and row-lock statements.
- Demo `readers` and `books` seed rows must not overwrite existing rows during
schema replay, because resetting reader eligibility or `available_copies` can
corrupt live borrowing state.
- Servlet controllers set JSP attributes such as `criteria`, `statuses`,
`overdueStatus`, `maxRenewals`, `borrowRecords`, `formValues`, `errors`,
`errorMessage`, and `successMessage`.
- JSP pages render JavaBean properties only; they must not call DAOs or embed
SQL.
### 4. Validation & Error Matrix
- Missing reader ID or book ID -> return to `borrowing/form.jsp` with field
errors.
- Unknown reader -> field error on `readerIdentifier`.
- Inactive or suspended reader -> field error on `readerIdentifier`.
- Reader at or above `max_borrow_count` active loans -> field error on
`readerIdentifier`.
- Unknown book -> field error on `bookIdentifier`.
- Unavailable, archived, or zero-copy book -> field error on `bookIdentifier`.
- Missing or non-positive record ID for return/renew -> flash error.
- Returning or renewing a returned loan -> validation failure on `status`.
- Renewing after the renewal limit -> validation failure on `renewalCount`.
- DAO/transaction failure -> log server-side details and return
`Borrowing service is temporarily unavailable. Please try again later.`
### 5. Good/Base/Bad Cases
- Good: a librarian creates a loan for active reader `RD-1000` and available
book `BK-1000`; the loan appears in `/borrowing` and book availability is
decremented.
- Base: `/borrowing?status=overdue` lists active, non-returned loans past due
without requiring a scheduler or stored overdue status.
- Bad: a Servlet updates `books.available_copies` outside the borrowing
transaction or a JSP issues SQL to filter loan records.
### 6. Tests Required
- Run `BorrowingServiceCheck` assertions for permission denial, inactive
readers, unavailable/no-copy books, max active loan count, successful borrow,
return inventory restoration, one-renewal limit, overdue search, reader loan
history, and DAO failure fallback.
- Run `PermissionPolicyCheck` to confirm readers have `BORROW_BOOKS`, readers
lack `MANAGE_BORROWING`, and librarians lack reader self-borrow permission.
Service or filter checks should also confirm staff use `/borrowing`, not the
reader-only `/reader/loans` history route.
- Scan JSPs for scriptlets and SQL/JDBC references.
- When Maven/Tomcat dependencies are installed, run `mvn clean package` to
compile Servlets and package JSP resources.
### 7. Wrong vs Correct
#### Wrong
```text
borrowing/manage.jsp -> JDBC -> UPDATE books SET available_copies = ...
```
#### Correct
```text
borrowing/form.jsp -> BorrowingManagementServlet -> BorrowingService -> BorrowRecordDao -> borrow_records + books in one transaction
```
## Scenario: Login And Permission Scaffold Schema
### 1. Scope / Trigger