diff --git a/share/models.py b/share/models.py index aa2da77..ceeb57c 100644 --- a/share/models.py +++ b/share/models.py @@ -1,6 +1,7 @@ import logging import typing -from datetime import date +from datetime import date, timedelta +from functools import singledispatchmethod from django.contrib.auth import get_user_model from django.db import OperationalError, models, transaction @@ -122,6 +123,7 @@ class Donation(models.Model): default=states.PendingApprovalState.state_id(), choices=states.DonationStateEnum.choices, ) + estimated_delivery_days = models.PositiveSmallIntegerField() excepted_delivery_date = models.DateField(null=True) events = models.JSONField(default=list) created_by = models.ForeignKey(User, on_delete=models.CASCADE) @@ -132,13 +134,26 @@ class Donation(models.Model): def required_item_name(self) -> str: return self.required_item.name + @singledispatchmethod + def perform_action(self, arg): + raise NotImplementedError("Unknown action") + + @perform_action.register + def _approval_action(self, action: states.ApprovalAction): + self.excepted_delivery_date = date.today() + timedelta( + days=self.estimated_delivery_days + ) + def calc_state(self) -> states.State: current_state = states.get_state(self.state) for raw_event in self.events: e = states.get_event(raw_event) if e.timestamp < self.modified_at.timestamp(): continue - current_state = current_state.apply(e) + current_state, action = current_state.apply(e) + if action is not None: + self.perform_action(action) + return current_state def set_event(self, user, raw_event: typing.Dict): diff --git a/share/states.py b/share/states.py index f871033..284251c 100644 --- a/share/states.py +++ b/share/states.py @@ -6,6 +6,18 @@ from pydantic import BaseModel, Field, validator +class Action: + pass + + +class ApprovalAction(Action): + pass + + +class CancelAction(Action): + pass + + class Event(BaseModel): name: str = "" timestamp: float = Field(default_factory=time.time) @@ -38,7 +50,7 @@ class DonationCancelledEvent(Event): class State(ABC): @abstractmethod - def apply(self, event: Event) -> "State": + def apply(self, event: Event) -> typing.Tuple["State", typing.Optional[Action]]: raise NotImplementedError() @classmethod @@ -47,52 +59,52 @@ def state_id(cls) -> str: class InvalidState(State): - def apply(self, event: Event) -> State: - return InvalidState() + def apply(self, event: Event) -> typing.Tuple["State", typing.Optional[Action]]: + return InvalidState(), None class CancelledState(State): - def apply(self, event: Event) -> State: - return CancelledState() + def apply(self, event: Event) -> typing.Tuple["State", typing.Optional[Action]]: + return CancelledState(), CancelAction() class CollectingState(State): - def apply(self, event: Event) -> State: - return CancelledState() + def apply(self, event: Event) -> typing.Tuple["State", typing.Optional[Action]]: + return CancelledState(), None class PendingApprovalState(State): - def apply(self, event: Event) -> State: + def apply(self, event: Event) -> typing.Tuple["State", typing.Optional[Action]]: if event.name == DonationApprovedEvent.event_id(): - return PendingDispatchState() + return PendingDispatchState(), ApprovalAction() elif event.name == DonationCancelledEvent.event_id(): - return CancelledState() + return CancelledState(), None else: - return InvalidState() + return InvalidState(), None class PendingDispatchState(State): - def apply(self, event: Event) -> State: + def apply(self, event: Event) -> typing.Tuple["State", typing.Optional[Action]]: if event.name == DonationDispatchedEvent.event_id(): - return DoneState() + return DoneState(), None # return PendingDeliveryState() elif event.name == DonationCancelledEvent.event_id(): - return CancelledState() + return CancelledState(), None else: - return InvalidState() + return InvalidState(), None class PendingDeliveryState(State): - def apply(self, event: Event) -> State: + def apply(self, event: Event) -> typing.Tuple["State", typing.Optional[Action]]: if event.name == DonationDeliveredEvent.event_id(): - return DoneState() + return DoneState(), None else: - return InvalidState() + return InvalidState(), None class DoneState(State): - def apply(self, event: Event) -> State: - return InvalidState() + def apply(self, event: Event) -> typing.Tuple["State", typing.Optional[Action]]: + return InvalidState(), None class DonationStateMachine: