# -*- 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.__class__(other) - self 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 ''.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 ''.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 ''.format(amount = self._amount) def __str__(self): return u'{amount}'.format(amount = quantize(self._amount, places='0.01')) class JDMerchantPermillage(Permillage): def to_jd_params(self): """ 打印出来的是百分之多少 比如设定的是JDMerchantPermillage(6) 表示是千分之6 则使用str之后 显示的是 0.60 :return: """ return u'{amount:.2f}'.format(amount=quantize((self * Decimal('0.1'))._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)