177 lines
6.2 KiB
Python
177 lines
6.2 KiB
Python
"""Database models for persisted pricing configuration settings."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
from typing import TYPE_CHECKING
|
|
|
|
from sqlalchemy import (
|
|
JSON,
|
|
DateTime,
|
|
ForeignKey,
|
|
Integer,
|
|
Numeric,
|
|
String,
|
|
Text,
|
|
UniqueConstraint,
|
|
)
|
|
from sqlalchemy.orm import Mapped, mapped_column, relationship, validates
|
|
from sqlalchemy.sql import func
|
|
|
|
from config.database import Base
|
|
from services.currency import normalise_currency
|
|
|
|
if TYPE_CHECKING: # pragma: no cover
|
|
from .project import Project
|
|
|
|
|
|
class PricingSettings(Base):
|
|
"""Persisted pricing defaults applied to scenario evaluations."""
|
|
|
|
__tablename__ = "pricing_settings"
|
|
|
|
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
|
name: Mapped[str] = mapped_column(String(128), nullable=False, unique=True)
|
|
slug: Mapped[str] = mapped_column(String(64), nullable=False, unique=True)
|
|
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
default_currency: Mapped[str | None] = mapped_column(
|
|
String(3), nullable=True)
|
|
default_payable_pct: Mapped[float] = mapped_column(
|
|
Numeric(5, 2), nullable=False, default=100.0
|
|
)
|
|
moisture_threshold_pct: Mapped[float] = mapped_column(
|
|
Numeric(5, 2), nullable=False, default=8.0
|
|
)
|
|
moisture_penalty_per_pct: Mapped[float] = mapped_column(
|
|
Numeric(14, 4), nullable=False, default=0.0
|
|
)
|
|
metadata_payload: Mapped[dict | None] = mapped_column(
|
|
"metadata", JSON, nullable=True
|
|
)
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), nullable=False, server_default=func.now()
|
|
)
|
|
updated_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now()
|
|
)
|
|
|
|
metal_overrides: Mapped[list["PricingMetalSettings"]] = relationship(
|
|
"PricingMetalSettings",
|
|
back_populates="pricing_settings",
|
|
cascade="all, delete-orphan",
|
|
passive_deletes=True,
|
|
)
|
|
impurity_overrides: Mapped[list["PricingImpuritySettings"]] = relationship(
|
|
"PricingImpuritySettings",
|
|
back_populates="pricing_settings",
|
|
cascade="all, delete-orphan",
|
|
passive_deletes=True,
|
|
)
|
|
projects: Mapped[list["Project"]] = relationship(
|
|
"Project",
|
|
back_populates="pricing_settings",
|
|
cascade="all",
|
|
)
|
|
|
|
@validates("slug")
|
|
def _normalise_slug(self, key: str, value: str) -> str:
|
|
return value.strip().lower()
|
|
|
|
@validates("default_currency")
|
|
def _validate_currency(self, key: str, value: str | None) -> str | None:
|
|
return normalise_currency(value)
|
|
|
|
def __repr__(self) -> str: # pragma: no cover
|
|
return f"PricingSettings(id={self.id!r}, slug={self.slug!r})"
|
|
|
|
|
|
class PricingMetalSettings(Base):
|
|
"""Contract-specific overrides for a particular metal."""
|
|
|
|
__tablename__ = "pricing_metal_settings"
|
|
__table_args__ = (
|
|
UniqueConstraint(
|
|
"pricing_settings_id", "metal_code", name="uq_pricing_metal_settings_code"
|
|
),
|
|
)
|
|
|
|
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
|
pricing_settings_id: Mapped[int] = mapped_column(
|
|
ForeignKey("pricing_settings.id", ondelete="CASCADE"), nullable=False, index=True
|
|
)
|
|
metal_code: Mapped[str] = mapped_column(String(32), nullable=False)
|
|
payable_pct: Mapped[float | None] = mapped_column(
|
|
Numeric(5, 2), nullable=True)
|
|
moisture_threshold_pct: Mapped[float | None] = mapped_column(
|
|
Numeric(5, 2), nullable=True)
|
|
moisture_penalty_per_pct: Mapped[float | None] = mapped_column(
|
|
Numeric(14, 4), nullable=True
|
|
)
|
|
data: Mapped[dict | None] = mapped_column(JSON, nullable=True)
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), nullable=False, server_default=func.now()
|
|
)
|
|
updated_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now()
|
|
)
|
|
|
|
pricing_settings: Mapped["PricingSettings"] = relationship(
|
|
"PricingSettings", back_populates="metal_overrides"
|
|
)
|
|
|
|
@validates("metal_code")
|
|
def _normalise_metal_code(self, key: str, value: str) -> str:
|
|
return value.strip().lower()
|
|
|
|
def __repr__(self) -> str: # pragma: no cover
|
|
return (
|
|
"PricingMetalSettings(" # noqa: ISC001
|
|
f"id={self.id!r}, pricing_settings_id={self.pricing_settings_id!r}, "
|
|
f"metal_code={self.metal_code!r})"
|
|
)
|
|
|
|
|
|
class PricingImpuritySettings(Base):
|
|
"""Impurity penalty thresholds associated with pricing settings."""
|
|
|
|
__tablename__ = "pricing_impurity_settings"
|
|
__table_args__ = (
|
|
UniqueConstraint(
|
|
"pricing_settings_id",
|
|
"impurity_code",
|
|
name="uq_pricing_impurity_settings_code",
|
|
),
|
|
)
|
|
|
|
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
|
pricing_settings_id: Mapped[int] = mapped_column(
|
|
ForeignKey("pricing_settings.id", ondelete="CASCADE"), nullable=False, index=True
|
|
)
|
|
impurity_code: Mapped[str] = mapped_column(String(32), nullable=False)
|
|
threshold_ppm: Mapped[float] = mapped_column(
|
|
Numeric(14, 4), nullable=False, default=0.0)
|
|
penalty_per_ppm: Mapped[float] = mapped_column(
|
|
Numeric(14, 4), nullable=False, default=0.0)
|
|
notes: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), nullable=False, server_default=func.now()
|
|
)
|
|
updated_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now()
|
|
)
|
|
|
|
pricing_settings: Mapped["PricingSettings"] = relationship(
|
|
"PricingSettings", back_populates="impurity_overrides"
|
|
)
|
|
|
|
@validates("impurity_code")
|
|
def _normalise_impurity_code(self, key: str, value: str) -> str:
|
|
return value.strip().upper()
|
|
|
|
def __repr__(self) -> str: # pragma: no cover
|
|
return (
|
|
"PricingImpuritySettings(" # noqa: ISC001
|
|
f"id={self.id!r}, pricing_settings_id={self.pricing_settings_id!r}, "
|
|
f"impurity_code={self.impurity_code!r})"
|
|
)
|