Source code for pay.models.invoice_item

from __future__ import annotations

from decimal import Decimal
from onegov.core.orm import Base
from onegov.core.orm.mixins import TimestampMixin
from onegov.core.orm.types import UUID
from onegov.pay.models.payable import PayableManyTimes
from onegov.pay.constants import PRECISION, SCALE
from sqlalchemy import Boolean
from sqlalchemy import Column
from sqlalchemy import Date
from sqlalchemy import ForeignKey
from sqlalchemy import Numeric
from sqlalchemy import Text
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import relationship
from sqlalchemy.orm import validates
from uuid import uuid4


from typing import TYPE_CHECKING
if TYPE_CHECKING:
    import uuid
    from datetime import date
    from onegov.pay.models import Invoice
    from sqlalchemy.sql import ColumnElement


[docs] class InvoiceItem(Base, TimestampMixin, PayableManyTimes): """ An item in an invoice. """
[docs] __tablename__ = 'invoice_items'
#: the polymorphic type of the invoice item
[docs] type: Column[str] = Column( Text, nullable=False, default=lambda: 'generic' )
[docs] __mapper_args__ = { 'polymorphic_on': type, 'polymorphic_identity': 'generic' }
#: the public id of the invoice item
[docs] id: Column[uuid.UUID] = Column( UUID, # type:ignore[arg-type] primary_key=True, default=uuid4 )
#: the invoice this item belongs to # FIXME: Shouldn't this be nullable=False?
[docs] invoice_id: Column[uuid.UUID | None] = Column( UUID, # type:ignore[arg-type] ForeignKey('invoices.id') )
[docs] invoice: relationship[Invoice] = relationship( 'Invoice', back_populates='items' )
#: the item group (all items with the same text are visually grouped)
[docs] group: Column[str] = Column(Text, nullable=False)
#: a secondary group who is not necessarily grouped visually
[docs] family: Column[str | None] = Column(Text, nullable=True)
#: the item text
[docs] text: Column[str] = Column(Text, nullable=False)
#: true if paid
[docs] paid: Column[bool] = Column(Boolean, nullable=False, default=False)
#: the payment date
[docs] payment_date: Column[date | None] = Column(Date, nullable=True)
#: the transaction id if paid through a bank or online transaction
[docs] tid: Column[str | None] = Column(Text, nullable=True)
#: the source of the transaction id, e.g. stripe, xml
[docs] source: Column[str | None] = Column(Text, nullable=True)
#: the unit to pay.. # FIXME: I don't think this should be nullable
[docs] unit: Column[Decimal] = Column( # type: ignore[assignment] Numeric(precision=PRECISION, scale=SCALE), nullable=True )
#: ..multiplied by the quantity.. # FIXME: and neither should this be
[docs] quantity: Column[Decimal] = Column( # type: ignore[assignment] Numeric(precision=PRECISION, scale=SCALE), nullable=True )
if TYPE_CHECKING:
[docs] amount: Column[Decimal]
#: ..together form the amount @hybrid_property # type: ignore[no-redef] def amount(self) -> Decimal | None: if self.unit is None or self.quantity is None: return None return round( round(self.unit, SCALE) * round(self.quantity, SCALE), SCALE ) @amount.expression # type:ignore[no-redef] def amount(cls) -> ColumnElement[Decimal | None]: return cls.unit * cls.quantity #: the VAT factor (`net_amount` times the factor yields `amount`)
[docs] vat_factor: Column[Decimal | None] = Column( Numeric(precision=5, scale=4), nullable=True )
@property
[docs] def vat_rate(self) -> Decimal | None: """ A convenience attribute to access/set the VAT rate in % """ if self.vat_factor is None: return None return round((self.vat_factor - Decimal('1')) * Decimal('100'), 2)
@vat_rate.setter def vat_rate(self, value: Decimal | None) -> None: if value is None: self.vat_factor = None else: self.vat_factor = round( (value + Decimal('100')) / Decimal('100'), 4 ) @property
[docs] def vat(self) -> Decimal: return self.amount - self.net_amount
@property
[docs] def net_amount(self) -> Decimal: if self.vat_factor is None: return self.amount return round(self.amount / self.vat_factor, SCALE)
@validates('source')
[docs] def validate_source(self, key: str, value: str | None) -> str | None: assert value in ( None, 'xml', 'datatrans', 'stripe_connect', 'worldline_saferpay' ) return value