File money.py of Package fint-bot

from __future__ import annotations

import math

from dataclasses import dataclass
from tinkoff.invest import MoneyValue, Quotation


@dataclass(init=False, order=True)
class Money:
    units: int
    nano: int
    MOD: int = 10 ** 9

    def __init__(self, value: int | float | Quotation | MoneyValue, nano: int = None):
        if nano is not None:
            # Если передан nano, value должен быть int
            self.units = int(value)
            self.nano = int(nano)
            self._normalize()
        else:
            match value:
                case int() as value:
                    self.units = value
                    self.nano = 0
                case float() as value:
                    # Более точное преобразование float
                    self.units = int(math.trunc(value))
                    fraction = abs(value - self.units)
                    self.nano = int(round(fraction * self.MOD))
                    if value < 0 and self.nano > 0:
                        # Для отрицательных чисел корректируем nano
                        self.nano = -self.nano
                    self._normalize()
                case Quotation() | MoneyValue() as value:
                    self.units = value.units
                    self.nano = value.nano
                    self._normalize()
                case _:
                    raise ValueError(f'{type(value)} is not supported as initial value for Money')

    def _normalize(self):
        """Нормализация nano в диапазон [0, MOD-1] или [-MOD+1, 0] и корректировка units"""
        if self.nano >= self.MOD or self.nano <= -self.MOD:
            self.units += self.nano // self.MOD
            self.nano %= self.MOD
        
        # Если units и nano имеют разные знаки, корректируем
        if self.units == 0 and self.nano < 0:
            # Отрицательное значение с units = 0
            self.units = -1
            self.nano += self.MOD
        elif self.units < 0 and self.nano > 0:
            # units отрицательный, nano положительный
            self.units += 1
            self.nano -= self.MOD
        elif self.units > 0 and self.nano < 0:
            # units положительный, nano отрицательный
            self.units -= 1
            self.nano += self.MOD

    def __float__(self):
        return self.units + self.nano / self.MOD

    def to_float(self):
        return float(self)

    def to_quotation(self):
        return Quotation(self.units, self.nano)

    def to_money_value(self, currency: str):
        return MoneyValue(currency, self.units, self.nano)

    def __add__(self, other: Money) -> Money:
        result = Money(
            self.units + other.units,
            self.nano + other.nano
        )
        result._normalize()
        return result

    def __neg__(self) -> Money:
        return Money(-self.units, -self.nano)

    def __sub__(self, other: Money) -> Money:
        return self + (-other)

    def __mul__(self, other: int) -> Money:
        if not isinstance(other, int):
            raise TypeError("Multiplication is only supported by integers")
        
        result = Money(
            self.units * other,
            self.nano * other
        )
        result._normalize()
        return result

    def __str__(self) -> str:
        return f'<Money units={self.units} nano={self.nano} value={float(self):.2f}>'
openSUSE Build Service is sponsored by