Source code for Acquire.Accounting._transaction


__all__ = ["Transaction"]


def _getcontext():
    """Return the context used for all decimals in transactions. This
       context rounds to 6 decimal places and provides sufficient precision
       to support any value between 0 and 999,999,999,999,999.999,999,999
       (i.e. everything up to just under one quadrillion - I doubt we will
        ever have an account that has more than a trillion units in it!)
    """
    from decimal import Context as _Context
    return _Context(prec=24)


def _create_decimal(value):
    """Create a decimal from the passed value. This is a decimal that
       has 6 decimal places and is clamped between 0 <= value < 1 quadrillion
    """
    from decimal import Decimal as _Decimal

    try:
        d = _Decimal("%.6f" % value, _getcontext())
    except:
        value = _Decimal(value, _getcontext())
        d = _Decimal("%.6f" % value, _getcontext())

    if d < 0:
        from Acquire.Accounting import TransactionError
        raise TransactionError(
                "You cannot create a transaction with a negative value (%s)"
                % (value))

    elif d >= 1000000000000000:
        from Acquire.Accounting import TransactionError
        raise TransactionError(
                "You cannot create a transaction with a value greater than "
                "1 quadrillion! (%s)" % (value))

    return d


[docs]class Transaction: """This class provides basic information about a transaction - namely just the value (always positive) and what the transaction is for """ def __init__(self, value=0, description=None): """Create a transaction with the passed value and description. Values are positive values with a minimum resolution of 1e-6 (6 decimal places) and cannot exceed 999999.999999 (i.e. cannot be 1 million or greater). This is to ensure that a value will always fit into a f13.6 string (which is used for keys). If you need larger transactions, then use the static 'split' function to break a single too-large transaction into a list of smaller transactions """ value = _create_decimal(value) if value > Transaction.maximum_transaction_value(): from Acquire.Accounting import TransactionError raise TransactionError( "You cannot create a transaction (%s) with a " "value greater than %s. Please " "use 'split' to break this transaction (%s) into " "several separate transactions" % (description, Transaction.maximum_transaction_value(), value)) # ensure that the value is limited in resolution to 6 decimal places self._value = value if self._value < 0: from Acquire.Accounting import TransactionError raise TransactionError( "You cannot create a transaction (%s) with a " "negative value! %s" % (description, value)) if description is None: if self._value > 0: from Acquire.Accounting import TransactionError raise TransactionError( "You must give a description to all non-zero " "transactions! %s" % self.value()) else: self._description = None else: # ensure we are using utf-8 encoded strings self._description = str(description).encode("utf-8") \ .decode("utf-8") def __str__(self): return "%s [%s]" % (self.value(), self.description()) def __eq__(self, other): if isinstance(other, Transaction): return self.value() == other.value() and \ self.description() == other.description() else: return self.value() == _create_decimal(other) def __ne__(self, other): return not self.__eq__(other) def __lt__(self, other): if isinstance(other, Transaction): return self.value() < other.value() else: return self.value() < _create_decimal(other) def __gt__(self, other): if isinstance(other, Transaction): return self.value() > other.value() else: return self.value() > _create_decimal(other) def __ge__(self, other): return self.__eq__(other) or self.__gt__(other) def __le__(self, other): return self.__eq__(other) or self.__lt__(other)
[docs] def is_null(self): """Return whether or not this is a null transaction""" return self._value == 0 and self._description is None
[docs] def value(self): """Return the value of this transaction. This will be always greater than or equal to zero """ return self._value
[docs] def description(self): """Return the description of this transaction""" return self._description
[docs] @staticmethod def maximum_transaction_value(): """Return the maximum value for a single transaction. Currently this is 999999.999999 so that a transaction fits into a f013.6 string """ return _create_decimal(999999.999999)
[docs] @staticmethod def round(value): """Round the passed floating point value to the precision level of the transaction (currently 6 decimal places) """ return _create_decimal(value)
[docs] @staticmethod def split(value, description): """Split the passed transaction described by 'description' with value 'value' into a list of transactions that fit the maximum transaction limit. """ value = _create_decimal(value) if value < Transaction.maximum_transaction_value(): t = Transaction(value, description) return [t] else: orig_value = value values = [] while value > Transaction.maximum_transaction_value(): values.append(Transaction.maximum_transaction_value()) value -= Transaction.maximum_transaction_value() if value > 0: values.append(value) total = 0 for value in values: total += value if total != orig_value: values[-1] -= (total - orig_value) transactions = [] for i in range(0, len(values)): transactions.append(Transaction(values[i], "%s: %d of %d" % (description, i+1, len(values)))) values = None total = 0 for transaction in transactions: total += transaction.value() # ensure that the total is also rounded to 6 dp total = _create_decimal(total) if total != orig_value: from Acquire.Accounting import TransactionError raise TransactionError( "Error as split sum (%s) is not equal to the original " "value (%s)" % (total, orig_value)) return transactions
[docs] @staticmethod def from_data(data): """Return a newly constructed transaction from the passed dictionary that has been decoded from json """ transaction = Transaction() if (data and len(data) > 0): from Acquire.Accounting import create_decimal as _create_decimal transaction._value = _create_decimal(data["value"]) transaction._description = data["description"] return transaction
[docs] def to_data(self): """Return this transaction as a dictionary that can be encoded to json """ data = {} if not self.is_null(): data["value"] = str(self.value()) data["description"] = self._description return data