def _create_transaction_and_compare_to_amount(
            self, amount, comparison_amount=None):
        ledger1 = LedgerFactory()
        ledger2 = LedgerFactory()
        transaction = create_transaction(
            UserFactory(),
            ledger_entries=[
                LedgerEntry(
                    ledger=ledger1,
                    amount=amount),
                LedgerEntry(
                    ledger=ledger2,
                    amount=-amount),
            ]
        )

        entry1 = transaction.entries.get(ledger=ledger1)
        entry2 = transaction.entries.get(ledger=ledger2)
        if comparison_amount:
            self.assertNotEqual(entry1.amount, amount)
            self.assertEqual(entry1.amount, comparison_amount)
            self.assertNotEqual(entry2.amount, -amount)
            self.assertEqual(-entry2.amount, comparison_amount)
        else:
            self.assertEqual(entry1.amount, amount)
            self.assertEqual(entry2.amount, -amount)
Esempio n. 2
0
 def test_void_with_overridden_notes_and_type(self):
     """
     Test voiding while setting notes and type.
     """
     amount = D(100)
     evidence = UserFactory.create_batch(3)
     transaction = create_transaction(
         user=UserFactory(),
         evidence=evidence,
         ledger_entries=[
             LedgerEntry(
                 ledger=self.ar_ledger,
                 amount=credit(amount),
             ),
             LedgerEntry(
                 ledger=self.rev_ledger,
                 amount=debit(amount),
             ),
         ],
         type=self.ttype,
     )
     voiding_transaction = void_transaction(
         transaction,
         self.creation_user,
         notes='test notes',
     )
     self.assertEqual(voiding_transaction.notes, 'test notes')
     self.assertEqual(voiding_transaction.type, transaction.type)
 def _create_transaction_with_evidence(cls, evidence):
     return create_transaction(cls.create_user,
                               evidence=evidence,
                               ledger_entries=[
                                   LedgerEntry(ledger=cls.ledger,
                                               amount=credit(cls.AMOUNT)),
                                   LedgerEntry(ledger=cls.ledger,
                                               amount=debit(cls.AMOUNT)),
                               ])
Esempio n. 4
0
 def add_transaction(self, orders):
     return create_transaction(
         self.user,
         evidence=orders,
         ledger_entries=[
             LedgerEntry(ledger=self.ar_ledger, amount=credit(self.amount)),
             LedgerEntry(ledger=self.cash_ledger,
                         amount=debit(self.amount)),
         ],
     )
Esempio n. 5
0
 def add_transaction(self):
     return create_transaction(
         self.user,
         evidence=[self.order],
         ledger_entries=[
             LedgerEntry(ledger=self.ar_ledger, amount=credit(Decimal(50))),
             LedgerEntry(ledger=self.cash_ledger,
                         amount=debit(Decimal(50))),
         ],
     )
Esempio n. 6
0
 def test_debits_not_equal_to_credits(self):
     with self.assertRaises(TransactionBalanceException):
         validate_transaction(
             self.user,
             ledger_entries=[
                 LedgerEntry(ledger=self.revenue,
                             amount=credit(self.AMOUNT)),
                 LedgerEntry(ledger=self.accounts_receivable,
                             amount=debit(self.AMOUNT + 2)),
             ],
         )
Esempio n. 7
0
    def test_auto_timestamp(self):
        """
        If a posted_timestamp isn't specified we assume the posted_timestamp is
        the same as the transaction we're voiding.
        """
        amount = D(100)
        charge_txn = TransactionFactory(self.creation_user, ledger_entries=[
            LedgerEntry(amount=debit(amount), ledger=self.ar_ledger),
            LedgerEntry(amount=credit(amount), ledger=self.rev_ledger),
        ])

        void_txn = void_transaction(charge_txn, self.creation_user)
        self.assertEqual(
            charge_txn.posted_timestamp, void_txn.posted_timestamp)
Esempio n. 8
0
    def test_cant_void_twice(self):
        """
        Voiding a `Transaction` more than once is not permitted.
        """
        amount = D(100)
        txn = TransactionFactory(self.creation_user, ledger_entries=[
            LedgerEntry(amount=debit(amount), ledger=self.ar_ledger),
            LedgerEntry(amount=credit(amount), ledger=self.rev_ledger),
        ])

        void_transaction(txn, self.creation_user)

        self.assertRaises(
            UnvoidableTransactionException,
            void_transaction, txn, self.creation_user)
Esempio n. 9
0
    def test_given_timestamp(self):
        """
        If a posted_timestamp is given for the void, then use it
        """
        amount = D(100)
        charge_txn = TransactionFactory(self.creation_user, ledger_entries=[
            LedgerEntry(amount=debit(amount), ledger=self.ar_ledger),
            LedgerEntry(amount=credit(amount), ledger=self.rev_ledger),
        ])

        now = datetime.now()
        void_txn = void_transaction(
            charge_txn, self.creation_user,
            posted_timestamp=now)
        self.assertEqual(now, void_txn.posted_timestamp)
Esempio n. 10
0
    def test_with_existing_ledger_entry(self):
        existing_transaction = create_transaction(
            self.user,
            ledger_entries=[
                LedgerEntry(ledger=self.accounts_receivable,
                            amount=credit(self.amount)),
                LedgerEntry(ledger=self.accounts_receivable,
                            amount=debit(self.amount)),
            ],
        )

        with self.assertRaises(ExistingLedgerEntriesException):
            create_transaction(
                self.user,
                ledger_entries=list(existing_transaction.entries.all()),
            )
Esempio n. 11
0
    def test_setting_posted_timestamp(self):
        POSTED_DATETIME = datetime(2016, 2, 7, 11, 59)
        order = OrderFactory(amount=self.AMOUNT)

        txn_recognize = create_transaction(
            self.user,
            evidence=[order],
            ledger_entries=[
                LedgerEntry(ledger=self.revenue, amount=credit(self.AMOUNT)),
                LedgerEntry(ledger=self.accounts_receivable,
                            amount=debit(self.AMOUNT)),
            ],
            posted_timestamp=POSTED_DATETIME,
        )

        self.assertEqual(txn_recognize.posted_timestamp, POSTED_DATETIME)
Esempio n. 12
0
    def test_transaction_summary(self):
        ledger = LedgerFactory()
        amount = Decimal('500')
        ccx = CreditCardTransactionFactory()
        le1 = LedgerEntry(ledger=ledger, amount=credit(amount))
        le2 = LedgerEntry(ledger=ledger, amount=debit(amount))
        txn = TransactionFactory(evidence=[ccx], ledger_entries=[le1, le2])

        self.assertEqual(
            txn.summary(),
            {
                'entries': [str(entry) for entry in txn.entries.all()],
                'related_objects': [
                    'TransactionRelatedObject: CreditCardTransaction(id=%s)' %
                    ccx.id,
                ],
            },
        )
Esempio n. 13
0
    def test_custom_ledger_entries(self):
        ledger = LedgerFactory()
        amount = Decimal('500')
        TransactionFactory(evidence=[self.credit_card_transaction],
                           ledger_entries=[
                               LedgerEntry(ledger=ledger,
                                           amount=credit(amount)),
                               LedgerEntry(ledger=ledger,
                                           amount=debit(amount)),
                           ])

        assert_transaction_in_ledgers_for_amounts_with_evidence(
            ledger_amount_pairs=[
                (ledger.name, credit(amount)),
                (ledger.name, debit(amount)),
            ],
            evidence=[self.credit_card_transaction],
        )
Esempio n. 14
0
def TransactionFactory(
    user=None,
    evidence=None,
    ledger_entries=None,
    notes='',
    type=None,
    posted_timestamp=None,
):
    """
    Factory for creating a Transaction

    Instead of inheriting from DjangoModelFactory, TransactionFactory is
    a method made to look like a factory call because the creation and
    validation of Transactions is handeled by `create_transaction`.
    """
    if user is None:
        user = UserFactory()

    if evidence is None:
        evidence = [CreditCardTransactionFactory()]

    if ledger_entries is None:
        ledger = LedgerFactory()
        amount = Decimal('100')
        ledger_entries = [
            LedgerEntry(
                ledger=ledger,
                amount=debit(amount),
            ),
            LedgerEntry(
                ledger=ledger,
                amount=credit(amount),
            ),
        ]

    return create_transaction(
        user,
        evidence=evidence,
        ledger_entries=ledger_entries,
        notes=notes,
        type=type or TransactionTypeFactory(),
        posted_timestamp=posted_timestamp,
    )
Esempio n. 15
0
    def test_can_void_void(self):
        """
        A void can be voided, thus restoring the original transaction.
        """
        amount = D(100)
        txn = TransactionFactory(self.creation_user, ledger_entries=[
            LedgerEntry(amount=debit(amount), ledger=self.ar_ledger),
            LedgerEntry(amount=credit(amount), ledger=self.rev_ledger),
        ])

        void_txn = void_transaction(txn, self.creation_user)

        self.assertEqual(void_txn.voids, txn)

        void_void_txn = (void_transaction(void_txn, self.creation_user))
        self.assertEqual(void_void_txn.voids, void_txn)

        self.assertEqual(self.ar_ledger.get_balance(), amount)
        self.assertEqual(self.rev_ledger.get_balance(), -amount)
Esempio n. 16
0
    def test_void_with_non_default_type(self):
        """
        Test voiding a `Transaction` with a non-default `type`.
        """
        amount = D(100)
        txn = TransactionFactory(self.creation_user, ledger_entries=[
            LedgerEntry(amount=debit(amount), ledger=self.ar_ledger),
            LedgerEntry(amount=credit(amount), ledger=self.rev_ledger),
        ])

        new_ttype = TransactionTypeFactory()
        void_txn = void_transaction(txn, self.creation_user, type=new_ttype)

        self.assertEqual(void_txn.voids, txn)

        self.assertEqual(self.ar_ledger.get_balance(), D(0))
        self.assertEqual(self.rev_ledger.get_balance(), D(0))

        self.assertEqual(void_txn.type, new_ttype)
        self.assertNotEqual(void_txn.type, txn.type)
Esempio n. 17
0
 def test_simple_void(self):
     """
     Test voiding a `Transaction`.
     """
     amount = D(100)
     evidence = UserFactory.create_batch(3)
     transaction = create_transaction(
         user=UserFactory(),
         evidence=evidence,
         ledger_entries=[
             LedgerEntry(
                 ledger=self.ar_ledger,
                 amount=credit(amount),
             ),
             LedgerEntry(
                 ledger=self.rev_ledger,
                 amount=debit(amount),
             ),
         ],
     )
     self.assertEqual(self.ar_ledger.get_balance(), credit(amount))
     self.assertEqual(self.rev_ledger.get_balance(), debit(amount))
     voiding_transaction = void_transaction(transaction, self.creation_user)
     self.assertEqual(
         set(tro.related_object for tro
             in voiding_transaction.related_objects.all()),
         set(evidence),
     )
     self.assertEqual(self.ar_ledger.get_balance(), D(0))
     self.assertEqual(self.rev_ledger.get_balance(), D(0))
     self.assertEqual(voiding_transaction.voids, transaction)
     self.assertEqual(
         voiding_transaction.posted_timestamp,
         transaction.posted_timestamp)
     self.assertEqual(
         voiding_transaction.type,
         transaction.type)
     self.assertEqual(
         voiding_transaction.notes,
         'Voiding transaction {}'.format(transaction))
Esempio n. 18
0
def void_transaction(
    transaction,
    user,
    notes=None,
    type=None,
    posted_timestamp=None,
):
    """
    Create a new transaction that voids the given Transaction.

    The evidence will be the same as the voided Transaction. The ledger
    entries will be the same except have debits and credits swapped.

    If `notes` is not given, a default note will be set.

    If the posted_timestamp or type is not given, they will be the same
    as the voided Transaction.
    """
    try:
        transaction.voided_by
    except Transaction.DoesNotExist:
        # Because OneToOne fields throw an exception instead of returning
        # None!
        pass
    else:
        raise UnvoidableTransactionException(
            "Cannot void the same Transaction #({id}) more than once.".format(
                id=transaction.transaction_id))

    evidence = [
        tro.related_object for tro in transaction.related_objects.all()
    ]

    ledger_entries = [
        LedgerEntry(
            ledger=ledger_entry.ledger,
            amount=-ledger_entry.amount,
        ) for ledger_entry in transaction.entries.all()
    ]

    if notes is None:
        notes = 'Voiding transaction {}'.format(transaction)

    if posted_timestamp is None:
        posted_timestamp = transaction.posted_timestamp

    if type is None:
        type = transaction.type

    voiding_transaction = create_transaction(
        evidence=evidence,
        ledger_entries=ledger_entries,
        notes=notes,
        posted_timestamp=posted_timestamp,
        type=type,
        user=user,
    )

    voiding_transaction.voids = transaction
    voiding_transaction.save()

    return voiding_transaction
Esempio n. 19
0
    def test_using_ledgers_for_reconciliation(self):
        """
        Test ledger behavior with a revenue reconciliation worked example.

        This test creates an Order and a CreditCardTransaction and, using the
        four Ledgers created in setUp, it makes all of the ledger entries that
        an Order and Transaction would be expected to have.  There are three,
        specifically: Revenue Recognition (credit: Revenue, debit:A/R), recording
        incoming cash (credit: A/R, debit: Cash (unreconciled)) and Reconciliation
        (credit: Cash (reconciled), debit: Cash (unreconciled)).

        In table form:

        Event                   | Accounts Receivable (unreconciled) | Revenue | Cash (unreconciled) | Cash (reconciled) | Evidence Models
        ----------------------- | ---------------------------------- | ------- | ------------------- | ----------------- | --------------------------------------------------------------
        Test is complete        | -$500                              | +$500   |                     |                   | `Order`
        Patient pays            | +$500                              |         | -$500               |                   | `CreditCardTransaction`
        Payments are reconciled |                                    |         | +$500               | -$500             | both `Order` and `CreditCardTransaction`
        """  # noqa: E501
        order = OrderFactory()
        credit_card_transaction = CreditCardTransactionFactory()

        # Assert that this Order looks "unrecognized".
        self.assertEqual(
            get_balances_for_object(order),
            {},
        )

        # Add an entry debiting AR and crediting Revenue: this entry should
        # reference the Order.
        create_transaction(
            self.user,
            evidence=[order],
            ledger_entries=[
                LedgerEntry(
                    ledger=self.revenue,
                    amount=credit(self.AMOUNT)),
                LedgerEntry(
                    ledger=self.accounts_receivable,
                    amount=debit(self.AMOUNT)),
            ],
        )

        # Assert that the correct entries were created.
        self.assertEqual(LedgerEntry.objects.count(), 2)
        self.assertEqual(Transaction.objects.count(), 1)

        # Assert that this Order looks "recognized".
        self.assertEqual(
            get_balances_for_object(order),
            {
                self.revenue: -self.AMOUNT,
                self.accounts_receivable: self.AMOUNT,
            },
        )

        # Add an entry crediting "A/R" and debiting "Cash (unreconciled)": this
        # entry should reference the CreditCardTransaction.
        create_transaction(
            self.user,
            evidence=[credit_card_transaction],
            ledger_entries=[
                LedgerEntry(
                    ledger=self.accounts_receivable,
                    amount=credit(self.AMOUNT)),
                LedgerEntry(
                    ledger=self.cash_unrecon,
                    amount=debit(self.AMOUNT))
            ],
        )

        # Assert that the correct entries were created
        self.assertEqual(LedgerEntry.objects.count(), 4)
        self.assertEqual(Transaction.objects.count(), 2)

        # Assert the CreditCardTransaction is in "Cash (unreconciled)".
        self.assertEqual(
            get_balances_for_object(credit_card_transaction),
            {
                self.accounts_receivable: -self.AMOUNT,
                self.cash_unrecon: self.AMOUNT,
            },
        )

        # Add an entry crediting "Cash (Unreconciled)" and debiting "Cash
        # (Reconciled)": this entry should reference both an Order and
        # a CreditCardTransaction.
        create_transaction(
            self.user,
            evidence=[order, credit_card_transaction],
            ledger_entries=[
                LedgerEntry(
                    ledger=self.cash_unrecon,
                    amount=credit(self.AMOUNT)),
                LedgerEntry(
                    ledger=self.cash_recon,
                    amount=debit(self.AMOUNT))
            ],
            type=self.recon_ttype,
        )

        # Assert that the correct entries were created.
        self.assertEqual(LedgerEntry.objects.count(), 6)
        self.assertEqual(Transaction.objects.count(), 3)

        # Assert that revenue is recognized and reconciled.
        self.assertEqual(
            get_balances_for_object(order),
            {
                self.accounts_receivable: self.AMOUNT,
                self.cash_unrecon: -self.AMOUNT,
                self.cash_recon: self.AMOUNT,
                self.revenue: -self.AMOUNT,
            },
        )