def test_multiple_matches(self): """ Multiple matching transactions raises MultipleObjectsReturned. """ credit_card_transaction = CreditCardTransactionFactory() amount = Decimal('100') ledger = LedgerFactory() for _ in range(2): TransactionFactory( UserFactory(), ledger_entries=[ LedgerEntry(amount=debit(amount), ledger=ledger), LedgerEntry(amount=credit(amount), ledger=ledger), ], evidence=[credit_card_transaction], ) self.assertEqual(Transaction.objects.count(), 2) with self.assertRaises(Transaction.MultipleObjectsReturned): assert_transaction_in_ledgers_for_amounts_with_evidence( ledger_amount_pairs=[ (ledger.name, credit(amount)), (ledger.name, debit(amount)), ], evidence=[credit_card_transaction], )
def test_mismatch_on_evidence(self): """ An otherwise matching Trans. will fail if its evidence is different. """ credit_card_transaction = CreditCardTransactionFactory() amount = Decimal('100') ledger = LedgerFactory() TransactionFactory( UserFactory(), ledger_entries=[ LedgerEntry(amount=debit(amount), ledger=ledger), LedgerEntry(amount=credit(amount), ledger=ledger), ], evidence=[credit_card_transaction], ) ledger_amount_pairs = [ (ledger.name, credit(amount)), (ledger.name, debit(amount)), ] with self.assertRaises(Transaction.DoesNotExist): assert_transaction_in_ledgers_for_amounts_with_evidence( ledger_amount_pairs=ledger_amount_pairs, evidence=[ credit_card_transaction, CreditCardTransactionFactory()], ) with self.assertRaises(Transaction.DoesNotExist): assert_transaction_in_ledgers_for_amounts_with_evidence( ledger_amount_pairs=ledger_amount_pairs, evidence=[], )
def test_mismatch_on_ledger_entries(self): """ An otherwise matching Trans. will fail if its LedgerEntries mismatch. """ credit_card_transaction = CreditCardTransactionFactory() amount = Decimal('100') ledger = LedgerFactory() evidence = [credit_card_transaction] TransactionFactory( UserFactory(), ledger_entries=[ LedgerEntry(amount=debit(amount), ledger=ledger), LedgerEntry(amount=credit(amount), ledger=ledger), ], evidence=evidence, ) with self.assertRaises(Transaction.DoesNotExist): assert_transaction_in_ledgers_for_amounts_with_evidence( ledger_amount_pairs=[ (ledger.name + 'foo', credit(amount)), (ledger.name + 'foo', debit(amount)), ], evidence=evidence, ) with self.assertRaises(AssertionError): assert_transaction_in_ledgers_for_amounts_with_evidence( ledger_amount_pairs=[ (ledger.name, credit(amount + Decimal('1'))), (ledger.name, debit(amount + Decimal('1'))), ], evidence=evidence, )
def test_credit_and_debit_helper_functions(self): with mock.patch('capone.api.actions.settings') as mock_settings: mock_settings.DEBITS_ARE_NEGATIVE = True self.assertPositive(credit(self.AMOUNT)) self.assertNegative(debit(self.AMOUNT)) with mock.patch('capone.api.actions.settings') as mock_settings: mock_settings.DEBITS_ARE_NEGATIVE = False self.assertNegative(credit(self.AMOUNT)) self.assertPositive(debit(self.AMOUNT))
def test_transaction_fields(self): """ Test filtering by `posted_timestamp`, `notes`, `type`, and `user`. """ time = datetime.now() wrong_time = datetime.now() - timedelta(days=1) user1 = UserFactory() user2 = UserFactory() credit_card_transaction = CreditCardTransactionFactory() ttype1 = TransactionTypeFactory(name='1') ttype2 = TransactionTypeFactory(name='2') FIELDS_TO_VALUES = [ ('posted_timestamp', time, wrong_time), ('notes', 'foo', 'bar'), ('type', ttype1, ttype2), ('user', user1, user2), ] for field_name, right_value, wrong_value in FIELDS_TO_VALUES: TransactionFactory( evidence=[credit_card_transaction], **{field_name: right_value}) ledger = Ledger.objects.last() assert_transaction_in_ledgers_for_amounts_with_evidence( ledger_amount_pairs=[ (ledger.name, credit(Decimal('100'))), (ledger.name, debit(Decimal('100'))), ], evidence=[credit_card_transaction], **{field_name: right_value} )
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 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], )
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)), ])
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)), ], )
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))), ], )
def test_no_args(self): TransactionFactory(evidence=[self.credit_card_transaction]) ledger = Ledger.objects.last() assert_transaction_in_ledgers_for_amounts_with_evidence( ledger_amount_pairs=[ (ledger.name, credit(Decimal('100'))), (ledger.name, debit(Decimal('100'))), ], evidence=[self.credit_card_transaction], )
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)), ], )
def test_custom_evidence(self): ccx = CreditCardTransactionFactory() TransactionFactory(evidence=[ccx]) ledger = Ledger.objects.last() assert_transaction_in_ledgers_for_amounts_with_evidence( ledger_amount_pairs=[ (ledger.name, credit(Decimal('100'))), (ledger.name, debit(Decimal('100'))), ], evidence=[ccx], )
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))
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)
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)
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)
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()), )
def test_ledger_balance_update(self): self.add_transaction([self.order_1]) self.assert_objects_have_ledger_balances( (self.order_1, self.ar_ledger, credit(self.amount)), (self.order_1, self.cash_ledger, debit(self.amount)), (self.order_2, self.ar_ledger, None), (self.order_2, self.cash_ledger, None), ) self.add_transaction([self.order_2]) self.assert_objects_have_ledger_balances( (self.order_1, self.ar_ledger, credit(self.amount)), (self.order_1, self.cash_ledger, debit(self.amount)), (self.order_2, self.ar_ledger, credit(self.amount)), (self.order_2, self.cash_ledger, debit(self.amount)), ) self.add_transaction([self.order_1]) self.assert_objects_have_ledger_balances( (self.order_1, self.ar_ledger, credit(self.amount) * 2), (self.order_1, self.cash_ledger, debit(self.amount) * 2), (self.order_2, self.ar_ledger, credit(self.amount)), (self.order_2, self.cash_ledger, debit(self.amount)), ) transaction = self.add_transaction([self.order_1, self.order_2]) self.assert_objects_have_ledger_balances( (self.order_1, self.ar_ledger, credit(self.amount) * 3), (self.order_1, self.cash_ledger, debit(self.amount) * 3), (self.order_2, self.ar_ledger, credit(self.amount) * 2), (self.order_2, self.cash_ledger, debit(self.amount) * 2), ) void_transaction(transaction, self.user) self.assert_objects_have_ledger_balances( (self.order_1, self.ar_ledger, credit(self.amount) * 2), (self.order_1, self.cash_ledger, debit(self.amount) * 2), (self.order_2, self.ar_ledger, credit(self.amount)), (self.order_2, self.cash_ledger, debit(self.amount)), )
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)
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, ], }, )
def test_no_matches(self): """ No matching transaction raises DoesNotExist. """ TransactionFactory() credit_card_transaction = CreditCardTransactionFactory() ledger = Ledger.objects.last() self.assertTrue(Transaction.objects.exists()) with self.assertRaises(Transaction.DoesNotExist): assert_transaction_in_ledgers_for_amounts_with_evidence( ledger_amount_pairs=[ (ledger.name, credit(Decimal('100'))), (ledger.name, debit(Decimal('100'))), ], evidence=[credit_card_transaction], )
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, )
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)
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)
def test_custom_fields(self): """ Test setting fields `posted_timestamp`, `notes`, `type`, and `user`. """ time = datetime.now() FIELDS_TO_VALUES = [ ('posted_timestamp', time), ('notes', 'booga'), ('type', TransactionTypeFactory()), ('user', UserFactory()), ] for field_name, value in FIELDS_TO_VALUES: TransactionFactory(evidence=[self.credit_card_transaction], **{field_name: value}) ledger = Ledger.objects.last() assert_transaction_in_ledgers_for_amounts_with_evidence( ledger_amount_pairs=[ (ledger.name, credit(Decimal('100'))), (ledger.name, debit(Decimal('100'))), ], evidence=[self.credit_card_transaction], **{field_name: value})
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, }, )
def test_rebuild_ledger_balance(self): rebuild_ledger_balances() self.assert_objects_have_ledger_balances( (self.order_1, self.ar_ledger, None), (self.order_1, self.cash_ledger, None), (self.order_2, self.ar_ledger, None), (self.order_2, self.cash_ledger, None), ) self.add_transaction([self.order_1]) rebuild_ledger_balances() self.assert_objects_have_ledger_balances( (self.order_1, self.ar_ledger, credit(self.amount)), (self.order_1, self.cash_ledger, debit(self.amount)), (self.order_2, self.ar_ledger, None), (self.order_2, self.cash_ledger, None), ) self.add_transaction([self.order_2]) rebuild_ledger_balances() self.assert_objects_have_ledger_balances( (self.order_1, self.ar_ledger, credit(self.amount)), (self.order_1, self.cash_ledger, debit(self.amount)), (self.order_2, self.ar_ledger, credit(self.amount)), (self.order_2, self.cash_ledger, debit(self.amount)), ) self.add_transaction([self.order_1]) rebuild_ledger_balances() self.assert_objects_have_ledger_balances( (self.order_1, self.ar_ledger, credit(self.amount) * 2), (self.order_1, self.cash_ledger, debit(self.amount) * 2), (self.order_2, self.ar_ledger, credit(self.amount)), (self.order_2, self.cash_ledger, debit(self.amount)), ) transaction = self.add_transaction([self.order_1, self.order_2]) self.assert_objects_have_ledger_balances( (self.order_1, self.ar_ledger, credit(self.amount) * 3), (self.order_1, self.cash_ledger, debit(self.amount) * 3), (self.order_2, self.ar_ledger, credit(self.amount) * 2), (self.order_2, self.cash_ledger, debit(self.amount) * 2), ) void_transaction(transaction, self.user) rebuild_ledger_balances() self.assert_objects_have_ledger_balances( (self.order_1, self.ar_ledger, credit(self.amount) * 2), (self.order_1, self.cash_ledger, debit(self.amount) * 2), (self.order_2, self.ar_ledger, credit(self.amount)), (self.order_2, self.cash_ledger, debit(self.amount)), ) LedgerBalance.objects.update(balance=Decimal('1.00')) LedgerBalance.objects.first().delete() rebuild_ledger_balances() self.assert_objects_have_ledger_balances( (self.order_1, self.ar_ledger, credit(self.amount) * 2), (self.order_1, self.cash_ledger, debit(self.amount) * 2), (self.order_2, self.ar_ledger, credit(self.amount)), (self.order_2, self.cash_ledger, debit(self.amount)), )