123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302 |
- # -*- coding: utf-8 -*-
- #!/usr/bin/env python
- from decimal import Decimal, ROUND_DOWN
- from bson.decimal128 import Decimal128
- from apilib.numerics import force_decimal, quantize, UnitBase
- class MoneyComparisonError(TypeError):
- # This exception was needed often enough to merit its own
- # Exception class.
- def __init__(self, other):
- assert not isinstance(other, Money)
- self.other = other
- def __str__(self):
- # Note: at least w/ Python 2.x, use __str__, not __unicode__.
- return "Cannot compare instances of Money and %s" \
- % self.other.__class__.__name__
- class Money(UnitBase):
- """所有与金钱相来往的操作都走这个类,不暴露具体的运算,如取精度等"""
- # 以后或许会有多币种支持
- __currency__ = None
- __places__ = '0.01'
- def __init__(self, amount):
- if isinstance(amount, Money):
- self._amount = quantize(amount.amount, places=self.__places__)
- else:
- self._amount = quantize(Decimal(str(amount).strip()), places=self.__places__)
- @property
- def amount(self):
- return self._amount
- @property
- def mongo_amount(self):
- return Decimal128(self.amount)
- @property
- def precision(self):
- """精度等级 小数的位数"""
- return len(self.__places__.split(".")[1])
- def __repr__(self): return '<{currency} {amount:.2f}> '.format(currency=self.__currency__, amount=self._amount)
- def __add__(self, other):
- if isinstance(other, Money):
- return self.__class__(self._amount + other.amount)
- else:
- return self.__class__(other) + self
- def __sub__(self, other):
- if isinstance(other, Money):
- return self.__class__(self._amount - other.amount)
- else:
- return self - self.__class__(other)
- def __mul__(self, other):
- if isinstance(other, Money):
- raise TypeError('Cannot multiply two Money instances.')
- elif isinstance(other, Ratio):
- return self.__class__(amount=(self.amount * other.amount))
- else:
- return self.__class__(amount=(self.amount * force_decimal(other)))
- def __abs__(self):
- return self.__class__(amount=abs(self.amount))
- def __neg__(self):
- return self.__class__(amount=-self.amount)
- def __eq__(self, other):
- return (isinstance(other, Money) and
- (self.amount == other.amount))
- def __ne__(self, other):
- result = self.__eq__(other)
- return not result
- def __lt__(self, other):
- if not isinstance(other, Money):
- raise MoneyComparisonError(other)
- return self.amount < other.amount
- def __gt__(self, other):
- if not isinstance(other, Money):
- raise MoneyComparisonError(other)
- return self.amount > other.amount
- def __le__(self, other):
- if not isinstance(other, Money):
- raise MoneyComparisonError(other)
- return self.amount <= other.amount
- def __ge__(self, other):
- if not isinstance(other, Money):
- raise MoneyComparisonError(other)
- return self.amount >= other.amount
- def __str__(self):
- return str(self._amount)
- def __int__(self):
- return int(self._amount)
- def __float__(self):
- return float(self._amount)
- class RMB(Money):
- """
- > RMB(1) + RMB(2) == RMB(3)
- """
- __currency__ = 'RMB'
- def __str__(self):
- return u'{amount}'.format(amount=self._amount)
- @classmethod
- def fen_to_yuan(cls, fen):
- return cls(fen) * Decimal('0.01')
- @classmethod
- def yuan_to_fen(cls, yuan):
- return int((yuan * 100))
- class VirtualCoin(Money):
- __currency__ = 'VirtualCoin'
- def __str__(self):
- return u'{amount}'.format(amount=self._amount)
- @property
- def as_impulses(self):
- return int(self.amount)
- def sum_rmb(list_):
- return sum(list_, RMB(0))
- def sum_virtual_coin(list_):
- return sum(list_, VirtualCoin(0))
- def sum_accuracy_rmb(list_):
- return sum(list_, AccuracyRMB(0))
- class Ratio(object):
- def __init__(self, amount):
- if isinstance(amount, Ratio):
- self._amount = amount.amount
- else:
- amount = Decimal(str(amount).strip()) # type: Decimal
- self._amount = amount
- @property
- def amount(self):
- return self._amount
- @property
- def mongo_amount(self):
- return Decimal128(self.amount)
- def __eq__(self, other):
- if isinstance(other, Ratio):
- return self.amount == other.amount
- else:
- raise TypeError('Ratio can only be compared to another Ratio')
- def __ne__(self, other):
- result = self.__eq__(other)
- return not result
- def __lt__(self, other):
- if not isinstance(other, Ratio):
- raise TypeError('Ratio can only be compared against another ratio')
- return self.amount < other.amount
- def __gt__(self, other):
- if not isinstance(other, Ratio):
- raise TypeError('Ratio can only be compared against another ratio')
- return self.amount > other.amount
- def __le__(self, other):
- if not isinstance(other, Ratio):
- raise TypeError('Ratio can only be compared against another ratio')
- return self.amount <= other.amount
- def __ge__(self, other):
- if not isinstance(other, Ratio):
- raise TypeError('Ratio can only be compared against another ratio')
- return self.amount >= other.amount
- def __repr__(self):
- return '<Ratio {amount:.4f}>'.format(amount=self._amount)
- def quantize(self):
- return self.__class__(self._amount.quantize(Decimal('0.0001'), rounding=ROUND_DOWN))
- def __str__(self):
- return u'{amount}'.format(amount=self.quantize()._amount)
- def __add__(self, other):
- if isinstance(other, Ratio):
- return self.__class__(self._amount + other.amount)
- else:
- raise TypeError('Ratio can only be added to a Ratio object')
- def __sub__(self, other):
- if isinstance(other, Ratio):
- return self.__class__(self._amount - other.amount)
- else:
- raise TypeError('Ratio can only be subtracted to a Ratio object')
- def __mul__(self, other):
- if isinstance(other, (Money,Ratio)):
- return other.__class__(amount=(self.amount * other.amount))
- else:
- return self.__class__(amount=(self.amount * force_decimal(other)))
- def __div__(self, other):
- if isinstance(other, (Money, Percent, Ratio)):
- raise TypeError('Ration objects don\'t support division with (Money, Percent, Ration)')
- else:
- return Ratio(self.amount / force_decimal(other))
- def sum_ratio(ratios):
- return sum(ratios, Ratio(0))
- class Percent(Ratio):
- @property
- def as_ratio(self):
- return Ratio(self.amount * Decimal('0.01'))
- def __mul__(self, other):
- if isinstance(other, Money):
- return other.__class__(amount=(self.as_ratio.amount * other.amount))
- elif isinstance(other, Percent):
- return Percent(self.amount * other.as_ratio.amount)
- else:
- return self.__class__(amount=(self.amount * force_decimal(other)))
- def __div__(self, other):
- if isinstance(other, (Money, Percent, Ratio)):
- raise TypeError('Percent objects don\'t support division with (Money, Percent, Ration)')
- else:
- return Percent(self.amount / force_decimal(other))
- def __repr__(self):
- return '<Percent {amount:.2f}>'.format(amount = self._amount)
- def __str__(self):
- return u'{amount}'.format(amount = quantize(self._amount, places='0.01'))
- class Permillage(Ratio):
- @property
- def as_ratio(self):
- return Ratio(self.amount * Decimal('0.001'))
- def __mul__(self, other):
- if isinstance(other, Money):
- return other.__class__(amount=(self.as_ratio.amount * other.amount))
- elif isinstance(other, Permillage):
- return Percent(self.amount * other.as_ratio.amount)
- else:
- return self.__class__(amount=(self.amount * force_decimal(other)))
- def __div__(self, other):
- if isinstance(other, Money):
- raise TypeError('Percent objects don\'t support division with (Money, Percent, Permillage, Ratio)')
- else:
- return Percent(self.amount / force_decimal(other))
- def __repr__(self):
- return '<Permillage {amount:.2f}>'.format(amount = self._amount)
- def __str__(self):
- return u'{amount}'.format(amount = quantize(self._amount, places='0.01'))
- class AccuracyRMB(RMB):
- """
- 精度等级更高的RMB 算到0.01分 适用于单笔结账
- """
- __places__ = "0.001"
- __currency__ = "AccuracyRMB"
- def __repr__(self): return '<{currency} {amount:.4f}> '.format(currency=self.__currency__, amount=self._amount)
|