__all__ = ["CreditNote"]
[docs]class CreditNote:
"""This class holds all of the information about a completed credit. This
is combined with a debit note of equal value to form a transaction
record
"""
def __init__(self, debit_note=None, account=None,
receipt=None, refund=None, bucket=None):
"""Create the corresponding credit note for the passed debit_note. This
will credit value from the note to the passed account. The credit
will use the same UID as the debit, and the same datetime. This
will then be paired with the debit note to form a TransactionRecord
that can be written to the ledger
"""
self._account_uid = None
nargs = (receipt is not None) + (refund is not None)
if nargs > 1:
raise ValueError("You can create a CreditNote with a receipt "
"or a refund - not both!")
if receipt is not None:
self._create_from_receipt(debit_note, receipt, account, bucket)
elif refund is not None:
self._create_from_refund(debit_note, refund, account, bucket)
elif (debit_note is not None) and (account is not None):
self._create_from_debit_note(debit_note, account, bucket)
else:
self._debit_account_uid = None
self._datetime = None
self._uid = None
self._debit_note_uid = None
from Acquire.Accounting import create_decimal as _create_decimal
self._value = _create_decimal(0)
def __str__(self):
if self.is_null():
return "CreditNote::null"
else:
return "CreditNote:%s>>%s" % (self.value(), self.account_uid())
def __eq__(self, other):
if isinstance(other, self.__class__):
return self._uid == other._uid
else:
return False
def __ne__(self, other):
return not self.__eq__(other)
[docs] def is_null(self):
"""Return whether or not this note is null"""
return self._uid is None
[docs] def needs_receipting(self):
"""Return whether or not this CreditNote needs to be receipted.
If so, then the funds are only held provisionally, and must
be receipted by CreditNote.receipt_by() else they will
be returned to the DebitNote account
"""
return self.is_provisional()
[docs] def receipt_by(self):
"""Return the datetime by which this credit note must be
receipted, or else the funds will be returned. This will
return None if the CreditNote does not need receipting
"""
if self.is_provisional():
return self._receipt_by
else:
return None
[docs] def account_uid(self):
"""Return the UID of the account to which the value was credited"""
if self.is_null():
return None
else:
return self._account_uid
[docs] def credit_account_uid(self):
"""Synonym for self.account_uid()"""
return self.account_uid()
[docs] def debit_account_uid(self):
"""Return the UID of the account from which the value was debited"""
if self.is_null():
return None
else:
return self._debit_account_uid
[docs] def datetime(self):
"""Return the datetime for this credit note"""
return self._datetime
[docs] def uid(self):
"""Return the UID of this credit note. This will not match the debit
note UID - you need to use debit_note_uid() to get the UID of
the debit note that matches this credit note
"""
return self._uid
[docs] def debit_note_uid(self):
"""Return the UID of the debit note that matches this credit note.
While at the moment only a single credit note matches a debit note,
it may be in the future that we divide a credit over several
accounts (and thus several credit notes)
"""
return self._debit_note_uid
[docs] def value(self):
"""Return the value of this note. This may be less than the
corresponding debit note if only part of the value of the
debit note is transferred into the account
"""
return self._value
[docs] def is_provisional(self):
"""Return whether or not this credit note is provisional
(i.e. the value will only be transferred on completion
of work and provision of a receipt. Note that the value
will only be transferred if this CreditNote is receipted
before CreditNote.receipt_by())
"""
if self.is_null():
return False
else:
return self._is_provisional
def _create_from_refund(self, debit_note, refund, account, bucket):
"""Internal function used to create the credit note from
the passed refund. This will actually transfer value from the
debit note to the credited account (which was the original
debited account)
"""
from Acquire.Accounting import DebitNote as _DebitNote
from Acquire.Accounting import Refund as _Refund
from Acquire.Accounting import TransactionRecord as _TransactionRecord
from Acquire.Accounting import TransactionState as _TransactionState
from Acquire.Accounting import Account as _Account
if not isinstance(debit_note, _DebitNote):
raise TypeError("You can only create a CreditNote "
"with a DebitNote")
if not isinstance(refund, _Refund):
raise TypeError("You can only refund a Refund object: %s"
% str(refund.__class__))
# get the transaction behind this refund and ensure it is in the
# refunding state...
transaction = _TransactionRecord.load_test_and_set(
refund.transaction_uid(),
_TransactionState.REFUNDING,
_TransactionState.REFUNDING, bucket=bucket)
# ensure that the receipt matches the transaction...
transaction.assert_matching_refund(refund)
if account is None:
account = _Account(transaction.debit_account_uid(), bucket)
elif account.uid() != refund.debit_account_uid():
raise ValueError("The accounts do not match when refunding "
"the receipt: %s versus %s" %
(account.uid(), refund.debit_account_uid()))
(uid, datetime) = account._credit_refund(debit_note, refund, bucket)
self._account_uid = account.uid()
self._debit_account_uid = debit_note.account_uid()
self._datetime = datetime
self._uid = uid
self._debit_note_uid = debit_note.uid()
self._value = debit_note.value()
self._is_provisional = debit_note.is_provisional()
if self._is_provisional:
self._receipt_by = debit_note.receipt_by()
# finally(!) move the transaction into the refunded state
_TransactionRecord.load_test_and_set(
refund.transaction_uid(),
_TransactionState.REFUNDING,
_TransactionState.REFUNDED, bucket=bucket)
def _create_from_receipt(self, debit_note, receipt, account, bucket):
"""Internal function used to create the credit note from
the passed receipt. This will actually transfer value from the
debit note to the credited account
"""
from Acquire.Accounting import DebitNote as _DebitNote
from Acquire.Accounting import Refund as _Refund
from Acquire.Accounting import TransactionRecord as _TransactionRecord
from Acquire.Accounting import TransactionState as _TransactionState
from Acquire.Accounting import Account as _Account
from Acquire.Accounting import Receipt as _Receipt
if not isinstance(debit_note, _DebitNote):
raise TypeError("You can only create a CreditNote "
"with a DebitNote")
if not isinstance(receipt, _Receipt):
raise TypeError("You can only receipt a Receipt object: %s"
% str(receipt.__class__))
# get the transaction behind this receipt and ensure it is in the
# receipting state...
transaction = _TransactionRecord.load_test_and_set(
receipt.transaction_uid(),
_TransactionState.RECEIPTING,
_TransactionState.RECEIPTING, bucket=bucket)
# ensure that the receipt matches the transaction...
transaction.assert_matching_receipt(receipt)
if account is None:
account = _Account(transaction.credit_account_uid(), bucket)
elif account.uid() != receipt.credit_account_uid():
raise ValueError("The accounts do not match when crediting "
"the receipt: %s versus %s" %
(account.uid(), receipt.credit_account_uid()))
(uid, datetime) = account._credit_receipt(debit_note, receipt, bucket)
self._account_uid = account.uid()
self._debit_account_uid = debit_note.account_uid()
self._datetime = datetime
self._uid = uid
self._debit_note_uid = debit_note.uid()
self._value = debit_note.value()
self._is_provisional = debit_note.is_provisional()
if debit_note.is_provisional():
self._receipt_by = debit_note.receipt_by()
# finally(!) move the transaction into the receipted state
_TransactionRecord.load_test_and_set(
receipt.transaction_uid(),
_TransactionState.RECEIPTING,
_TransactionState.RECEIPTED, bucket=bucket)
def _create_from_debit_note(self, debit_note, account, bucket):
"""Internal function used to create the credit note that matches
the passed debit note. This will actually transfer value from
the debit note to the passed account
"""
from Acquire.Accounting import DebitNote as _DebitNote
from Acquire.Accounting import Account as _Account
if not isinstance(debit_note, _DebitNote):
raise TypeError("You can only create a CreditNote "
"with a DebitNote")
if not isinstance(account, _Account):
raise TypeError("You can only create a CreditNote with an "
"Account")
(uid, datetime) = account._credit(debit_note, bucket=bucket)
self._account_uid = account.uid()
self._debit_account_uid = debit_note.account_uid()
self._datetime = datetime
self._uid = uid
self._debit_note_uid = debit_note.uid()
self._value = debit_note.value()
self._is_provisional = debit_note.is_provisional()
if self._is_provisional:
self._receipt_by = debit_note.receipt_by()
[docs] @staticmethod
def from_data(data):
"""Construct and return a new CreditNote from the passed json-decoded
dictionary
"""
note = CreditNote()
if (data and len(data) > 0):
from Acquire.ObjectStore import string_to_datetime \
as _string_to_datetime
from Acquire.Accounting import create_decimal as _create_decimal
note._account_uid = data["account_uid"]
note._debit_account_uid = data["debit_account_uid"]
note._uid = data["uid"]
note._debit_note_uid = data["debit_note_uid"]
note._datetime = _string_to_datetime(data["datetime"])
note._value = _create_decimal(data["value"])
note._is_provisional = data["is_provisional"]
if note._is_provisional:
note._receipt_by = _string_to_datetime(data["receipt_by"])
return note
[docs] def to_data(self):
"""Return this credit note as a dictionary that can be
encoded to json
"""
data = {}
if not self.is_null():
from Acquire.ObjectStore import datetime_to_string \
as _datetime_to_string
data["account_uid"] = self._account_uid
data["debit_account_uid"] = self._debit_account_uid
data["uid"] = self._uid
data["debit_note_uid"] = self._debit_note_uid
data["datetime"] = _datetime_to_string(self._datetime)
data["value"] = str(self._value)
data["is_provisional"] = self._is_provisional
if self._is_provisional:
data["receipt_by"] = _datetime_to_string(self._receipt_by)
return data