22from datetime import datetime , timedelta
33from typing import Optional
44
5+ from modules .bidding .domain .events import (
6+ BidWasPlaced ,
7+ BidWasRetracted ,
8+ HighestBidderWasOutbid ,
9+ ListingWasCancelled ,
10+ )
511from modules .bidding .domain .rules import (
612 BidCanBeRetracted ,
713 ListingCanBeCancelled ,
8- PlacedBidMustBeGreaterOrEqualThanNextMinimumBid ,
14+ PriceOfPlacedBidMustBeGreaterOrEqualThanNextMinimumPrice ,
915)
1016from modules .bidding .domain .value_objects import Bid , Bidder , Seller
1117from seedwork .domain .entities import AggregateRoot
@@ -26,18 +32,6 @@ class ListingCannotBeCancelled(DomainException):
2632 ...
2733
2834
29- class BidPlacedEvent (DomainEvent ):
30- ...
31-
32-
33- class BidRetractedEvent (DomainEvent ):
34- ...
35-
36-
37- class ListingCancelledEvent (DomainEvent ):
38- ...
39-
40-
4135@dataclass (kw_only = True )
4236class Listing (AggregateRoot [GenericUUID ]):
4337 seller : Seller
@@ -61,34 +55,57 @@ def next_minimum_price(self) -> Money:
6155 return self .current_price + Money (amount = 1 , currency = self .ask_price .currency )
6256
6357 # public commands
64- def place_bid (self , bid : Bid ) -> DomainEvent :
58+ def place_bid (self , bid : Bid ):
6559 """Public method"""
6660 self .check_rule (
67- PlacedBidMustBeGreaterOrEqualThanNextMinimumBid (
61+ PriceOfPlacedBidMustBeGreaterOrEqualThanNextMinimumPrice (
6862 current_price = bid .max_price , next_minimum_price = self .next_minimum_price
6963 )
7064 )
7165
66+ previous_winner_id = self .highest_bid .bidder .id if self .highest_bid else None
67+ current_winner_id = bid .bidder .id
68+
7269 if self .has_bid_placed_by (bidder = bid .bidder ):
7370 self ._update_bid (bid )
7471 else :
7572 self ._add_bid (bid )
7673
77- return BidPlacedEvent (
78- listing_id = self .id , bidder = bid .bidder , max_price = bid .max_price
74+ self .register_event (
75+ BidWasPlaced (
76+ listing_id = self .id ,
77+ bidder_id = bid .bidder .id ,
78+ )
7979 )
8080
81- def retract_bid_of (self , bidder : Bidder ) -> DomainEvent :
81+ # if there was previous winner...
82+ if previous_winner_id is not None and previous_winner_id != current_winner_id :
83+ self .register_event (
84+ HighestBidderWasOutbid (
85+ listing_id = self .id ,
86+ outbid_bidder_id = previous_winner_id ,
87+ )
88+ )
89+
90+ def retract_bid_of (self , bidder : Bidder ):
8291 """Public method"""
8392 bid = self .get_bid_of (bidder )
8493 self .check_rule (
8594 BidCanBeRetracted (listing_ends_at = self .ends_at , bid_placed_at = bid .placed_at )
8695 )
8796
8897 self ._remove_bid_of (bidder = bidder )
89- return BidRetractedEvent (listing_id = self .id , bidder_id = bidder .id )
98+ self .register_event (
99+ BidWasRetracted (
100+ listing_id = self .id ,
101+ retracting_bidder_id = bidder .id ,
102+ winning_bidder_id = self .highest_bid .bidder .id
103+ if self .highest_bid
104+ else None ,
105+ )
106+ )
90107
91- def cancel (self ) -> DomainEvent :
108+ def cancel (self ):
92109 """
93110 Seller can cancel a listing (end a listing early). Listing must be eligible to cancelled,
94111 depending on time left and if bids have been placed.
@@ -100,14 +117,13 @@ def cancel(self) -> DomainEvent:
100117 )
101118 )
102119 self .ends_at = datetime .utcnow ()
103- return ListingCancelledEvent ( listing_id = self .id )
120+ self . register_event ( ListingWasCancelled ( listing_id = self .id ) )
104121
105122 def end (self ) -> DomainEvent :
106123 """
107124 Ends listing.
108125 """
109126 raise NotImplementedError ()
110- return []
111127
112128 # public queries
113129 def get_bid_of (self , bidder : Bidder ) -> Bid :
@@ -126,7 +142,7 @@ def has_bid_placed_by(self, bidder: Bidder) -> bool:
126142 return True
127143
128144 @property
129- def winning_bid (self ) -> Optional [Bid ]:
145+ def highest_bid (self ) -> Optional [Bid ]:
130146 try :
131147 highest_bid = max (self .bids , key = lambda bid : bid .max_price )
132148 except ValueError :
0 commit comments