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(title="")) text: ClassifiedAdText = field(default_factory=lambda: ClassifiedAdText(text="")) 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.title: errors.append("title") if not self.text.text: 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(title=event.title)) case ClassifiedAdTextUpdated(): return replace(self, text=ClassifiedAdText(text=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.title)) def update_text(self, text: ClassifiedAdText) -> ClassifiedAd: return self.apply(ClassifiedAdTextUpdated(id=self.id.value, ad_text=text.text)) def update_price(self, price: Price) -> ClassifiedAd: return self.apply(ClassifiedAdPriceUpdated(id=self.id.value, price=price.amount))