cli: add commands for borrowing and delivering items
This commit is contained in:
parent
18053bf002
commit
b2f8d23637
|
@ -18,9 +18,11 @@ from worblehat.services.argument_parser import parse_args
|
||||||
from worblehat.models import *
|
from worblehat.models import *
|
||||||
|
|
||||||
from .prompt_utils import *
|
from .prompt_utils import *
|
||||||
from .subclis.advanced_options import AdvancedOptions
|
from .subclis import (
|
||||||
from .subclis.bookcase_item import BookcaseItemCli
|
AdvancedOptions,
|
||||||
from .subclis.bookcase_shelf_selector import select_bookcase_shelf
|
BookcaseItemCli,
|
||||||
|
select_bookcase_shelf,
|
||||||
|
)
|
||||||
|
|
||||||
# TODO: Category seems to have been forgotten. Maybe relevant interactivity should be added?
|
# TODO: Category seems to have been forgotten. Maybe relevant interactivity should be added?
|
||||||
# However, is there anyone who are going to search by category rather than just look in
|
# However, is there anyone who are going to search by category rather than just look in
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from cmd import Cmd
|
from cmd import Cmd
|
||||||
|
from datetime import datetime
|
||||||
from typing import Any, Callable
|
from typing import Any, Callable
|
||||||
|
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
|
@ -24,6 +25,10 @@ def prompt_yes_no(question: str, default: bool | None = None) -> bool:
|
||||||
}[answer]
|
}[answer]
|
||||||
|
|
||||||
|
|
||||||
|
def format_date(date: datetime):
|
||||||
|
return date.strftime("%a %b %d, %Y")
|
||||||
|
|
||||||
|
|
||||||
class InteractiveItemSelector(Cmd):
|
class InteractiveItemSelector(Cmd):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
from .advanced_options import AdvancedOptions
|
||||||
|
from .bookcase_item import BookcaseItemCli
|
||||||
|
from .bookcase_shelf_selector import select_bookcase_shelf
|
|
@ -6,11 +6,14 @@ from sqlalchemy.orm import Session
|
||||||
from worblehat.cli.prompt_utils import (
|
from worblehat.cli.prompt_utils import (
|
||||||
InteractiveItemSelector,
|
InteractiveItemSelector,
|
||||||
NumberedCmd,
|
NumberedCmd,
|
||||||
|
format_date,
|
||||||
prompt_yes_no,
|
prompt_yes_no,
|
||||||
)
|
)
|
||||||
from worblehat.models import (
|
from worblehat.models import (
|
||||||
Bookcase,
|
Bookcase,
|
||||||
BookcaseItem,
|
BookcaseItem,
|
||||||
|
BookcaseItemBorrowing,
|
||||||
|
BookcaseItemBorrowingQueue,
|
||||||
Language,
|
Language,
|
||||||
MediaType,
|
MediaType,
|
||||||
)
|
)
|
||||||
|
@ -22,13 +25,14 @@ from worblehat.services.bookcase_item import (
|
||||||
from .bookcase_shelf_selector import select_bookcase_shelf
|
from .bookcase_shelf_selector import select_bookcase_shelf
|
||||||
|
|
||||||
def _selected_bookcase_item_prompt(bookcase_item: BookcaseItem) -> str:
|
def _selected_bookcase_item_prompt(bookcase_item: BookcaseItem) -> str:
|
||||||
|
amount_borrowed = len(bookcase_item.borrowings)
|
||||||
return dedent(f'''
|
return dedent(f'''
|
||||||
Item: {bookcase_item.name}
|
Item: {bookcase_item.name}
|
||||||
ISBN: {bookcase_item.isbn}
|
ISBN: {bookcase_item.isbn}
|
||||||
Amount: {bookcase_item.amount}
|
|
||||||
Authors: {', '.join(a.name for a in bookcase_item.authors)}
|
Authors: {', '.join(a.name for a in bookcase_item.authors)}
|
||||||
Bookcase: {bookcase_item.shelf.bookcase.short_str()}
|
Bookcase: {bookcase_item.shelf.bookcase.short_str()}
|
||||||
Shelf: {bookcase_item.shelf.short_str()}
|
Shelf: {bookcase_item.shelf.short_str()}
|
||||||
|
Amount: {bookcase_item.amount - amount_borrowed}/{bookcase_item.amount}
|
||||||
''')
|
''')
|
||||||
|
|
||||||
class BookcaseItemCli(NumberedCmd):
|
class BookcaseItemCli(NumberedCmd):
|
||||||
|
@ -37,10 +41,12 @@ class BookcaseItemCli(NumberedCmd):
|
||||||
self.sql_session = sql_session
|
self.sql_session = sql_session
|
||||||
self.bookcase_item = bookcase_item
|
self.bookcase_item = bookcase_item
|
||||||
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def prompt_header(self) -> str:
|
def prompt_header(self) -> str:
|
||||||
return _selected_bookcase_item_prompt(self.bookcase_item)
|
return _selected_bookcase_item_prompt(self.bookcase_item)
|
||||||
|
|
||||||
|
|
||||||
def do_update_data(self, _: str):
|
def do_update_data(self, _: str):
|
||||||
item = create_bookcase_item_from_isbn(self.sql_session, self.bookcase_item.isbn)
|
item = create_bookcase_item_from_isbn(self.sql_session, self.bookcase_item.isbn)
|
||||||
self.bookcase_item.name = item.name
|
self.bookcase_item.name = item.name
|
||||||
|
@ -53,8 +59,115 @@ class BookcaseItemCli(NumberedCmd):
|
||||||
EditBookcaseCli(self.sql_session, self.bookcase_item, self).cmdloop()
|
EditBookcaseCli(self.sql_session, self.bookcase_item, self).cmdloop()
|
||||||
|
|
||||||
|
|
||||||
def do_loan(self, arg: str):
|
@staticmethod
|
||||||
print('TODO: implement loan')
|
def _prompt_username() -> str:
|
||||||
|
while True:
|
||||||
|
username = input('Username: ')
|
||||||
|
if prompt_yes_no(f'Is {username} correct?', default = True):
|
||||||
|
return username
|
||||||
|
|
||||||
|
|
||||||
|
def _has_active_borrowing(self, username: str) -> bool:
|
||||||
|
return self.sql_session.scalars(
|
||||||
|
select(BookcaseItemBorrowing)
|
||||||
|
.where(
|
||||||
|
BookcaseItemBorrowing.username == username,
|
||||||
|
BookcaseItemBorrowing.item == self.bookcase_item,
|
||||||
|
BookcaseItemBorrowing.delivered != True,
|
||||||
|
)
|
||||||
|
).one_or_none() is not None
|
||||||
|
|
||||||
|
|
||||||
|
def _has_borrowing_queue_item(self, username: str) -> bool:
|
||||||
|
return self.sql_session.scalars(
|
||||||
|
select(BookcaseItemBorrowingQueue)
|
||||||
|
.where(
|
||||||
|
BookcaseItemBorrowingQueue.username == username,
|
||||||
|
BookcaseItemBorrowingQueue.item == self.bookcase_item,
|
||||||
|
)
|
||||||
|
).one_or_none() is not None
|
||||||
|
|
||||||
|
|
||||||
|
def do_borrow(self, _: str):
|
||||||
|
active_borrowings = self.sql_session.scalars(
|
||||||
|
select(BookcaseItemBorrowing)
|
||||||
|
.where(
|
||||||
|
BookcaseItemBorrowing.item == self.bookcase_item,
|
||||||
|
BookcaseItemBorrowing.delivered != True,
|
||||||
|
)
|
||||||
|
.order_by(BookcaseItemBorrowing.end_time)
|
||||||
|
).all()
|
||||||
|
|
||||||
|
if len(active_borrowings) >= self.bookcase_item.amount:
|
||||||
|
print('This item is currently not available')
|
||||||
|
print()
|
||||||
|
print('Active borrowings:')
|
||||||
|
|
||||||
|
for b in active_borrowings:
|
||||||
|
print(f' {b.username} - Until {format_date(b.end_time)}')
|
||||||
|
|
||||||
|
if len(self.bookcase_item.borrowing_queue) > 0:
|
||||||
|
print('Borrowing queue:')
|
||||||
|
for i, b in enumerate(self.bookcase_item.borrowing_queue):
|
||||||
|
print(f' {i + 1} - {b.username}')
|
||||||
|
|
||||||
|
print()
|
||||||
|
|
||||||
|
if not prompt_yes_no('Would you like to enter the borrowing queue?', default = True):
|
||||||
|
return
|
||||||
|
username = self._prompt_username()
|
||||||
|
|
||||||
|
if self._has_active_borrowing(username):
|
||||||
|
print('You already have an active borrowing')
|
||||||
|
return
|
||||||
|
|
||||||
|
if self._has_borrowing_queue_item(username):
|
||||||
|
print('You are already in the borrowing queue')
|
||||||
|
return
|
||||||
|
|
||||||
|
borrowing_queue_item = BookcaseItemBorrowingQueue(username, self.bookcase_item)
|
||||||
|
self.sql_session.add(borrowing_queue_item)
|
||||||
|
print(f'{username} entered the queue!')
|
||||||
|
return
|
||||||
|
|
||||||
|
username = self._prompt_username()
|
||||||
|
|
||||||
|
borrowing_item = BookcaseItemBorrowing(username, self.bookcase_item)
|
||||||
|
self.sql_session.add(borrowing_item)
|
||||||
|
print(f'Successfully delivered the item. Please deliver it back by {format_date(borrowing_item.end_time)}')
|
||||||
|
|
||||||
|
def do_deliver(self, _: str):
|
||||||
|
borrowings = self.sql_session.scalars(
|
||||||
|
select(BookcaseItemBorrowing)
|
||||||
|
.join(BookcaseItem, BookcaseItem.uid == BookcaseItemBorrowing.fk_bookcase_item_uid)
|
||||||
|
.where(BookcaseItem.isbn == self.bookcase_item.isbn)
|
||||||
|
.order_by(BookcaseItemBorrowing.username)
|
||||||
|
).all()
|
||||||
|
|
||||||
|
if len(borrowings) == 0:
|
||||||
|
print('No one seems to have borrowed this item')
|
||||||
|
return
|
||||||
|
|
||||||
|
print('Borrowers:')
|
||||||
|
for i, b in enumerate(borrowings):
|
||||||
|
print(f' {i + 1}) {b.username}')
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
selection = int(input('> '))
|
||||||
|
except ValueError:
|
||||||
|
print('Error: selection must be an integer')
|
||||||
|
continue
|
||||||
|
|
||||||
|
if selection < 1 or selection > len(borrowings):
|
||||||
|
print('Error: selection out of range')
|
||||||
|
continue
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
borrowing = borrowings[selection - 1]
|
||||||
|
borrowing.delivered = True
|
||||||
|
print(f'Successfully delivered the item for {borrowing.username}')
|
||||||
|
|
||||||
|
|
||||||
def do_done(self, _: str):
|
def do_done(self, _: str):
|
||||||
|
@ -63,14 +176,18 @@ class BookcaseItemCli(NumberedCmd):
|
||||||
|
|
||||||
funcs = {
|
funcs = {
|
||||||
1: {
|
1: {
|
||||||
'f': do_loan,
|
'f': do_borrow,
|
||||||
'doc': 'Loan',
|
'doc': 'Borrow',
|
||||||
},
|
},
|
||||||
2: {
|
2: {
|
||||||
|
'f': do_deliver,
|
||||||
|
'doc': 'Deliver',
|
||||||
|
},
|
||||||
|
3: {
|
||||||
'f': do_edit,
|
'f': do_edit,
|
||||||
'doc': 'Edit',
|
'doc': 'Edit',
|
||||||
},
|
},
|
||||||
3: {
|
4: {
|
||||||
'f': do_update_data,
|
'f': do_update_data,
|
||||||
'doc': 'Pull updated data from online databases',
|
'doc': 'Pull updated data from online databases',
|
||||||
},
|
},
|
||||||
|
|
|
@ -43,8 +43,8 @@ class BookcaseItem(Base, UidMixin, UniqueNameMixin):
|
||||||
media_type: Mapped[MediaType] = relationship(back_populates='items')
|
media_type: Mapped[MediaType] = relationship(back_populates='items')
|
||||||
shelf: Mapped[BookcaseShelf] = relationship(back_populates='items')
|
shelf: Mapped[BookcaseShelf] = relationship(back_populates='items')
|
||||||
language: Mapped[Language] = relationship()
|
language: Mapped[Language] = relationship()
|
||||||
borrowings: Mapped[BookcaseItemBorrowing] = relationship(back_populates='item')
|
borrowings: Mapped[set[BookcaseItemBorrowing]] = relationship(back_populates='item')
|
||||||
borrowing_queue: Mapped[BookcaseItemBorrowingQueue] = relationship(back_populates='item')
|
borrowing_queue: Mapped[set[BookcaseItemBorrowingQueue]] = relationship(back_populates='item')
|
||||||
|
|
||||||
categories: Mapped[set[Category]] = relationship(
|
categories: Mapped[set[Category]] = relationship(
|
||||||
secondary = Item_Category.__table__,
|
secondary = Item_Category.__table__,
|
||||||
|
|
|
@ -35,4 +35,6 @@ class BookcaseItemBorrowing(Base, UidMixin):
|
||||||
item: BookcaseItem,
|
item: BookcaseItem,
|
||||||
):
|
):
|
||||||
self.username = username
|
self.username = username
|
||||||
self.item = item
|
self.item = item
|
||||||
|
self.start_time = datetime.now()
|
||||||
|
self.end_time = datetime.now() + timedelta(days=30)
|
|
@ -34,4 +34,5 @@ class BookcaseItemBorrowingQueue(Base, UidMixin):
|
||||||
item: BookcaseItem,
|
item: BookcaseItem,
|
||||||
):
|
):
|
||||||
self.username = username
|
self.username = username
|
||||||
self.item = item
|
self.item = item
|
||||||
|
self.entered_queue_time = datetime.now()
|
Loading…
Reference in New Issue