byceps/byceps

View on GitHub
byceps/services/shop/order/dbmodels/line_item.py

Summary

Maintainability
A
0 mins
Test Coverage
A
97%
"""
byceps.services.shop.order.dbmodels.line_item
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

:Copyright: 2014-2024 Jochen Kupperschmidt
:License: Revised BSD (see `LICENSE` file for details)
"""

from datetime import datetime
from decimal import Decimal
from typing import Any, TYPE_CHECKING

from sqlalchemy.orm import Mapped, mapped_column, relationship


if TYPE_CHECKING:
    hybrid_property = property
else:
    from sqlalchemy.ext.hybrid import hybrid_property

from byceps.database import db
from byceps.services.shop.article.dbmodels.article import DbArticle
from byceps.services.shop.article.models import (
    ArticleID,
    ArticleNumber,
    ArticleType,
)
from byceps.services.shop.order.models.number import OrderNumber
from byceps.services.shop.order.models.order import LineItemID

from .order import DbOrder


class DbLineItem(db.Model):
    """A line item that belongs to an order."""

    __tablename__ = 'shop_order_line_items'

    id: Mapped[LineItemID] = mapped_column(db.Uuid, primary_key=True)
    order_number: Mapped[OrderNumber] = mapped_column(
        db.UnicodeText,
        db.ForeignKey('shop_orders.order_number'),
        index=True,
    )
    order: Mapped[DbOrder] = relationship(DbOrder, backref='line_items')
    article_id: Mapped[ArticleID] = mapped_column(
        db.Uuid, db.ForeignKey('shop_articles.id'), index=True
    )
    article_number: Mapped[ArticleNumber] = mapped_column(
        db.UnicodeText,
        db.ForeignKey('shop_articles.item_number'),
        index=True,
    )
    article: Mapped[DbArticle] = relationship(
        DbArticle, foreign_keys=[article_id]
    )
    _article_type: Mapped[str] = mapped_column('article_type', db.UnicodeText)
    name: Mapped[str] = mapped_column(db.UnicodeText)
    unit_price: Mapped[Decimal] = mapped_column(db.Numeric(6, 2))
    tax_rate: Mapped[Decimal] = mapped_column(db.Numeric(3, 3))
    quantity: Mapped[int] = mapped_column(db.CheckConstraint('quantity > 0'))
    line_amount: Mapped[Decimal] = mapped_column(db.Numeric(7, 2))
    processing_required: Mapped[bool]
    processing_result: Mapped[Any | None] = mapped_column(db.JSONB)
    processed_at: Mapped[datetime | None]

    def __init__(
        self,
        line_item_id: LineItemID,
        order: DbOrder,
        article_id: ArticleID,
        article_number: ArticleNumber,
        article_type: ArticleType,
        name: str,
        unit_price: Decimal,
        tax_rate: Decimal,
        quantity: int,
        line_amount: Decimal,
        processing_required: bool,
    ) -> None:
        self.id = line_item_id
        # Require order instance rather than order number as argument
        # because line items are created together with the order – and
        # until the order is created, there is no order number assigned.
        self.order = order
        self.article_id = article_id
        self.article_number = article_number
        self._article_type = article_type.name
        self.name = name
        self.unit_price = unit_price
        self.tax_rate = tax_rate
        self.quantity = quantity
        self.line_amount = line_amount
        self.processing_required = processing_required

    @hybrid_property
    def article_type(self) -> ArticleType:
        return ArticleType[self._article_type]