Skip to content

Commit

Permalink
DailySheet view, repository, and hold/checkout sync event handler
Browse files Browse the repository at this point in the history
  • Loading branch information
subhashb committed Jun 19, 2024
1 parent 6bd3721 commit 6332bbf
Show file tree
Hide file tree
Showing 18 changed files with 625 additions and 281 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -162,4 +162,6 @@ cython_debug/
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

.DS_Store
.DS_Store
*.todo
todos
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"module": "pytest",
"justMyCode": false,
"args": [
"tests/lending/bdd/holds/test_holds.py::test_patron_cancels_an_active_hold",
"tests/lending/bdd/checkouts/test_checkouts.py::test_patron_checks_out_a_book_on_hold",
]
},
]
Expand Down
284 changes: 11 additions & 273 deletions poetry.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ classifiers=[

[tool.poetry.dependencies]
python = "^3.11"
protean = {version = "0.12.0", extras=[] }
# protean = {version = "0.12.0", extras=[] }
protean = {path = "../../protean", develop = true}

[tool.poetry.group.test]
optional = true
Expand Down
4 changes: 4 additions & 0 deletions src/lending/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
PatronType,
)

from lending.app.dailysheet import DailySheet # isort:skip


__all__ = [
"Patron",
"PatronType",
Expand All @@ -32,4 +35,5 @@
"place_hold",
"DailySheetService",
"checkout",
"DailySheet",
]
269 changes: 269 additions & 0 deletions src/lending/app/dailysheet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
from datetime import date, timedelta
from typing import Union

from protean import handle
from protean.exceptions import ObjectNotFoundError
from protean.fields import Auto, Date, DateTime, Identifier, String

from lending import Checkout, CheckoutStatus, Hold, HoldStatus
from lending.domain import lending
from lending.model.patron import (
BookCheckedOut,
BookOverdue,
BookReturned,
HoldCancelled,
HoldExpired,
HoldPlaced,
)


@lending.view
class DailySheet:
id = Auto(identifier=True)
patron_id = Identifier(required=True)
patron_type = String(required=True)
hold_id = Identifier()
hold_book_id = Identifier()
hold_branch_id = Identifier()
hold_type = String()
hold_status = String()
hold_requested_at = DateTime()
hold_expires_on = Date()
checkout_id = Identifier()
checkout_book_id = Identifier()
checkout_branch_id = Identifier()
checkout_checked_out_at = DateTime()
checkout_status = String()
checkout_due_on = Date()
checkout_returned_at = DateTime()
checkout_overdue_at = DateTime()


@lending.repository(part_of=DailySheet)
class DailySheetRepository:
def find_hold_for_patron(self, patron_id, hold_id) -> Union[Hold, None]:
daily_sheet_record = None
try:
daily_sheet_record = self._dao.find_by(
patron_id=patron_id,
hold_id=hold_id,
)
except ObjectNotFoundError:
pass

return daily_sheet_record

def find_checkout_for_patron(self, patron_id, checkout_id) -> Union[Checkout, None]:
daily_sheet_record = None
try:
daily_sheet_record = self._dao.find_by(
patron_id=patron_id,
checkout_id=checkout_id,
)
except ObjectNotFoundError:
pass

return daily_sheet_record

def expiring_holds(self, on=date.today() - timedelta(days=1)):
return (
self._dao.query.filter(
hold_status=HoldStatus.ACTIVE.value,
hold_expires_on=on,
)
.all()
.items
)

def expired_holds(self):
return (
self._dao.query.filter(
hold_status=HoldStatus.EXPIRED.value,
)
.all()
.items
)

def checkouts_to_be_marked_overdue(self, on=date.today() - timedelta(days=1)):
return (
self._dao.query.filter(
checkout_status=CheckoutStatus.ACTIVE.value,
checkout_due_on=on,
)
.all()
.items
)

def overdue_checkouts(self):
return (
self._dao.query.filter(
checkout_status=CheckoutStatus.OVERDUE.value,
)
.all()
.items
)


@lending.event_handler(stream_name="patron")
class DailySheetManager:
@handle(HoldExpired)
def handle_hold_expired(self, event: HoldExpired):
repo = lending.repository_for(DailySheet)

daily_sheet_record = repo.find_hold_for_patron(
patron_id=event.patron_id,
hold_id=event.hold_id,
)

if daily_sheet_record:
daily_sheet_record.hold_status = HoldStatus.EXPIRED.value
else:
daily_sheet_record = DailySheet(
patron_id=event.patron_id,
patron_type=event.patron_type,
hold_id=event.hold_id,
hold_status=HoldStatus.EXPIRED.value,
hold_book_id=event.book_id,
hold_branch_id=event.branch_id,
hold_type=event.hold_type,
hold_requested_at=event.requested_at,
hold_expires_on=event.expires_on,
)

repo.add(daily_sheet_record)

@handle(HoldPlaced)
def handle_hold_placed(self, event: HoldPlaced):
repo = lending.repository_for(DailySheet)

daily_sheet_record = repo.find_hold_for_patron(
patron_id=event.patron_id,
hold_id=event.hold_id,
)

if daily_sheet_record:
daily_sheet_record.hold_status = HoldStatus.EXPIRED.value
else:
daily_sheet_record = DailySheet(
patron_id=event.patron_id,
patron_type=event.patron_type,
hold_id=event.hold_id,
hold_status=HoldStatus.ACTIVE.value,
hold_book_id=event.book_id,
hold_branch_id=event.branch_id,
hold_type=event.hold_type,
hold_requested_at=event.requested_at,
hold_expires_on=event.expires_on,
)

repo.add(daily_sheet_record)

@handle(HoldCancelled)
def handle_hold_cancelled(self, event: HoldCancelled):
repo = lending.repository_for(DailySheet)

daily_sheet_record = repo.find_hold_for_patron(
patron_id=event.patron_id,
hold_id=event.hold_id,
)

if daily_sheet_record:
daily_sheet_record.hold_status = HoldStatus.CANCELLED.value
else:
daily_sheet_record = DailySheet(
patron_id=event.patron_id,
patron_type=event.patron_type,
hold_id=event.hold_id,
hold_status=HoldStatus.CANCELLED.value,
hold_book_id=event.book_id,
hold_branch_id=event.branch_id,
hold_type=event.hold_type,
hold_requested_at=event.requested_at,
hold_expires_on=event.expires_on,
)

repo.add(daily_sheet_record)

@handle(BookCheckedOut)
def handle_book_checked_out(self, event: BookCheckedOut):
repo = lending.repository_for(DailySheet)

daily_sheet_record = repo.find_checkout_for_patron(
patron_id=event.patron_id,
checkout_id=event.checkout_id,
)

if daily_sheet_record:
daily_sheet_record.checkout_id = event.checkout_id
daily_sheet_record.checkout_book_id = event.book_id
daily_sheet_record.checkout_branch_id = event.branch_id
daily_sheet_record.checkout_checked_out_at = event.checked_out_at
daily_sheet_record.checkout_due_on = event.due_on
daily_sheet_record.checkout_status = "ACTIVE"
else:
daily_sheet_record = DailySheet(
patron_id=event.patron_id,
patron_type=event.patron_type,
checkout_id=event.checkout_id,
checkout_book_id=event.book_id,
checkout_branch_id=event.branch_id,
checkout_checked_out_at=event.checked_out_at,
checkout_due_on=event.due_on,
checkout_status="ACTIVE",
)

repo.add(daily_sheet_record)

@handle(BookReturned)
def handle_book_returned(self, event: BookReturned):
repo = lending.repository_for(DailySheet)

daily_sheet_record = repo.find_checkout_for_patron(
patron_id=event.patron_id,
checkout_id=event.checkout_id,
)

if daily_sheet_record:
daily_sheet_record.checkout_returned_at = event.returned_at
daily_sheet_record.checkout_status = "RETURNED"
else:
daily_sheet_record = DailySheet(
patron_id=event.patron_id,
patron_type=event.patron_type,
checkout_id=event.checkout_id,
checkout_book_id=event.book_id,
checkout_branch_id=event.branch_id,
checkout_checked_out_at=event.checked_out_at,
checkout_due_on=event.due_on,
checkout_returned_at=event.returned_at,
checkout_status="RETURNED",
)

repo.add(daily_sheet_record)

@handle(BookOverdue)
def handle_book_overdue(self, event: BookOverdue):
repo = lending.repository_for(DailySheet)

daily_sheet_record = repo.find_checkout_for_patron(
patron_id=event.patron_id,
checkout_id=event.checkout_id,
)

if daily_sheet_record:
daily_sheet_record.checkout_overdue_at = event.checked_out_at
daily_sheet_record.checkout_status = "OVERDUE"
else:
daily_sheet_record = DailySheet(
patron_id=event.patron_id,
patron_type=event.patron_type,
checkout_id=event.checkout_id,
checkout_book_id=event.book_id,
checkout_branch_id=event.branch_id,
checkout_checked_out_at=event.checked_out_at,
checkout_due_on=event.due_on,
checkout_overdue_at=event.checked_out_at,
checkout_status="OVERDUE",
)

repo.add(daily_sheet_record)
2 changes: 1 addition & 1 deletion src/lending/model/book.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class Book:
)


@lending.event_handler(part_of=Book)
@lending.event_handler(part_of=Book, stream_name="patron")
class PatronHoldEventsHandler:
@handle(HoldPlaced)
def mark_book_on_hold(self, event: HoldPlaced):
Expand Down
1 change: 1 addition & 0 deletions src/lending/model/checkout_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ def __call__(self):
# Raise Event
checkout.raise_(
BookCheckedOut(
checkout_id=checkout.id,
patron_id=self.patron.id,
patron_type=self.patron.patron_type,
book_id=self.book.id,
Expand Down
5 changes: 5 additions & 0 deletions src/lending/model/patron.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ class CheckoutStatus(Enum):
class BookCheckedOut:
"""Event raised when a patron checks out a book"""

checkout_id = Identifier(required=True)
patron_id = Identifier(required=True)
patron_type = String(required=True)
book_id = Identifier(required=True)
Expand All @@ -144,6 +145,7 @@ class BookCheckedOut:
class BookReturned:
"""Event raised when a patron returns a book"""

checkout_id = Identifier(required=True)
patron_id = Identifier(required=True)
patron_type = String(required=True)
book_id = Identifier(required=True)
Expand All @@ -157,6 +159,7 @@ class BookReturned:
class BookOverdue:
"""Event raised when a book is marked overdue"""

checkout_id = Identifier(required=True)
patron_id = Identifier(required=True)
patron_type = String(required=True)
book_id = Identifier(required=True)
Expand Down Expand Up @@ -187,6 +190,7 @@ def return_(self):

self.raise_(
BookReturned(
checkout_id=self.id,
patron_id=self._owner.id,
patron_type=self._owner.patron_type,
book_id=self.book_id,
Expand All @@ -202,6 +206,7 @@ def overdue(self):

self.raise_(
BookOverdue(
checkout_id=self.id,
patron_id=self._owner.id,
patron_type=self._owner.patron_type,
book_id=self.book_id,
Expand Down
5 changes: 5 additions & 0 deletions tests/lending/bdd/checkouts/step_defs/checkout_steps.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,8 @@ def confirm_returned_status():

@then("the book is successfully returned")
def confirm_successful_return():
if hasattr(g, "current_exception"):
print(g.current_exception.messages)
assert hasattr(g, "current_exception") is False

assert "BookReturned" in [
Expand All @@ -168,6 +170,9 @@ def confirm_successful_return():
def confirm_overdue_marking():
assert g.current_patrons[0].checkouts[0].status == "OVERDUE"
assert g.current_patrons[1].checkouts[0].status == "OVERDUE"

if hasattr(g, "current_exception"):
print(g.current_exception.messages)
assert hasattr(g, "current_exception") is False

assert "BookOverdue" in [
Expand Down
Empty file.
Loading

0 comments on commit 6332bbf

Please sign in to comment.