from __future__ import annotations from dataclasses import dataclass, field, replace from decimal import Decimal from typing import Self from domain.framework.entity import Entity from domain.marketplace.classified_ad_id import ClassifiedAdId from domain.marketplace.classified_ad_state import ClassifiedAdState from domain.marketplace.classified_ad_text import ClassifiedAdText from domain.marketplace.classified_ad_title import ClassifiedAdTitle from domain.marketplace.domain_event import ( ClassifiedAdCreated, ClassifiedAdPriceUpdated, ClassifiedAdSentForReview, ClassifiedAdTextUpdated, ClassifiedAdTitleChanged, DomainEvent, ) from domain.marketplace.invalid_entity_state_exception import ( InvalidEntityStateException, ) from domain.marketplace.price import Price from domain.marketplace.user_id import UserId @dataclass(frozen=True) class ClassifiedAd(Entity): id: ClassifiedAdId _ownerId: UserId title: ClassifiedAdTitle = field( default_factory=lambda: ClassifiedAdTitle(value="") ) text: ClassifiedAdText = field(default_factory=lambda: ClassifiedAdText(value="")) price: Price = field(default_factory=lambda: Price(amount=Decimal("0.00"))) state: ClassifiedAdState = ClassifiedAdState.Inactive _changes: tuple[DomainEvent, ...] = () def __post_init__(self): if not self._changes: object.__setattr__( self, "_changes", (ClassifiedAdCreated(id=self.id.value, owner_id=self._ownerId.value),), ) def _ensure_valid_state(self): errors = [] if not self.title.value: errors.append("title") if not self.text.value: errors.append("text") if self.price.amount <= 0: errors.append("price") if errors: raise InvalidEntityStateException( f"Cannot publish classified ad with missing or invalid: {', '.join(errors)}" ) def when(self, event: DomainEvent) -> Self: match event: case ClassifiedAdTitleChanged(): return replace(self, title=ClassifiedAdTitle(value=event.title)) case ClassifiedAdTextUpdated(): return replace(self, text=ClassifiedAdText(value=event.ad_text)) case ClassifiedAdPriceUpdated(): return replace(self, price=Price(amount=event.price)) case ClassifiedAdSentForReview(): return replace(self, state=ClassifiedAdState.PendingReview) return self def request_to_publish(self): return self.apply(ClassifiedAdSentForReview(id=self.id.value)) def set_title(self, title: ClassifiedAdTitle) -> ClassifiedAd: return self.apply(ClassifiedAdTitleChanged(id=self.id.value, title=title.value)) def update_text(self, text: ClassifiedAdText) -> ClassifiedAd: return self.apply(ClassifiedAdTextUpdated(id=self.id.value, ad_text=text.value)) def update_price(self, price: Price) -> ClassifiedAd: return self.apply( ClassifiedAdPriceUpdated(id=self.id.value, price=price.amount) )