dodil/erp-brick-pos

DODIL ERP brick: point of sale

src/brick_pos/application/inputs.py

"""The bounded inputs of this brick's use cases. An amount travels as a string and is checked INSIDE the use case (after the
permission), so a caller sees 422 {gate: bounds} and the reason, never an engine 500; ids and words are bounded here."""
from typing import Annotated, Any, Optional

from erp_platform.types import text
from pydantic import BaseModel, ConfigDict, Field, WithJsonSchema

Code = text(64, min_length=1)
Reason = text(500)
Word = text(200)
# Typed `Any` on purpose: pydantic would quietly make `true` the number 1 and a float a rounded guess; the use case sees what the
# caller sent and answers 422 {gate: bounds} for anything but an exact decimal string (domain/money.py).
Amt = Annotated[Any, WithJsonSchema({"type": "string", "pattern": r"^[0-9]{1,16}(\.[0-9]{1,3})?$",
                                     "description": "an exact decimal amount, as a string (\"10.00\")"})]
Setting = Annotated[Any, WithJsonSchema({"description": "true or false, an exact decimal string (\"5.00\"), a time (\"04:00\"), "
                                                         "a time zone (\"Europe/London\") or a word, as the setting's kind says"})]


class _In(BaseModel):
    model_config = ConfigDict(extra="forbid")


class LineIn(_In):
    """One product rung up. The price and the VAT come from product and tax, never from the till."""
    product_id: Code
    quantity: Amt
    unit_code: Optional[Code] = None
    modifiers: Optional[list[Code]] = None       # modifier products (MOD-OAT) that change the recipe the sale consumes, never the price


class RefundLineIn(_In):
    """One line of the original sale given back: its number on the sale and the quantity returned. `restock`: the item goes back on the
    shelf (only where its category allows it; omitted, the category decides)."""
    line_no: Annotated[int, Field(ge=1, le=1000)]
    quantity: Amt
    restock: Optional[bool] = None


class VoucherSaleIn(_In):
    """A gift voucher sold on this receipt: its code and its face value. It is live once the receipt is final."""
    voucher_code: Code
    amount: Amt


class RecipeLineIn(_In):
    """One component of a recipe: a product in the unit it is issued in (0.018 KGM of beans, 1 C62 of cups)."""
    component_id: Code
    quantity: Amt
    unit_code: Code


class RecipeModifierIn(_In):
    """A modifier's component: it ADDS the component (replaces left out) or takes the place of the base component it replaces."""
    modifier_id: Code
    component_id: Code
    quantity: Amt
    unit_code: Code
    replaces: Optional[Code] = None