Esempio n. 1
0
    def get_account(self, name, bucket=None):
        """Return the account called 'name' from this group

            Args:
                name (:obj:`str`): Name of account to retrieve
                bucket (:obj:`dict`): Bucket to load data from

            Returns:
                :obj:`Account`: Account object
        """
        self._assert_is_readable()

        if bucket is None:
            from Acquire.Service import get_service_account_bucket \
                as _get_service_account_bucket
            bucket = _get_service_account_bucket()

        try:
            from Acquire.ObjectStore import ObjectStore as _ObjectStore
            account_uid = _ObjectStore.get_string_object(
                bucket, self._account_key(name))
        except:
            account_uid = None

        if account_uid is None:
            # ensure that the user always has a "main" account
            if name == "main":
                return self.create_account("main",
                                           "primary user account",
                                           overdraft_limit=0,
                                           bucket=bucket)

            from Acquire.Accounting import AccountError
            raise AccountError("There is no account called '%s' in the "
                               "group '%s'" % (name, self.group()))

        from Acquire.Accounting import Account as _Account
        return _Account(uid=account_uid, bucket=bucket)
Esempio n. 2
0
    def refund(refund, bucket=None):
        """Create and record a new transaction from the passed refund. This
           applies the refund, thereby transferring value from the credit
           account to the debit account of the corresponding transaction.
           Note that you can only refund a transaction once!
           This returns the (already recorded) TransactionRecord for the
           refund
        """
        from Acquire.Accounting import Refund as _Refund
        from Acquire.Accounting import Account as _Account
        from Acquire.Accounting import DebitNote as _DebitNote
        from Acquire.Accounting import CreditNote as _CreditNote
        from Acquire.Accounting import PairedNote as _PairedNote
        from Acquire.Accounting import TransactionRecord as _TransactionRecord

        if not isinstance(refund, _Refund):
            raise TypeError("The Refund must be of type Refund")

        if refund.is_null():
            return _TransactionRecord()

        if bucket is None:
            from Acquire.Service import get_service_account_bucket \
                as _get_service_account_bucket
            bucket = _get_service_account_bucket()

        # return value from the credit to debit accounts
        debit_account = _Account(uid=refund.debit_account_uid(), bucket=bucket)
        credit_account = _Account(uid=refund.credit_account_uid(),
                                  bucket=bucket)

        # remember that a refund debits from the original credit account...
        # (and can only refund completed (DIRECT) transactions)
        debit_note = _DebitNote(refund=refund,
                                account=credit_account,
                                bucket=bucket)

        # now create the credit note to return the value into the debit account
        try:
            credit_note = _CreditNote(debit_note=debit_note,
                                      refund=refund,
                                      account=debit_account,
                                      bucket=bucket)
        except Exception as e:
            # delete the debit note
            try:
                debit_account._delete_note(debit_note, bucket=bucket)
            except:
                pass

            # reset the transaction to its original state
            try:
                _TransactionRecord.load_test_and_set(
                    refund.transaction_uid(),
                    _TransactionState.REFUNDING,
                    _TransactionState.DIRECT,
                    bucket=bucket)
            except:
                pass

            raise e

        try:
            paired_notes = _PairedNote.create(debit_note, credit_note)
        except Exception as e:
            # delete all records...!
            try:
                debit_account._delete_note(debit_note, bucket=bucket)
            except:
                pass

            try:
                credit_account._delete_note(credit_note, bucket=bucket)
            except:
                pass

            # reset the transaction to the pending state
            try:
                _TransactionRecord.load_test_and_set(
                    refund.transaction_uid(),
                    _TransactionState.REFUNDING,
                    _TransactionState.DIRECT,
                    bucket=bucket)
            except:
                pass

            raise e

        # now record the two entries to the ledger. The below function
        # is guaranteed not to raise an exception
        return Ledger._record_to_ledger(paired_notes,
                                        refund=refund,
                                        bucket=bucket)
Esempio n. 3
0
    def receipt(receipt, bucket=None):
        """Create and record a new transaction from the passed receipt. This
           applies the receipt, thereby actually transferring value from the
           debit account to the credit account of the corresponding
           transaction. Note that you can only receipt a transaction once!
           This returns the (already recorded) TransactionRecord for the
           receipt

           Args:
                receipt (Receipt): Receipt to use for transaction
                bucket (default=None): Bucket to load data from

           Returns:
                list: List of TransactionRecords

        """
        from Acquire.Accounting import Receipt as _Receipt
        from Acquire.Accounting import Account as _Account
        from Acquire.Accounting import DebitNote as _DebitNote
        from Acquire.Accounting import CreditNote as _CreditNote
        from Acquire.Accounting import TransactionRecord as _TransactionRecord
        from Acquire.Accounting import PairedNote as _PairedNote

        if not isinstance(receipt, _Receipt):
            raise TypeError("The Receipt must be of type Receipt")

        if receipt.is_null():
            return _TransactionRecord()

        if bucket is None:
            from Acquire.Service import get_service_account_bucket \
                as _get_service_account_bucket
            bucket = _get_service_account_bucket()

        # extract value into the debit note
        debit_account = _Account(uid=receipt.debit_account_uid(),
                                 bucket=bucket)
        credit_account = _Account(uid=receipt.credit_account_uid(),
                                  bucket=bucket)

        debit_note = _DebitNote(receipt=receipt,
                                account=debit_account,
                                bucket=bucket)

        # now create the credit note to put the value into the credit account
        try:
            credit_note = _CreditNote(debit_note=debit_note,
                                      receipt=receipt,
                                      account=credit_account,
                                      bucket=bucket)
        except Exception as e:
            # delete the debit note
            try:
                debit_account._delete_note(debit_note, bucket=bucket)
            except:
                pass

            # reset the transaction to the pending state
            try:
                _TransactionRecord.load_test_and_set(
                    receipt.transaction_uid(),
                    _TransactionState.RECEIPTING,
                    _TransactionState.PENDING,
                    bucket=bucket)
            except:
                pass

            raise e

        try:
            paired_notes = _PairedNote.create(debit_note, credit_note)
        except Exception as e:
            # delete all records...!
            try:
                debit_account._delete_note(debit_note, bucket=bucket)
            except:
                pass

            try:
                credit_account._delete_note(credit_note, bucket=bucket)
            except:
                pass

            # reset the transaction to the pending state
            try:
                _TransactionRecord.load_test_and_set(
                    receipt.transaction_uid(),
                    _TransactionState.RECEIPTING,
                    _TransactionState.PENDING,
                    bucket=bucket)
            except:
                pass

            raise e

        # now record the two entries to the ledger. The below function
        # is guaranteed not to raise an exception
        return Ledger._record_to_ledger(paired_notes,
                                        receipt=receipt,
                                        bucket=bucket)
Esempio n. 4
0
    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

           debit_note (DebitNote): DebitNote from which to take value
           receipt (Receipt): Receipt to create CreditNote from
           account (Account): Account to credit
           bucket (Bucket): Bucket to load data from

           Returns:
                None

        """
        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)
Esempio n. 5
0
    def create_account(self,
                       name,
                       description=None,
                       overdraft_limit=None,
                       bucket=None,
                       authorisation=None):
        """Create a new account called 'name' in this group. This will
           return the existing account if it already exists

           Args:
                name (str): Name of account to create
                description (default=None): Description of account
                overdraft_limit (int, default=None): Limit of overdraft
                bucket (dict, default=None): Bucket to load data from

            Returns:
                Account: New Account object
        """
        if name is None:
            raise ValueError("You must pass a name of the new account")

        from Acquire.Identity import Authorisation as _Authorisation

        if authorisation is not None:
            if not isinstance(authorisation, _Authorisation):
                raise TypeError("The authorisation must be type Authorisation")

            if self._user_guid is not None:
                if self._user_guid != authorisation.user_guid():
                    raise PermissionError(
                        "The same user who opened this accounts group (%s) "
                        "must create accounts in this group (%s)" %
                        (self._user_guid, authorisation.user_guid()))

            authorisation.verify("create_account %s" % name)

        self._assert_is_writeable()

        account_key = self._account_key(name)

        if bucket is None:
            from Acquire.Service import get_service_account_bucket \
                as _get_service_account_bucket
            bucket = _get_service_account_bucket()

        from Acquire.ObjectStore import ObjectStore as _ObjectStore
        from Acquire.Accounting import Account as _Account

        try:
            account_uid = _ObjectStore.get_string_object(bucket, account_key)
        except:
            account_uid = None

        if account_uid is not None:
            # this account already exists - just return it
            account = _Account(uid=account_uid, bucket=bucket)

            if account.group_name() != self.name():
                account.set_group(self)

            if overdraft_limit is not None:
                account.set_overdraft_limit(overdraft_limit, bucket=bucket)

            return account

        # make sure that no-one has created this account before
        from Acquire.ObjectStore import Mutex as _Mutex
        m = _Mutex(account_key, timeout=600, lease_time=600, bucket=bucket)

        try:
            account_uid = _ObjectStore.get_string_object(bucket, account_key)
        except:
            account_uid = None

        if account_uid is not None:
            m.unlock()
            # this account already exists - just return it
            account = _Account(uid=account_uid, bucket=bucket)

            if account.group_name() != self.name():
                account.set_group(self)

            if overdraft_limit is not None:
                account.set_overdraft_limit(overdraft_limit, bucket=bucket)

            return account

        # write a temporary UID to the object store so that we
        # can ensure we are the only function to create it
        try:
            _ObjectStore.set_string_object(bucket, account_key,
                                           "under_construction")
        except:
            m.unlock()
            raise

        m.unlock()

        # ok - we are the only function creating this account. Let's try
        # to create it properly. The account is created with the same
        # ACLRules of the group.
        try:
            from Acquire.Identity import ACLRules as _ACLRules
            account = _Account(name=name,
                               description=description,
                               group_name=self.name(),
                               aclrules=_ACLRules.inherit(),
                               bucket=bucket)
        except:
            try:
                _ObjectStore.delete_object(bucket, account_key)
            except:
                pass

            raise

        if overdraft_limit is not None:
            account.set_overdraft_limit(overdraft_limit, bucket=bucket)

        _ObjectStore.set_string_object(bucket, account_key, account.uid())

        return account
Esempio n. 6
0
    def _create_from_receipt(self, receipt, account, bucket):
        """Function used to construct a debit note by extracting
           the value specified in the passed receipt from the specified
           account. This is authorised using the authorisation held in
           the receipt, based on the original authorisation given in the
           provisional transaction. Note that the receipt must match
           up with a prior existing provisional transaction, and this
           must not have already been receipted or refunded. This will
           actually take value out of the passed account, with that
           value residing in this debit note until it is credited to
           another account

           Args:
                receipt (Receipt): Receipt to create DebitNote from
                account (Account): Account to take value from
                bucket (dict): Bucket to read data from
        """
        from Acquire.Accounting import Receipt as _Receipt

        if not isinstance(receipt, _Receipt):
            raise TypeError("You can only create a DebitNote with a "
                            "Receipt")

        if receipt.is_null():
            return

        if bucket is None:
            from Acquire.Service import get_service_account_bucket \
                as _get_service_account_bucket
            bucket = _get_service_account_bucket()

        from Acquire.Accounting import TransactionRecord as _TransactionRecord
        from Acquire.Accounting import TransactionState as _TransactionState
        from Acquire.Accounting import Account as _Account

        # get the transaction behind this receipt and move it into
        # the "receipting" state
        transaction = _TransactionRecord.load_test_and_set(
                        receipt.transaction_uid(),
                        _TransactionState.PROVISIONAL,
                        _TransactionState.RECEIPTING, bucket=bucket)

        try:
            # ensure that the receipt matches the transaction...
            transaction.assert_matching_receipt(receipt)

            if account is None:
                account = _Account(transaction.debit_account_uid(), bucket)
            elif account.uid() != receipt.debit_account_uid():
                raise ValueError("The accounts do not match when debiting "
                                 "the receipt: %s versus %s" %
                                 (account.uid(), receipt.debit_account_uid()))

            # now move value from liability to debit, and then into this
            # debit note
            (uid, datetime) = account._debit_receipt(receipt, bucket)

            self._transaction = receipt.transaction()
            self._account_uid = receipt.debit_account_uid()
            self._authorisation = receipt.authorisation()
            self._is_provisional = False

            self._datetime = datetime
            self._uid = str(uid)
        except:
            # move the transaction back to its original state...
            _TransactionRecord.load_test_and_set(
                        receipt.transaction_uid(),
                        _TransactionState.RECEIPTING,
                        _TransactionState.PROVISIONAL)
            raise