cli: add commands for borrowing and delivering items

This commit is contained in:
Oystein Kristoffer Tveit 2023-05-10 21:53:49 +02:00
parent 18053bf002
commit b2f8d23637
Signed by: oysteikt
GPG Key ID: 9F2F7D8250F35146
7 changed files with 143 additions and 13 deletions

View File

@ -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

View File

@ -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,

View File

@ -0,0 +1,3 @@
from .advanced_options import AdvancedOptions
from .bookcase_item import BookcaseItemCli
from .bookcase_shelf_selector import select_bookcase_shelf

View File

@ -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',
}, },

View File

@ -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__,

View File

@ -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)

View File

@ -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()