dodil/erp-brick-contracts

src/brick_contracts/domain/events.py

"""Domain events — what happened to a contract, in the past tense (modules/contracts domain_events).

They are WRITTEN as rows the aggregate already owns (ct_signature_event for the signing story, the state and
approval rows for the rest) and REPORTED by contracts.status / show_contract; the application layer also uses
them to run what a commit causes in a later unit of work (a signed renewal supersedes its original). An event is a
value: built once, never changed.
"""
from dataclasses import asdict, dataclass
from typing import Optional


@dataclass(frozen=True)
class DomainEvent:
    contract_id: str
    at: str                               # ISO 8601 UTC

    @property
    def name(self) -> str:
        return type(self).__name__

    def as_json(self) -> dict:
        return {"event": self.name, **asdict(self)}


@dataclass(frozen=True)
class Drafted(DomainEvent):
    """A contract was drafted from an approved template version (missing[] and deviations[] computed)."""
    template: str = ""
    missing: tuple = ()
    deviations: int = 0


@dataclass(frozen=True)
class Approved(DomainEvent):
    """An approval was granted for one draft fingerprint."""
    fingerprint: str = ""
    by: str = ""


@dataclass(frozen=True)
class Sent(DomainEvent):
    """Version N was frozen and signing links issued."""
    version: int = 0
    sha256: str = ""


@dataclass(frozen=True)
class Signed(DomainEvent):
    """Every signer of version N signed; the executed copy exists (or a copy signed elsewhere was filed)."""
    version: Optional[int] = None
    executed_sha256: str = ""
    elsewhere: bool = False


@dataclass(frozen=True)
class Declined(DomainEvent):
    """A signer declined, with a reason, or asked for changes."""
    signer_no: int = 0
    changes_requested: bool = False


@dataclass(frozen=True)
class Expired(DomainEvent):
    """OUT_FOR_SIGNATURE passed its link lifetime unsigned (EXPIRED_UNSIGNED)."""


@dataclass(frozen=True)
class Amended(DomainEvent):
    """An amendment, renewal or termination was signed and the original superseded or terminated."""
    original: str = ""
    kind: str = ""


__all__ = ["DomainEvent", "Drafted", "Approved", "Sent", "Signed", "Declined", "Expired", "Amended"]