monetary.py 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. # -*- coding: utf-8 -*-
  2. #!/usr/bin/env python
  3. from decimal import Decimal, ROUND_DOWN
  4. from bson.decimal128 import Decimal128
  5. from apilib.numerics import force_decimal, quantize, UnitBase
  6. class MoneyComparisonError(TypeError):
  7. # This exception was needed often enough to merit its own
  8. # Exception class.
  9. def __init__(self, other):
  10. assert not isinstance(other, Money)
  11. self.other = other
  12. def __str__(self):
  13. # Note: at least w/ Python 2.x, use __str__, not __unicode__.
  14. return "Cannot compare instances of Money and %s" \
  15. % self.other.__class__.__name__
  16. class Money(UnitBase):
  17. """所有与金钱相来往的操作都走这个类,不暴露具体的运算,如取精度等"""
  18. # 以后或许会有多币种支持
  19. __currency__ = None
  20. __places__ = '0.01'
  21. def __init__(self, amount):
  22. if isinstance(amount, Money):
  23. self._amount = quantize(amount.amount, places=self.__places__)
  24. else:
  25. self._amount = quantize(Decimal(str(amount).strip()), places=self.__places__)
  26. @property
  27. def amount(self):
  28. return self._amount
  29. @property
  30. def mongo_amount(self):
  31. return Decimal128(self.amount)
  32. @property
  33. def precision(self):
  34. """精度等级 小数的位数"""
  35. return len(self.__places__.split(".")[1])
  36. def __repr__(self): return '<{currency} {amount:.2f}> '.format(currency=self.__currency__, amount=self._amount)
  37. def __add__(self, other):
  38. if isinstance(other, Money):
  39. return self.__class__(self._amount + other.amount)
  40. else:
  41. return self.__class__(other) + self
  42. def __sub__(self, other):
  43. if isinstance(other, Money):
  44. return self.__class__(self._amount - other.amount)
  45. else:
  46. return self - self.__class__(other)
  47. def __mul__(self, other):
  48. if isinstance(other, Money):
  49. raise TypeError('Cannot multiply two Money instances.')
  50. elif isinstance(other, Ratio):
  51. return self.__class__(amount=(self.amount * other.amount))
  52. else:
  53. return self.__class__(amount=(self.amount * force_decimal(other)))
  54. def __abs__(self):
  55. return self.__class__(amount=abs(self.amount))
  56. def __neg__(self):
  57. return self.__class__(amount=-self.amount)
  58. def __eq__(self, other):
  59. return (isinstance(other, Money) and
  60. (self.amount == other.amount))
  61. def __ne__(self, other):
  62. result = self.__eq__(other)
  63. return not result
  64. def __lt__(self, other):
  65. if not isinstance(other, Money):
  66. raise MoneyComparisonError(other)
  67. return self.amount < other.amount
  68. def __gt__(self, other):
  69. if not isinstance(other, Money):
  70. raise MoneyComparisonError(other)
  71. return self.amount > other.amount
  72. def __le__(self, other):
  73. if not isinstance(other, Money):
  74. raise MoneyComparisonError(other)
  75. return self.amount <= other.amount
  76. def __ge__(self, other):
  77. if not isinstance(other, Money):
  78. raise MoneyComparisonError(other)
  79. return self.amount >= other.amount
  80. def __str__(self):
  81. return str(self._amount)
  82. def __int__(self):
  83. return int(self._amount)
  84. def __float__(self):
  85. return float(self._amount)
  86. class RMB(Money):
  87. """
  88. > RMB(1) + RMB(2) == RMB(3)
  89. """
  90. __currency__ = 'RMB'
  91. def __str__(self):
  92. return u'{amount}'.format(amount=self._amount)
  93. @classmethod
  94. def fen_to_yuan(cls, fen):
  95. return cls(fen) * Decimal('0.01')
  96. @classmethod
  97. def yuan_to_fen(cls, yuan):
  98. return int((yuan * 100))
  99. class VirtualCoin(Money):
  100. __currency__ = 'VirtualCoin'
  101. def __str__(self):
  102. return u'{amount}'.format(amount=self._amount)
  103. @property
  104. def as_impulses(self):
  105. return int(self.amount)
  106. def sum_rmb(list_):
  107. return sum(list_, RMB(0))
  108. def sum_virtual_coin(list_):
  109. return sum(list_, VirtualCoin(0))
  110. def sum_accuracy_rmb(list_):
  111. return sum(list_, AccuracyRMB(0))
  112. class Ratio(object):
  113. def __init__(self, amount):
  114. if isinstance(amount, Ratio):
  115. self._amount = amount.amount
  116. else:
  117. amount = Decimal(str(amount).strip()) # type: Decimal
  118. self._amount = amount
  119. @property
  120. def amount(self):
  121. return self._amount
  122. @property
  123. def mongo_amount(self):
  124. return Decimal128(self.amount)
  125. def __eq__(self, other):
  126. if isinstance(other, Ratio):
  127. return self.amount == other.amount
  128. else:
  129. raise TypeError('Ratio can only be compared to another Ratio')
  130. def __ne__(self, other):
  131. result = self.__eq__(other)
  132. return not result
  133. def __lt__(self, other):
  134. if not isinstance(other, Ratio):
  135. raise TypeError('Ratio can only be compared against another ratio')
  136. return self.amount < other.amount
  137. def __gt__(self, other):
  138. if not isinstance(other, Ratio):
  139. raise TypeError('Ratio can only be compared against another ratio')
  140. return self.amount > other.amount
  141. def __le__(self, other):
  142. if not isinstance(other, Ratio):
  143. raise TypeError('Ratio can only be compared against another ratio')
  144. return self.amount <= other.amount
  145. def __ge__(self, other):
  146. if not isinstance(other, Ratio):
  147. raise TypeError('Ratio can only be compared against another ratio')
  148. return self.amount >= other.amount
  149. def __repr__(self):
  150. return '<Ratio {amount:.4f}>'.format(amount=self._amount)
  151. def quantize(self):
  152. return self.__class__(self._amount.quantize(Decimal('0.0001'), rounding=ROUND_DOWN))
  153. def __str__(self):
  154. return u'{amount}'.format(amount=self.quantize()._amount)
  155. def __add__(self, other):
  156. if isinstance(other, Ratio):
  157. return self.__class__(self._amount + other.amount)
  158. else:
  159. raise TypeError('Ratio can only be added to a Ratio object')
  160. def __sub__(self, other):
  161. if isinstance(other, Ratio):
  162. return self.__class__(self._amount - other.amount)
  163. else:
  164. raise TypeError('Ratio can only be subtracted to a Ratio object')
  165. def __mul__(self, other):
  166. if isinstance(other, (Money,Ratio)):
  167. return other.__class__(amount=(self.amount * other.amount))
  168. else:
  169. return self.__class__(amount=(self.amount * force_decimal(other)))
  170. def __div__(self, other):
  171. if isinstance(other, (Money, Percent, Ratio)):
  172. raise TypeError('Ration objects don\'t support division with (Money, Percent, Ration)')
  173. else:
  174. return Ratio(self.amount / force_decimal(other))
  175. def sum_ratio(ratios):
  176. return sum(ratios, Ratio(0))
  177. class Percent(Ratio):
  178. @property
  179. def as_ratio(self):
  180. return Ratio(self.amount * Decimal('0.01'))
  181. def __mul__(self, other):
  182. if isinstance(other, Money):
  183. return other.__class__(amount=(self.as_ratio.amount * other.amount))
  184. elif isinstance(other, Percent):
  185. return Percent(self.amount * other.as_ratio.amount)
  186. else:
  187. return self.__class__(amount=(self.amount * force_decimal(other)))
  188. def __div__(self, other):
  189. if isinstance(other, (Money, Percent, Ratio)):
  190. raise TypeError('Percent objects don\'t support division with (Money, Percent, Ration)')
  191. else:
  192. return Percent(self.amount / force_decimal(other))
  193. def __repr__(self):
  194. return '<Percent {amount:.2f}>'.format(amount = self._amount)
  195. def __str__(self):
  196. return u'{amount}'.format(amount = quantize(self._amount, places='0.01'))
  197. class Permillage(Ratio):
  198. @property
  199. def as_ratio(self):
  200. return Ratio(self.amount * Decimal('0.001'))
  201. def __mul__(self, other):
  202. if isinstance(other, Money):
  203. return other.__class__(amount=(self.as_ratio.amount * other.amount))
  204. elif isinstance(other, Permillage):
  205. return Percent(self.amount * other.as_ratio.amount)
  206. else:
  207. return self.__class__(amount=(self.amount * force_decimal(other)))
  208. def __div__(self, other):
  209. if isinstance(other, Money):
  210. raise TypeError('Percent objects don\'t support division with (Money, Percent, Permillage, Ratio)')
  211. else:
  212. return Percent(self.amount / force_decimal(other))
  213. def __repr__(self):
  214. return '<Permillage {amount:.2f}>'.format(amount = self._amount)
  215. def __str__(self):
  216. return u'{amount}'.format(amount = quantize(self._amount, places='0.01'))
  217. class AccuracyRMB(RMB):
  218. """
  219. 精度等级更高的RMB 算到0.01分 适用于单笔结账
  220. """
  221. __places__ = "0.001"
  222. __currency__ = "AccuracyRMB"
  223. def __repr__(self): return '<{currency} {amount:.4f}> '.format(currency=self.__currency__, amount=self._amount)