def test_add(self): t1 = Total(100, 'USD', 100, 'EUR') t2 = Total(80, 'USD', 150, 'GBP') t = t1 + t2 assert t['USD'].amount == 180 assert t['EUR'].amount == 100 assert t['GBP'].amount == 150
def test_sub(self): t1 = Total(100, 'USD', 100, 'EUR') t2 = Total(80, 'USD', 150, 'GBP') t = t1 - t2 assert t['USD'].amount == 20 assert t['EUR'].amount == 100 assert t['GBP'].amount == -150
def test_it_pays_invoices_in_different_currencies(self): invoice2 = Invoice.objects.create(account_id=self.account.id, due_date=date.today()) Charge.objects.create(account=self.account, invoice=invoice2, amount=Money(5, 'EUR'), product_code='BCHARGE') Transaction.objects.create(account=self.account, amount=Money(5, 'EUR'), success=True) Transaction.objects.create(account=self.account, amount=Money(10, 'CHF'), success=True) paid_invoice_ids = accounts.assign_funds_to_account_pending_invoices(account_id=self.account.id) assert paid_invoice_ids == [self.invoice1.pk, invoice2.pk] self.invoice1.refresh_from_db() assert_attrs(self.invoice1, {'status': Invoice.PAID, 'items': [ {'amount': Money(40, 'CHF'), 'product_code': 'ACHARGE'}, ], 'transactions': [ {'amount': Money(30, 'CHF')}, {'amount': Money(10, 'CHF')}, ]}) assert self.invoice1.due() == Total(Money(0, 'CHF')) invoice2.refresh_from_db() assert_attrs(invoice2, {'status': Invoice.PAID, 'items': [ {'amount': Money(5, 'EUR'), 'product_code': 'BCHARGE'}, ], 'transactions': [ {'amount': Money(5, 'EUR')}, ]}) assert invoice2.due() == Total(Money(0, 'EUR'))
def test_sub_rev(self): t1 = Total(100, 'USD', 100, 'EUR') t2 = Total(80, 'USD', 150, 'GBP') t = t2 - t1 assert t['USD'].amount == -20 assert t['EUR'].amount == -100 assert t['GBP'].amount == 150
def test_total_charges_should_select_just_the_right_charges(self): invoice = Invoice.objects.create(account=self.account, due_date=date.today()) Charge.objects.create(account=self.account, invoice=invoice, amount=Money(8, 'CHF'), product_code='ACHARGE') Charge.objects.create(account=self.account, invoice=invoice, amount=Money(2, 'CHF'), product_code='BCHARGE') Charge.objects.create(account=self.account, invoice=invoice, amount=Money(-1, 'CHF'), product_code='ACREDIT') Charge.objects.create(account=self.account, invoice=invoice, amount=Money(6, 'CHF'), product_code=CARRIED_FORWARD) Transaction.objects.create(account=self.account, invoice=invoice, amount=Money(15, 'CHF'), success=True) with self.assertNumQueries(1): assert invoice.total_charges() == Total(10, 'CHF') # Just to demonstrate that the due amount is completely different: assert invoice.due() == Total(0, 'CHF')
def test_full_scenario(self): # 1- At first the invoice is partially paid. paid_invoice_ids = accounts.assign_funds_to_account_pending_invoices(account_id=self.account.id) assert paid_invoice_ids == [] self.invoice1.refresh_from_db() assert_attrs(self.invoice1, {'status': Invoice.PENDING, 'items': [ {'amount': Money(40, 'CHF'), 'product_code': 'ACHARGE'}, ], 'transactions': [ {'amount': Money(30, 'CHF')}, ]}) # 2- A payment is made with more than enough money to pay the invoice. transaction2 = Transaction.objects.create(account=self.account, amount=Money(28, 'CHF'), success=True) paid_invoice_ids = accounts.assign_funds_to_account_pending_invoices(account_id=self.account.id) assert paid_invoice_ids == [self.invoice1.pk] transaction2.refresh_from_db() assert transaction2.invoice == self.invoice1 self.invoice1.refresh_from_db() assert_attrs(self.invoice1, {'status': Invoice.PAID, 'items': [ {'amount': Money(40, 'CHF'), 'product_code': 'ACHARGE'}, {'amount': Money(18, 'CHF'), 'product_code': CARRIED_FORWARD}, ], 'transactions': [ {'amount': Money(30, 'CHF')}, {'amount': Money(28, 'CHF')}, ]}) assert self.invoice1.due() == Total(Money(0, 'CHF')) uninvoiced_charges = Charge.objects.uninvoiced(account_id=self.account.id) assert len(uninvoiced_charges) == 1 uninvoiced_charge = uninvoiced_charges[0] assert_attrs(uninvoiced_charge, {'amount': Money(-18, 'CHF'), 'product_code': CREDIT_REMAINING}) # 3- A second charge is added to the account. invoice2 = Invoice.objects.create(account_id=self.account.id, due_date=date.today()) Charge.objects.create(account=self.account, invoice=invoice2, amount=Money(12, 'CHF'), product_code='BCHARGE') paid_invoice_ids = accounts.assign_funds_to_account_pending_invoices(account_id=self.account.id) assert paid_invoice_ids == [invoice2.pk] invoice2.refresh_from_db() assert_attrs(invoice2, {'status': Invoice.PAID, 'items': [ {'amount': Money(-18, 'CHF'), 'product_code': CREDIT_REMAINING}, {'amount': Money(12, 'CHF'), 'product_code': 'BCHARGE'}, {'amount': Money(6, 'CHF'), 'product_code': CARRIED_FORWARD}, ]}) assert invoice2.due() == Total(Money(0, 'CHF')) uninvoiced_charges = Charge.objects.uninvoiced(account_id=self.account.id) assert len(uninvoiced_charges) == 1 uninvoiced_charge = uninvoiced_charges[0] assert_attrs(uninvoiced_charge, {'amount': Money(-6, 'CHF'), 'product_code': CREDIT_REMAINING})
def test_it_should_create_an_invoice_even_when_enough_credit(self): Charge.objects.create(account=self.account, amount=Money(10, 'CHF'), product_code='ACHARGE') Charge.objects.create(account=self.account, amount=Money(-30, 'CHF'), product_code='ACREDIT') invoices = accounts.create_invoices(account_id=self.account.pk, due_date=date.today()) assert len(invoices) == 1 invoice = invoices[0] assert invoice.total_charges() == Total(10, 'CHF') assert invoice.due() == Total(10, 'CHF') assert invoice.items.count() == 1
def test_uninvoiced_should_ignore_invoiced_charges(self): Invoice.objects.create(id=1, account=self.account, due_date=date.today()) Charge.objects.create(account=self.account, invoice_id=1, amount=Money(10, 'CHF'), product_code='ACHARGE') with self.assertNumQueries(2): uc = Charge.objects.uninvoiced(account_id=self.account.pk) assert len(uc) == 0 assert total_amount(uc) == Total()
def test_uninvoiced_should_consider_credits(self): Charge.objects.create(account=self.account, amount=Money(10, 'CHF'), product_code='ACHARGE') Charge.objects.create(account=self.account, amount=Money(-30, 'CHF'), product_code='ACREDIT') with self.assertNumQueries(2): uc = Charge.objects.uninvoiced(account_id=self.account.pk) assert len(uc) == 2 assert total_amount(uc) == Total(-20, 'CHF')
def test_uninvoiced_can_be_in_multiple_currencies(self): Charge.objects.create(account=self.account, amount=Money(10, 'CHF'), product_code='ACHARGE') Charge.objects.create(account=self.account, amount=Money(-30, 'EUR'), product_code='ACREDIT') with self.assertNumQueries(2): uc = Charge.objects.uninvoiced(account_id=self.account.pk) assert len(uc) == 2 assert total_amount(uc) == Total(10, 'CHF', -30, 'EUR')
def test_uninvoiced_should_ignore_deleted_charges(self): Charge.objects.create(account=self.account, amount=Money(10, 'CHF'), product_code='ACHARGE') Charge.objects.create(account=self.account, deleted=True, amount=Money(5, 'CHF'), product_code='BCHARGE') with self.assertNumQueries(2): uc = Charge.objects.uninvoiced(account_id=self.account.pk) assert len(uc) == 1 assert total_amount(uc) == Total(10, 'CHF')
def test_it_should_compute_the_invoice_due_ignoring_deleted_charges(self): invoice = Invoice.objects.create(account=self.account, due_date=date.today()) Charge.objects.create(account=self.account, invoice=invoice, amount=Money(10, 'CHF'), product_code='ACHARGE') Charge.objects.create(account=self.account, invoice=invoice, amount=Money(-3, 'CHF'), product_code='ACREDIT') Charge.objects.create(account=self.account, invoice=invoice, amount=Money(1000, 'CHF'), product_code='ACHARGE', deleted=True) with self.assertNumQueries(2): assert invoice.due() == Total(7, 'CHF')
def test_zero_and_nonzero_values(self): t = Total(100, 'USD', 0, 'EUR') assert TotalSerializer(t).data == [ {'amount': '100.00', 'amount_currency': 'USD'} ] assert TotalIncludingZeroSerializer(t).data == [ {'amount': '100.00', 'amount_currency': 'USD'}, {'amount': '0.00', 'amount_currency': 'EUR'} ]
def test_it_should_pay_invoice_with_already_assigned_payment(self): invoice = Invoice.objects.create(account_id=self.account.id, due_date=date.today()) Transaction.objects.create(account=self.account, invoice=invoice, amount=Money(40, 'CHF'), success=True) Charge.objects.create(account=self.account, invoice=invoice, amount=Money(40, 'CHF'), product_code='ACHARGE') with self.assertNumQueries(4): paid = accounts.assign_funds_to_invoice(invoice_id=invoice.pk) assert paid assert invoice.due() == Total([Money(0, 'CHF')])
def test_unsuccessful_transactions_should_not_impact_the_balance(self): account = Account.objects.create(owner=self.user, currency='CHF') Charge.objects.create(account=account, amount=Money(10, 'CHF'), product_code='ACHARGE') psp_payment = MyPSPPayment(payment_ref='apaymentref') Transaction.objects.create(account=account, amount=Money(6, 'CHF'), success=False, payment_method='VIS', credit_card_number='4111 1111 1111 1111', psp_object=psp_payment) with self.assertNumQueries(2): assert account.balance() == Total(-10, 'CHF')
def test_balance_as_of_date_should_ignore_more_recent_charges(self): account = Account.objects.create(owner=self.user, currency='CHF') old_charge = Charge.objects.create(account=account, amount=Money(5, 'CHF'), product_code='OLD') # It's not possible to prevent auto-add-now from setting the current time, so we do 2 steps old_charge.created = parse_datetime('2015-01-01T00:00:00Z') old_charge.save() Charge.objects.create(account=account, amount=Money(10, 'CHF'), product_code='TODAY') with self.assertNumQueries(2): assert account.balance(as_of=parse_datetime('2016-06-06T00:00:00Z')) == Total([Money(-5, 'CHF')])
def test_it_should_create_an_invoice_when_money_is_due(self): Charge.objects.create(account=self.account, amount=Money(10, 'CHF'), product_code='ACHARGE') Charge.objects.create(account=self.account, amount=Money(-3, 'CHF'), product_code='ACREDIT') invoices = accounts.create_invoices(account_id=self.account.pk, due_date=date.today()) assert len(invoices) == 1 invoice = invoices[0] assert invoice.total_charges() == Total(10, 'CHF') assert invoice.due() == Total(10, 'CHF') assert invoice.items.count() == 1 # Verify there is nothing left to invoice on this account assert not accounts.create_invoices(account_id=self.account.pk, due_date=date.today())
def test_it_should_compute_the_account_balance_in_multiple_currencies( self): account = Account.objects.create(owner=self.user, currency='CHF') Charge.objects.create(account=account, amount=Money(10, 'CHF'), product_code='ACHARGE') Charge.objects.create(account=account, amount=Money(-3, 'EUR'), product_code='ACREDIT') with self.assertNumQueries(2): assert account.balance() == Total(-10, 'CHF', 3, 'EUR')
def test_it_should_assign_funds_even_if_not_enough_to_pay_invoice_fully(self): invoice = Invoice.objects.create(account_id=self.account.id, due_date=date.today()) Charge.objects.create(account=self.account, invoice=invoice, amount=Money(40, 'CHF'), product_code='ACHARGE') transaction = Transaction.objects.create(account=self.account, amount=Money(31, 'CHF'), success=True) with self.assertNumQueries(6): paid = accounts.assign_funds_to_invoice(invoice_id=invoice.pk) assert not paid transaction.refresh_from_db() assert transaction.invoice == invoice invoice.refresh_from_db() assert invoice.status == Invoice.PENDING assert invoice.due() == Total([Money(9, 'CHF')])
def test_it_should_assign_credit_to_invoice_and_pay_it(self): invoice = Invoice.objects.create(account_id=self.account.id, due_date=date.today()) Charge.objects.create(account=self.account, invoice=invoice, amount=Money(40, 'CHF'), product_code='ACHARGE') credit = Charge.objects.create(account=self.account, amount=Money(-40, 'CHF')) with self.assertNumQueries(7): paid = accounts.assign_funds_to_invoice(invoice_id=invoice.pk) assert paid credit.refresh_from_db() assert credit.invoice == invoice invoice.refresh_from_db() assert invoice.status == Invoice.PAID assert invoice.due() == Total([Money(0, 'CHF')])
def test_it_should_compute_the_invoice_due_in_multiple_currencies(self): invoice = Invoice.objects.create(account=self.account, due_date=date.today()) Charge.objects.create(account=self.account, invoice=invoice, amount=Money(10, 'CHF'), product_code='ACHARGE') Charge.objects.create(account=self.account, invoice=invoice, amount=Money(-3, 'EUR'), product_code='ACREDIT') with self.assertNumQueries(2): assert invoice.due() == Total(10, 'CHF', -3, 'EUR')
def test_it_should_compute_the_invoice_due_when_overpayment(self): invoice = Invoice.objects.create(account=self.account, due_date=date.today()) Charge.objects.create(account=self.account, invoice=invoice, amount=Money(10, 'CHF'), product_code='ACHARGE') Transaction.objects.create(account=self.account, invoice=invoice, amount=Money(15, 'CHF'), success=True) with self.assertNumQueries(2): assert invoice.due() == Total(-5, 'CHF')
def test_it_should_assign_multiple_payments_to_invoice_and_pay_it(self): invoice = Invoice.objects.create(account_id=self.account.id, due_date=date.today()) Charge.objects.create(account=self.account, invoice=invoice, amount=Money(40, 'CHF'), product_code='ACHARGE') transaction_1 = Transaction.objects.create(account=self.account, amount=Money(15, 'CHF'), success=True) transaction_2 = Transaction.objects.create(account=self.account, amount=Money(25, 'CHF'), success=True) with self.assertNumQueries(8): paid = accounts.assign_funds_to_invoice(invoice_id=invoice.pk) assert paid transaction_1.refresh_from_db() assert transaction_1.invoice == invoice transaction_2.refresh_from_db() assert transaction_2.invoice == invoice invoice.refresh_from_db() assert invoice.status == Invoice.PAID assert invoice.due() == Total([Money(0, 'CHF')])
def test_abs(self): total_neg = Total(-10, 'USD', 20, 'GBP') t = abs(total_neg) assert t['USD'].amount == 10 assert t['GBP'].amount == 20 assert t['EUR'].amount == 0
def test_neq(self): assert not (Total() != Total()) assert not (Total(0, 'USD') != Total()) t1 = Total(100, 'USD', 100, 'EUR') t2 = Total(80, 'USD', 150, 'GBP') assert not (t1 != +t1) assert t1 != t2 assert not (Total([Money(100, 'USD')]) != Total([Money(100, 'USD')])) assert not (Total([Money(100, 'USD'), Money(0, 'EUR')]) != Total([Money(100, 'USD')])) assert Total([Money(100, 'USD'), Money(10, 'EUR')]) != Total([Money(100, 'USD')])
def test_unique_currency(self): with raises(ValueError): Total([Money(0, 'USD'), Money(0, 'USD')])
def test_eq_zero(self): assert Total() == 0 assert Total(0, 'USD') == 0 assert Total(0, 'USD', 0, 'CHF') == 0 assert not (Total(100, 'USD', 100, 'EUR') == 0)
def test_eq(self): assert Total() == Total() assert Total(0, 'USD') == Total() t1 = Total(100, 'USD', 100, 'EUR') t2 = Total(80, 'USD', 150, 'GBP') assert t1 == +t1 assert not (t1 == t2) assert Total(100, 'USD') == Total(100, 'USD') assert Total(100, 'USD', 0, 'EUR') == Total(100, 'USD') assert not (Total(100, 'USD', 10, 'EUR') == Total(100, 'USD'))
def test_bool(self): assert not bool(Total()) assert not bool(Total(0, 'USD')) assert bool(Total(100, 'USD')) assert bool(Total(0, 'USD', 100, 'EUR')) assert not bool(Total(0, 'USD', 0, 'EUR'))
def test_pos(self): t1 = Total(100, 'USD', 100, 'EUR') t = +t1 assert t['USD'].amount == 100 assert t['EUR'].amount == 100 assert t['GBP'].amount == 0