diff --git a/shop/models.py b/shop/models.py
index 7e8c792c..87a22985 100644
--- a/shop/models.py
+++ b/shop/models.py
@@ -285,6 +285,9 @@ class EpayPayment(CreatedUpdatedModel, UUIDModel):
class CreditNote(CreatedUpdatedModel):
+ class Meta:
+ ordering = ['-created']
+
amount = models.DecimalField(max_digits=10, decimal_places=2)
text = models.TextField()
pdf = models.FileField(
@@ -300,7 +303,7 @@ class CreditNote(CreatedUpdatedModel):
)
paid = models.BooleanField(
verbose_name=_('Paid?'),
- help_text=_('Whether this creditnote has been paid.'),
+ help_text=_('Whether the amount in this creditnote has been paid back to the customer.'),
default=False,
)
sent_to_customer = models.BooleanField(default=False)
diff --git a/shop/templates/creditnote_list.html b/shop/templates/creditnote_list.html
new file mode 100644
index 00000000..b93497a7
--- /dev/null
+++ b/shop/templates/creditnote_list.html
@@ -0,0 +1,36 @@
+{% extends 'shop_base.html' %}
+{% load bootstrap3 %}
+{% load shop_tags %}
+
+{% block shop_content %}
+
Credit Notes
+
+
+
+ Credit Note ID |
+ Text |
+ Amount |
+ Paid? |
+ PDF |
+
+
+
+{% for creditnote in creditnotes %}
+
+ {{ creditnote.id }} |
+ {{ creditnote.text }} |
+ {{ creditnote.amount|currency }} |
+ {{ creditnote.paid|truefalseicon }} |
+
+ {% if creditnote.pdf %}
+ {% url 'shop:download_creditnote' pk=creditnote.pk as creditnote_download_url %}
+ {% bootstrap_button "PDF" icon="save-file" href=creditnote_download_url button_class="btn-primary btn-xs" %}
+ {% else %}
+ N/A
+ {% endif %}
+ |
+
+{% endfor %}
+
+
+{% endblock %}
diff --git a/shop/templates/shop_base.html b/shop/templates/shop_base.html
index 77b63a87..e3550ec5 100644
--- a/shop/templates/shop_base.html
+++ b/shop/templates/shop_base.html
@@ -7,16 +7,19 @@
- Shop
{% if product %}
- - {{ product.category }}
- - {{ product.name }}
+ - {{ product.category }}
+ - {{ product.name }}
{% elif current_category %}
- - {{ current_category }}
+ - {{ current_category }}
{% endif %}
{% if user.is_authenticated and user.orders.exists %}
- {% if has_tickets %}
- - Tickets
- {% endif %}
- - Orders
+ {% if has_tickets %}
+ - Tickets
+ {% endif %}
+ - Orders
+ {% if user.creditnotes.exists %}
+ - Credit Notes
+ {% endif %}
{% endif %}
diff --git a/shop/urls.py b/shop/urls.py
index 3e6892c0..5dc7d29b 100644
--- a/shop/urls.py
+++ b/shop/urls.py
@@ -23,4 +23,7 @@ urlpatterns = [
url(r'tickets/$', TicketListView.as_view(), name='ticket_list'),
url(r'tickets/(?P\b[0-9A-Fa-f]{8}\b(-\b[0-9A-Fa-f]{4}\b){3}-\b[0-9A-Fa-f]{12}\b)$', TicketDetailView.as_view(), name='ticket_detail'),
+
+ url(r'creditnotes/$', CreditNoteListView.as_view(), name='creditnote_list'),
+ url(r'creditnotes/(?P[0-9]+)/pdf/$', DownloadCreditNoteView.as_view(), name='download_creditnote'),
]
diff --git a/shop/views.py b/shop/views.py
index 2891cab6..178b971b 100644
--- a/shop/views.py
+++ b/shop/views.py
@@ -40,11 +40,38 @@ from vendor.coinify_callback import CoinifyCallback
import json, time
+class EnsureCreditNoteHasPDFMixin(SingleObjectMixin):
+ model = CreditNote
+
+ def dispatch(self, request, *args, **kwargs):
+ if not self.get_object().pdf:
+ messages.error(request, "This creditnote has no PDF yet!")
+ return HttpResponseRedirect(reverse_lazy('shop:creditnote_list'))
+
+ return super(EnsureCreditNoteHasPDFMixin, self).dispatch(
+ request, *args, **kwargs
+ )
+
+
+class EnsureUserOwnsCreditNoteMixin(SingleObjectMixin):
+ model = CreditNote
+
+ def dispatch(self, request, *args, **kwargs):
+ # If the user does not own this creditnote OR is not staff
+ if not request.user.is_staff:
+ if self.get_object().user != request.user:
+ raise Http404("CreditNote not found")
+
+ return super(EnsureUserOwnsCreditNoteMixin, self).dispatch(
+ request, *args, **kwargs
+ )
+
+
class EnsureUserOwnsOrderMixin(SingleObjectMixin):
model = Order
def dispatch(self, request, *args, **kwargs):
- # If the user does not own this ticket OR is not staff
+ # If the user does not own this order OR is not staff
if not request.user.is_staff:
if self.get_object().user != request.user:
raise Http404("Order not found")
@@ -319,6 +346,24 @@ class DownloadInvoiceView(LoginRequiredMixin, EnsureUserOwnsOrderMixin, EnsurePa
response.write(self.get_object().invoice.pdf.read())
return response
+
+
+class CreditNoteListView(LoginRequiredMixin, ListView):
+ model = CreditNote
+ template_name = "creditnote_list.html"
+ context_object_name = 'creditnotes'
+
+
+class DownloadCreditNoteView(LoginRequiredMixin, EnsureUserOwnsCreditNoteMixin, EnsureCreditNoteHasPDFMixin, SingleObjectMixin, View):
+ model = CreditNote
+
+ def get(self, request, *args, **kwargs):
+ response = HttpResponse(content_type='application/pdf')
+ response['Content-Disposition'] = 'attachment; filename="%s"' % self.get_object().filename
+ response.write(self.get_object().invoice.pdf.read())
+ return response
+
+
#################################################################################
class EpayFormView(LoginRequiredMixin, EnsureUserOwnsOrderMixin, EnsureUnpaidOrderMixin, EnsureClosedOrderMixin, EnsureOrderHasProductsMixin, DetailView):