diff --git a/registrasion/controllers/credit_note.py b/registrasion/controllers/credit_note.py index 182c10e9..e4784c8b 100644 --- a/registrasion/controllers/credit_note.py +++ b/registrasion/controllers/credit_note.py @@ -1,3 +1,5 @@ +import datetime + from django.db import transaction from registrasion.models import commerce @@ -53,3 +55,27 @@ class CreditNoteController(ForId, object): inv.update_status() # TODO: Add administration fee generator. + @transaction.atomic + def cancellation_fee(self, percentage): + ''' Generates an invoice with a cancellation fee, and applies + credit to the invoice. + + percentage (Decimal): The percentage of the credit note to turn into + a cancellation fee. Must be 0 <= percentage <= 100. + ''' + + from invoice import InvoiceController # Circular imports bleh. + + assert(percentage >= 0 and percentage <= 100) + + cancellation_fee = self.credit_note.value * percentage / 100 + due = datetime.timedelta(days=1) + item = [("Cancellation fee", cancellation_fee)] + invoice = InvoiceController.manual_invoice( + self.credit_note.invoice.user, due, item + ) + + if not invoice.is_paid: + self.apply_to_invoice(invoice) + + return InvoiceController(invoice) diff --git a/registrasion/forms.py b/registrasion/forms.py index cc555b78..4839b733 100644 --- a/registrasion/forms.py +++ b/registrasion/forms.py @@ -3,6 +3,7 @@ from registrasion.models import commerce from registrasion.models import inventory from django import forms +from django.core.exceptions import ValidationError class ApplyCreditNoteForm(forms.Form): @@ -31,6 +32,14 @@ class ApplyCreditNoteForm(forms.Form): ) +class CancellationFeeForm(forms.Form): + + percentage = forms.DecimalField( + required=True, + min_value=0, + max_value=100, + ) + class ManualCreditNoteRefundForm(forms.ModelForm): class Meta: diff --git a/registrasion/tests/test_credit_note.py b/registrasion/tests/test_credit_note.py index 2857ce7f..c4be1041 100644 --- a/registrasion/tests/test_credit_note.py +++ b/registrasion/tests/test_credit_note.py @@ -440,3 +440,28 @@ class CreditNoteTestCase(TestHelperMixin, RegistrationCartTestCase): # Generate invoice that should be automatically paid invoice2 = self._manual_invoice(1) self.assertTrue(invoice2.invoice.is_paid) + + def test_cancellation_fee_is_applied(self): + + invoice1 = self._manual_invoice(1) + invoice1.pay("Pay", invoice1.invoice.value) + invoice1.refund() + + percentage = 15 + + cn = self._credit_note_for_invoice(invoice1.invoice) + canc = cn.cancellation_fee(15) + + # Cancellation fee exceeds the amount for the invoice. + self.assertTrue(canc.invoice.is_paid) + + # Cancellation fee is equal to 15% of credit note's value + self.assertEqual( + canc.invoice.value, + cn.credit_note.value * percentage / 100 + ) + + def test_cancellation_fee_is_applied_when_another_invoice_is_unpaid(self): + + extra_invoice = self._manual_invoice(23) + self.test_cancellation_fee_is_applied() diff --git a/registrasion/views.py b/registrasion/views.py index f96104f1..9248dce4 100644 --- a/registrasion/views.py +++ b/registrasion/views.py @@ -765,6 +765,9 @@ def credit_note(request, note_id, access_code=None): # to an invoice. "refund_form": form, # A form for applying a *manual* # refund of the credit note. + "cancellation_fee_form" : form, # A form for generating an + # invoice with a + # cancellation fee } ''' @@ -783,6 +786,11 @@ def credit_note(request, note_id, access_code=None): prefix="refund_note" ) + cancellation_fee_form = forms.CancellationFeeForm( + request.POST or None, + prefix="cancellation_fee" + ) + if request.POST and apply_form.is_valid(): inv_id = apply_form.cleaned_data["invoice"] invoice = commerce.Invoice.objects.get(pk=inv_id) @@ -805,10 +813,20 @@ def credit_note(request, note_id, access_code=None): prefix="refund_note", ) + elif request.POST and cancellation_fee_form.is_valid(): + percentage = cancellation_fee_form.cleaned_data["percentage"] + invoice = current_note.cancellation_fee(percentage) + messages.success( + request, + "Generated cancellation fee for credit note %d." % note_id, + ) + return redirect("invoice", invoice.invoice.id) + data = { "credit_note": current_note.credit_note, "apply_form": apply_form, "refund_form": refund_form, + "cancellation_fee_form": cancellation_fee_form, } return render(request, "registrasion/credit_note.html", data)