Coverage for src/worblehat/queries/borrowing.py: 36%
45 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-09-25 22:25 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-09-25 22:25 +0000
1from datetime import datetime, timedelta
3from sqlalchemy import exists, select
4from sqlalchemy.orm import Session
6from worblehat.models import BookcaseItem, Borrowing, BorrowingEventType, BorrowingLog
8DEFAULT_LOAN_DAYS = 30
11def list_active_borrowings(sql_session: Session) -> list[Borrowing]:
12 return list(
13 sql_session.scalars(
14 select(Borrowing).order_by(Borrowing.due_time),
15 ).all(),
16 )
19def list_active_borrowings_for_item(
20 sql_session: Session,
21 item: BookcaseItem,
22) -> list[Borrowing]:
23 return list(
24 sql_session.scalars(
25 select(Borrowing)
26 .where(Borrowing.fk_bookcase_item_uid == item.uid)
27 .order_by(Borrowing.due_time),
28 ).all(),
29 )
32def get_active_borrowing(
33 sql_session: Session,
34 username: str,
35 item: BookcaseItem,
36) -> Borrowing | None:
37 return sql_session.scalars(
38 select(Borrowing).where(
39 Borrowing.username == username,
40 Borrowing.fk_bookcase_item_uid == item.uid,
41 ),
42 ).one_or_none()
45def has_active_borrowing(sql_session: Session, username: str, item: BookcaseItem) -> bool:
46 return sql_session.scalar(
47 select(
48 exists().where(
49 Borrowing.username == username,
50 Borrowing.fk_bookcase_item_uid == item.uid,
51 ),
52 ),
53 )
56def list_borrowings_for_isbn(sql_session: Session, isbn: str) -> list[Borrowing]:
57 return list(
58 sql_session.scalars(
59 select(Borrowing)
60 .join(
61 BookcaseItem,
62 BookcaseItem.uid == Borrowing.fk_bookcase_item_uid,
63 )
64 .where(BookcaseItem.isbn == isbn)
65 .order_by(Borrowing.username),
66 ).all(),
67 )
70def list_overdue_borrowings(sql_session: Session) -> list[Borrowing]:
71 return list(
72 sql_session.scalars(
73 select(Borrowing)
74 .where(Borrowing.due_time < datetime.now())
75 .order_by(Borrowing.due_time),
76 ).all(),
77 )
80def list_borrowing_log_for_item(
81 sql_session: Session,
82 item: BookcaseItem,
83) -> list[BorrowingLog]:
84 return list(
85 sql_session.scalars(
86 select(BorrowingLog)
87 .where(BorrowingLog.fk_bookcase_item_uid == item.uid)
88 .order_by(BorrowingLog.due_time),
89 ).all(),
90 )
93def borrow_item(
94 sql_session: Session,
95 username: str,
96 item: BookcaseItem,
97 loan_days: int = DEFAULT_LOAN_DAYS,
98 _unsafe: bool = False,
99) -> tuple[Borrowing, BorrowingLog]:
100 if not _unsafe:
101 if has_active_borrowing(sql_session, username, item):
102 raise ValueError(f"{username} already has an active borrowing of this item")
103 if len(list_active_borrowings_for_item(sql_session, item)) >= item.amount:
104 raise ValueError("No available copies of this item to borrow")
106 due_time = datetime.now() + timedelta(days=loan_days)
107 log_entry = BorrowingLog(username, item, BorrowingEventType.BORROWED, due_time=due_time)
108 sql_session.add(log_entry)
109 sql_session.flush()
110 current = sql_session.get_one(Borrowing, (item.uid, username))
111 return current, log_entry
114def renew_borrowing(
115 sql_session: Session,
116 borrowing: Borrowing,
117 loan_days: int = DEFAULT_LOAN_DAYS,
118 _unsafe: bool = False,
119) -> Borrowing:
120 if (
121 not _unsafe
122 and get_active_borrowing(sql_session, borrowing.username, borrowing.item) is None
123 ):
124 raise ValueError(f"{borrowing.username} does not currently have this item borrowed")
126 due_time = datetime.now() + timedelta(days=loan_days)
127 sql_session.add(
128 BorrowingLog(
129 borrowing.username,
130 borrowing.item,
131 BorrowingEventType.RENEWED,
132 due_time=due_time,
133 ),
134 )
135 sql_session.flush()
136 sql_session.refresh(borrowing)
137 return borrowing
140def return_item(sql_session: Session, borrowing: Borrowing, _unsafe: bool = False) -> None:
141 if (
142 not _unsafe
143 and get_active_borrowing(sql_session, borrowing.username, borrowing.item) is None
144 ):
145 raise ValueError(f"{borrowing.username} does not currently have this item borrowed")
147 sql_session.add(
148 BorrowingLog(borrowing.username, borrowing.item, BorrowingEventType.RETURNED),
149 )
150 sql_session.flush()
151 sql_session.expunge(borrowing)