dodil/erp-brick-tax

DODIL ERP brick: tax

src/brick_tax/domain/errors.py

"""The refusals a rule makes. The use cases turn them into a 409 with these words, through both doors.

A refused transition returns its unmet gates (shared.yaml conventions.readiness): `GateUnmet` carries them as
{gate, met, reason, fix}, so a screen, an agent and a selling brick all read the same list and never re-derive a rule.
"""
import json
from dataclasses import dataclass


@dataclass(frozen=True)
class Gate:
    """One precondition, answered: a stable id, whether it is met, why not (in the customer's words), and the fix."""
    gate: str
    met: bool
    reason: str
    fix: str

    def as_dict(self) -> dict:
        return {"gate": self.gate, "met": self.met, "reason": self.reason, "fix": self.fix}


class RuleBroken(Exception):
    """A business rule said no. The message is written for the person who tried."""


class GateUnmet(RuleBroken):
    """A transition refused because gates are unmet. `gates` are only the unmet ones, each with met=False."""

    def __init__(self, gates):
        self.gates = list(gates)
        super().__init__(self.gates[0].reason if self.gates else "refused")

    def text(self) -> str:
        """The refusal as one string both doors can carry: the first reason, then every unmet gate as JSON."""
        return f"{self.gates[0].reason} — unmet gates: {json.dumps([g.as_dict() for g in self.gates])}"


def unmet(gate: str, reason: str, fix: str) -> GateUnmet:
    return GateUnmet([Gate(gate, False, reason, fix)])