Skip to content

Commit

Permalink
Add Checkout related events along with tests
Browse files Browse the repository at this point in the history
  • Loading branch information
subhashb committed Jun 18, 2024
1 parent c4cce0c commit 7f2ab3e
Show file tree
Hide file tree
Showing 9 changed files with 114 additions and 19 deletions.
2 changes: 1 addition & 1 deletion src/lending/book.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
from protean import handle
from protean.fields import String

from lending.patron import HoldPlaced
from lending.domain import lending
from lending.patron import HoldPlaced


class BookStatus(Enum):
Expand Down
33 changes: 22 additions & 11 deletions src/lending/checkout_service.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from protean import invariant
from protean.exceptions import ValidationError
from protean.exceptions import ObjectNotFoundError, ValidationError
from protean.fields import Identifier

from lending.patron import Checkout, HoldStatus, Patron, PatronType
from lending.book import Book, BookType
from lending.domain import lending
from lending.patron import BookCheckedOut, Checkout, Patron, PatronType


@lending.domain_service(part_of=[Patron, Book])
Expand All @@ -29,19 +29,30 @@ def regular_patron_cannot_place_hold_on_restricted_book(self):
)

def __call__(self):
self.patron.add_checkouts(
Checkout(
book_id=self.book.id,
branch_id=self.branch_id,
)
checkout = Checkout(
book_id=self.book.id,
branch_id=self.branch_id,
)
self.patron.add_checkouts(checkout)

# Find and update hold corresponding to book if it exists
hold = None
try:
hold = next(h for h in self.patron.holds if h.book_id == self.book.id)
except StopIteration:
hold = self.patron.get_one_from_holds(book_id=self.book.id)
except ObjectNotFoundError:
hold = None

if hold:
hold.status = HoldStatus.CHECKED_OUT.value
self.patron.add_holds(hold)
hold.checkout()

# Raise Event
checkout.raise_(
BookCheckedOut(
patron_id=self.patron.id,
patron_type=self.patron.patron_type,
book_id=self.book.id,
branch_id=self.branch_id,
checkout_date=checkout.checkout_date,
due_date=checkout.due_date,
)
)
2 changes: 1 addition & 1 deletion src/lending/holding_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
from protean.fields import Identifier

from lending.book import Book, BookStatus, BookType
from lending.patron import Hold, HoldPlaced, HoldStatus, HoldType, Patron, PatronType
from lending.domain import lending
from lending.patron import Hold, HoldPlaced, HoldStatus, HoldType, Patron, PatronType


@lending.domain_service(part_of=[Patron, Book])
Expand Down
63 changes: 63 additions & 0 deletions src/lending/patron.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ class Hold:
request_date = DateTime(required=True)
expiry_date = Date(required=True)

def checkout(self):
self.status = HoldStatus.CHECKED_OUT.value

def expire(self):
self.status = HoldStatus.EXPIRED.value

Expand Down Expand Up @@ -125,6 +128,43 @@ class CheckoutStatus(Enum):
OVERDUE = "OVERDUE"


@lending.event(part_of="Patron")
class BookCheckedOut:
"""Event raised when a patron checks out a book"""

patron_id = Identifier(required=True)
patron_type = String(required=True)
book_id = Identifier(required=True)
branch_id = Identifier(required=True)
checkout_date = DateTime(required=True)
due_date = Date(required=True)


@lending.event(part_of="Patron")
class BookReturned:
"""Event raised when a patron returns a book"""

patron_id = Identifier(required=True)
patron_type = String(required=True)
book_id = Identifier(required=True)
branch_id = Identifier(required=True)
checkout_date = DateTime(required=True)
due_date = Date(required=True)
return_date = DateTime(required=True)


@lending.event(part_of="Patron")
class BookOverdue:
"""Event raised when a book is marked overdue"""

patron_id = Identifier(required=True)
patron_type = String(required=True)
book_id = Identifier(required=True)
branch_id = Identifier(required=True)
checkout_date = DateTime(required=True)
due_date = Date(required=True)


@lending.entity(part_of="Patron")
class Checkout:
"""The action of a patron borrowing a book from the library
Expand All @@ -145,9 +185,32 @@ def return_(self):
self.status = CheckoutStatus.RETURNED.value
self.return_date = datetime.now()

self.raise_(
BookReturned(
patron_id=self._owner.id,
patron_type=self._owner.patron_type,
book_id=self.book_id,
branch_id=self.branch_id,
checkout_date=self.checkout_date,
due_date=self.due_date,
return_date=self.return_date,
)
)

def overdue(self):
self.status = CheckoutStatus.OVERDUE.value

self.raise_(
BookOverdue(
patron_id=self._owner.id,
patron_type=self._owner.patron_type,
book_id=self.book_id,
branch_id=self.branch_id,
checkout_date=self.checkout_date,
due_date=self.due_date,
)
)


@lending.aggregate
class Patron:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Feature: Check Out a Book
When the patron checks out the book
Then the checkout is successfully completed
And the checkout has a validity of CHECKOUT_PERIOD
And the hold is marked as checked out

Scenario: Patron checks out an available circulating book
Given a circulating book is available
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ Feature: Process Overdue Checkouts
Scenario: System processes and updates the status of overdue checkouts
Given the system has overdue checkouts
When the system processes the overdue checkouts
Then the checkout statuses are updated to overdue
Then the checkouts are marked overdue
4 changes: 2 additions & 2 deletions tests/lending/bdd/checkouts/features/return_a_book.feature
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ Feature: Return a Book
Scenario: Patron returns a book on or before the due date
Given a patron has checked out a book
When the patron returns the book
Then the return is successfully processed
Then the book is successfully returned

Scenario: Patron returns a book after the due date
Given a patron has checked out a book
And the book is overdue
When the patron returns the book
Then the return is successfully processed
Then the book is successfully returned
And the overdue status is cleared
24 changes: 22 additions & 2 deletions tests/lending/bdd/checkouts/step_defs/checkout_steps.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,10 @@ def confirm_checkout_book():
assert len(g.current_user.checkouts) == 1
assert g.current_user.checkouts[0].book_id == g.current_book.id

assert "BookCheckedOut" in [
event.__class__.__name__ for event in g.current_user._events
]


@then(cfparse("the checkout has a validity of {validity_days_config}"))
def confirm_checkout_expiry(validity_days_config):
Expand All @@ -150,13 +154,29 @@ def confirm_returned_status():
assert g.current_user.checkouts[0].status == "RETURNED"


@then("the return is successfully processed")
@then("the book is successfully returned")
def confirm_successful_return():
assert hasattr(g, "current_exception") is False

assert "BookReturned" in [
event.__class__.__name__ for event in g.current_user._events
]


@then("the checkout statuses are updated to overdue")
@then("the checkouts are marked overdue")
def confirm_overdue_marking():
assert g.current_patrons[0].checkouts[0].status == "OVERDUE"
assert g.current_patrons[1].checkouts[0].status == "OVERDUE"
assert hasattr(g, "current_exception") is False

assert "BookOverdue" in [
event.__class__.__name__ for event in g.current_patrons[0]._events
]
assert "BookOverdue" in [
event.__class__.__name__ for event in g.current_patrons[1]._events
]


@then("the hold is marked as checked out")
def confirm_hold_checked_out():
assert g.current_user.holds[0].status == "CHECKED_OUT"
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ Feature: Generate Daily Sheets for Overdue Checkouts
Scenario: System processes and updates the status of overdue checkouts
Given the system has generated a daily sheet for overdue checkouts
When the system processes the overdue checkouts
Then the checkout statuses are updated to overdue
Then the checkouts are marked overdue

0 comments on commit 7f2ab3e

Please sign in to comment.