From 12e04c248fb1969a0796a03e493e1a14569d18ec Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Mon, 25 Apr 2016 08:13:44 +1000 Subject: [PATCH] Credit notes are now generated when invoices are overpaid, or invoices are paid into void or refunded invoices. Closes #37. --- registrasion/controllers/invoice.py | 17 +++++-- registrasion/tests/controller_helpers.py | 7 ++- registrasion/tests/test_invoice.py | 58 +++++++++++++++++++----- 3 files changed, 64 insertions(+), 18 deletions(-) diff --git a/registrasion/controllers/invoice.py b/registrasion/controllers/invoice.py index d2b6bf3f..ef4ee320 100644 --- a/registrasion/controllers/invoice.py +++ b/registrasion/controllers/invoice.py @@ -13,6 +13,7 @@ from cart import CartController from credit_note import CreditNoteController from for_id import ForId + class InvoiceController(ForId, object): __MODEL__ = commerce.Invoice @@ -195,11 +196,6 @@ class InvoiceController(ForId, object): # Invoice no longer has amount owing self._mark_paid() - if remainder < 0: - CreditNoteController.generate_from_invoice( - self.invoice, - 0 - remainder, - ) elif total_paid == 0 and num_payments > 0: # Invoice has multiple payments totalling zero self._mark_void() @@ -215,6 +211,17 @@ class InvoiceController(ForId, object): # Should not ever change from here pass + # Generate credit notes from residual payments + residual = 0 + if self.invoice.is_paid: + if remainder < 0: + residual = 0 - remainder + elif self.invoice.is_void or self.invoice.is_refunded: + residual = total_paid + + if residual != 0: + CreditNoteController.generate_from_invoice(self.invoice, residual) + def _mark_paid(self): ''' Marks the invoice as paid, and updates the attached cart if necessary. ''' diff --git a/registrasion/tests/controller_helpers.py b/registrasion/tests/controller_helpers.py index ac676c03..3ede49c2 100644 --- a/registrasion/tests/controller_helpers.py +++ b/registrasion/tests/controller_helpers.py @@ -34,11 +34,14 @@ class TestingCartController(CartController): class TestingInvoiceController(InvoiceController): - def pay(self, reference, amount): + def pay(self, reference, amount, pre_validate=True): ''' Testing method for simulating an invoice paymenht by the given amount. ''' - self.validate_allowed_to_pay() + if pre_validate: + # Manual payments don't pre-validate; we should test that things + # still work if we do silly things. + self.validate_allowed_to_pay() ''' Adds a payment ''' commerce.ManualPayment.objects.create( diff --git a/registrasion/tests/test_invoice.py b/registrasion/tests/test_invoice.py index b64ae58c..a7db2849 100644 --- a/registrasion/tests/test_invoice.py +++ b/registrasion/tests/test_invoice.py @@ -24,6 +24,10 @@ class InvoiceTestCase(RegistrationCartTestCase): return TestingInvoiceController.for_cart(self.reget(cart.cart)) + def _credit_note_for_invoice(self, invoice): + note = commerce.CreditNote.objects.get(invoice=invoice) + return TestingCreditNoteController(note) + def test_create_invoice(self): current_cart = TestingCartController.for_user(self.USER_1) @@ -314,8 +318,7 @@ class InvoiceTestCase(RegistrationCartTestCase): invoice.refund() # There should be one credit note generated out of the invoice. - credit_note = commerce.CreditNote.objects.get(invoice=invoice.invoice) - cn = TestingCreditNoteController(credit_note) + cn = self._credit_note_for_invoice(invoice.invoice) # That credit note should be in the unclaimed pile self.assertEquals(1, commerce.CreditNote.unclaimed().count()) @@ -342,8 +345,7 @@ class InvoiceTestCase(RegistrationCartTestCase): invoice.refund() # There should be one credit note generated out of the invoice. - credit_note = commerce.CreditNote.objects.get(invoice=invoice.invoice) - cn = TestingCreditNoteController(credit_note) + cn = self._credit_note_for_invoice(invoice.invoice) self.assertEquals(1, commerce.CreditNote.unclaimed().count()) @@ -381,8 +383,7 @@ class InvoiceTestCase(RegistrationCartTestCase): invoice.refund() # There should be one credit note generated out of the invoice. - credit_note = commerce.CreditNote.objects.get(invoice=invoice.invoice) - cn = TestingCreditNoteController(credit_note) + cn = self._credit_note_for_invoice(invoice.invoice) # Create a new cart with invoice, pay it invoice_2 = self._invoice_containing_prod_1(1) @@ -415,9 +416,8 @@ class InvoiceTestCase(RegistrationCartTestCase): self.assertEquals(1, commerce.CreditNote.unclaimed().count()) - credit_note = commerce.CreditNote.objects.get(invoice=invoice.invoice) + cn = self._credit_note_for_invoice(invoice.invoice) - cn = TestingCreditNoteController(credit_note) cn.refund() # Refunding a credit note should mark it as claimed @@ -444,9 +444,7 @@ class InvoiceTestCase(RegistrationCartTestCase): self.assertEquals(1, commerce.CreditNote.unclaimed().count()) - credit_note = commerce.CreditNote.objects.get(invoice=invoice.invoice) - - cn = TestingCreditNoteController(credit_note) + cn = self._credit_note_for_invoice(invoice.invoice) # Create a new cart with invoice cart = TestingCartController.for_user(self.USER_1) @@ -460,3 +458,41 @@ class InvoiceTestCase(RegistrationCartTestCase): # Cannot refund this credit note as it is already applied. with self.assertRaises(ValidationError): cn.refund() + + def test_money_into_void_invoice_generates_credit_note(self): + invoice = self._invoice_containing_prod_1(1) + invoice.void() + + val = invoice.invoice.value + + invoice.pay("Paying into the void.", val, pre_validate=False) + cn = self._credit_note_for_invoice(invoice.invoice) + self.assertEqual(val, cn.credit_note.value) + + def test_money_into_refunded_invoice_generates_credit_note(self): + invoice = self._invoice_containing_prod_1(1) + + val = invoice.invoice.value + + invoice.pay("Paying the first time.", val) + invoice.refund() + + cnval = val - 1 + invoice.pay("Paying into the void.", cnval, pre_validate=False) + + notes = commerce.CreditNote.objects.filter(invoice=invoice.invoice) + notes = sorted(notes, key = lambda note: note.value) + + self.assertEqual(cnval, notes[0].value) + self.assertEqual(val, notes[1].value) + + def test_money_into_paid_invoice_generates_credit_note(self): + invoice = self._invoice_containing_prod_1(1) + + val = invoice.invoice.value + + invoice.pay("Paying the first time.", val) + + invoice.pay("Paying into the void.", val, pre_validate=False) + cn = self._credit_note_for_invoice(invoice.invoice) + self.assertEqual(val, cn.credit_note.value)