barker/barker/barker/models/inventory.py

132 lines
4.5 KiB
Python

import uuid
from decimal import Decimal
from typing import TYPE_CHECKING, List
from barker.models.product import Product
from sqlalchemy import (
Boolean,
ColumnElement,
ForeignKey,
Integer,
Numeric,
UniqueConstraint,
case,
func,
text,
type_coerce,
)
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import Mapped, mapped_column, relationship
from ..db.base_class import reg
if TYPE_CHECKING:
from .inventory_modifier import InventoryModifier
from .kot import Kot
from .tax import Tax
@reg.mapped_as_dataclass(unsafe_hash=True)
class Inventory:
__tablename__ = "inventories"
__table_args__ = (UniqueConstraint("kot_id", "product_id", "is_happy_hour", "price"),)
id: Mapped[uuid.UUID] = mapped_column(
"id", UUID(as_uuid=True), primary_key=True, server_default=text("gen_random_uuid()"), insert_default=uuid.uuid4
)
kot_id: Mapped[uuid.UUID] = mapped_column(
"kot_id", UUID(as_uuid=True), ForeignKey("kots.id"), nullable=False, index=True
)
product_id: Mapped[uuid.UUID] = mapped_column(
"product_id", UUID(as_uuid=True), ForeignKey("products.id"), nullable=False
)
quantity: Mapped[Decimal] = mapped_column("quantity", Numeric(precision=15, scale=2), nullable=False)
price: Mapped[Decimal] = mapped_column("price", Numeric(precision=15, scale=2), nullable=False)
is_happy_hour: Mapped[bool] = mapped_column("is_happy_hour", Boolean, nullable=False)
tax_rate: Mapped[Decimal] = mapped_column("tax_rate", Numeric(precision=15, scale=5), nullable=False)
tax_id: Mapped[uuid.UUID] = mapped_column("tax_id", UUID(as_uuid=True), ForeignKey("taxes.id"), nullable=False)
discount: Mapped[Decimal] = mapped_column("discount", Numeric(precision=15, scale=5), nullable=False)
sort_order: Mapped[int] = mapped_column("sort_order", Integer, nullable=False)
kot: Mapped["Kot"] = relationship(back_populates="inventories")
tax: Mapped["Tax"] = relationship(back_populates="inventories")
product: Mapped["Product"] = relationship(back_populates="inventories")
modifiers: Mapped[List["InventoryModifier"]] = relationship(back_populates="inventory")
def __init__(
self,
kot_id=None,
product_id=None,
quantity=None,
price=None,
discount=None,
is_hh=None,
tax_id=None,
tax_rate=None,
sort_order=None,
product=None,
tax=None,
):
self.kot_id = kot_id
if product_id is not None:
self.product_id = product_id
self.quantity = quantity
self.price = price
self.discount = discount
self.is_happy_hour = is_hh
if tax_id is not None:
self.tax_id = tax_id
self.tax_rate = tax_rate
self.sort_order = sort_order
if product is not None:
self.product = product
if tax is not None:
self.tax = tax
@hybrid_property
def effective_price(self) -> Decimal:
return 0 if self.is_happy_hour else self.price
@effective_price.inplace.expression
@classmethod
def _effective_price_expression(cls) -> ColumnElement[Decimal]:
return type_coerce(
case((cls.is_happy_hour == True, 0), else_=cls.price), Numeric(precision=15, scale=2) # noqa: E712
).label("effective_price")
@hybrid_property
def net(self) -> Decimal:
return self.effective_price * self.quantity * (1 - self.discount)
@net.inplace.expression
@classmethod
def _net_expression(cls) -> ColumnElement[Decimal]:
return type_coerce(
cls.effective_price * cls.quantity * (1 - cls.discount), Numeric(precision=15, scale=2)
).label("net")
@hybrid_property
def tax_amount(self) -> Decimal:
return self.net * self.tax_rate
@tax_amount.inplace.expression
@classmethod
def _tax_amount_expression(cls) -> ColumnElement[Decimal]:
return type_coerce(cls.net * cls.tax_rate, Numeric(precision=15, scale=2)).label("tax_amount")
@hybrid_property
def amount(self) -> Decimal:
return round(Decimal(self.net * (1 + self.tax_rate)), 2)
@amount.inplace.expression
@classmethod
def _amount_expression(cls) -> ColumnElement[Decimal]:
# func.round(cls.net * (1 + cls.tax_rate), 2)
return type_coerce(
func.round(cls.net * (1 + cls.tax_rate) / 0.02, 0) * 0.02, Numeric(precision=15, scale=2)
).label("amount")