借书/还书/续借/逾期管理
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user