Source code for rechu.models.receipt
"""
Models for receipt data.
"""
import datetime
from typing import Optional
from sqlalchemy import Column, ForeignKey, String, Table
from sqlalchemy.orm import MappedColumn, Relationship, mapped_column, \
relationship
from .base import Base, Price, Quantity, Unit
from .product import Product
[docs]
class Receipt(Base): # pylint: disable=too-few-public-methods
"""
Receipt model for a receipt from a certain date at a shop with products
and possibly discounts.
"""
__tablename__ = "receipt"
filename: MappedColumn[str] = mapped_column(String(255), primary_key=True)
updated: MappedColumn[datetime.datetime]
date: MappedColumn[datetime.date]
shop: MappedColumn[str] = mapped_column(String(32)) # shop.key
products: Relationship[list["ProductItem"]] = \
relationship(back_populates="receipt", cascade="all, delete-orphan",
passive_deletes=True, order_by="ProductItem.position")
discounts: Relationship[list["Discount"]] = \
relationship(cascade="all, delete-orphan", passive_deletes=True,
order_by="Discount.position")
@property
def total_price(self) -> Price:
"""
Retrieve the total cost of the receipt after discounts.
"""
total = sum(product.price for product in self.products) + \
sum(discount.price_decrease for discount in self.discounts)
return Price(total)
def __repr__(self) -> str:
return f"Receipt(date={self.date.isoformat()!r}, shop={self.shop!r})"
# Association table for products involved in discounts
DiscountItems = Table("receipt_discount_products", Base.metadata,
Column("discount_id", ForeignKey('receipt_discount.id',
ondelete='CASCADE'),
primary_key=True),
Column("product_id", ForeignKey('receipt_product.id',
ondelete='CASCADE'),
primary_key=True))
[docs]
class ProductItem(Base): # pylint: disable=too-few-public-methods
"""
Product model for a product item mentioned on a receipt.
"""
__tablename__ = "receipt_product"
id: MappedColumn[int] = mapped_column(primary_key=True)
receipt_key: MappedColumn[str] = \
mapped_column(ForeignKey('receipt.filename', ondelete='CASCADE'))
receipt: Relationship[Receipt] = relationship(back_populates="products")
quantity: MappedColumn[Quantity]
label: MappedColumn[str]
price: MappedColumn[Price]
discount_indicator: MappedColumn[Optional[str]]
discounts: Relationship[list["Discount"]] = \
relationship(secondary=DiscountItems, back_populates="items",
passive_deletes=True)
product_id: MappedColumn[Optional[int]] = \
mapped_column(ForeignKey('product.id', ondelete='SET NULL'))
product: Relationship[Optional[Product]] = relationship()
position: MappedColumn[int]
# Extracted fields from quantity
amount: MappedColumn[float]
unit: MappedColumn[Optional[Unit]]
def __repr__(self) -> str:
return (f"ProductItem(receipt={self.receipt_key!r}, "
f"quantity='{self.quantity!s}', label={self.label!r}, "
f"price={self.price!s}, "
f"discount_indicator={self.discount_indicator!r}, "
f"product={self.product_id!r})")
[docs]
class Discount(Base): # pylint: disable=too-few-public-methods
"""
Discount model for a discount action mentioned on a receipt.
"""
__tablename__ = "receipt_discount"
id: MappedColumn[int] = mapped_column(primary_key=True)
receipt_key: MappedColumn[str] = \
mapped_column(ForeignKey('receipt.filename', ondelete='CASCADE'))
receipt: Relationship[Receipt] = relationship(back_populates="discounts")
label: MappedColumn[str]
price_decrease: MappedColumn[Price]
items: Relationship[list[ProductItem]] = \
relationship(secondary=DiscountItems, back_populates="discounts",
passive_deletes=True)
position: MappedColumn[int]
def __repr__(self) -> str:
return (f"Discount(receipt={self.receipt_key!r}, label={self.label!r}, "
f"price_decrease={self.price_decrease!s}, "
f"items={[item.label for item in self.items]!r})")