add proforma invoice support

This commit is contained in:
Thomas Steen Rasmussen 2019-07-09 10:38:14 +02:00
parent fef09baa3d
commit effd900b62
8 changed files with 194 additions and 30 deletions

View File

@ -14,8 +14,30 @@ def do_work():
The invoice worker creates Invoice objects for shop orders and
for custom orders. It also generates PDF files for Invoice objects
that have no PDF. It also emails invoices for shop orders.
It also generates proforma invoices for all closed orders.
"""
# check if we need to generate any proforma invoices for shop orders
for order in Order.objects.filter(pdf="", open__isnull=True):
# generate proforma invoice for this Order
pdffile = generate_pdf_letter(
filename=order.filename,
template="pdf/proforma_invoice.html",
formatdict={
"hostname": settings.ALLOWED_HOSTS[0],
"order": order,
"bank": settings.BANKACCOUNT_BANK,
"bank_iban": settings.BANKACCOUNT_IBAN,
"bank_bic": settings.BANKACCOUNT_SWIFTBIC,
"bank_dk_reg": settings.BANKACCOUNT_REG,
"bank_dk_accno": settings.BANKACCOUNT_ACCOUNT,
},
)
# update order object with the file
order.pdf.save(order.filename, File(pdffile))
order.save()
logger.info("Generated proforma invoice PDF for order %s" % order)
# check if we need to generate any invoices for shop orders
for order in Order.objects.filter(paid=True, invoice__isnull=True):
# generate invoice for this Order

View File

@ -0,0 +1,18 @@
# Generated by Django 2.1.7 on 2019-07-08 21:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('shop', '0057_order_notes'),
]
operations = [
migrations.AddField(
model_name='order',
name='pdf',
field=models.FileField(blank=True, null=True, upload_to='proforma_invoices/'),
),
]

View File

@ -128,6 +128,8 @@ class Order(CreatedUpdatedModel):
blank=True,
)
pdf = models.FileField(null=True, blank=True, upload_to="proforma_invoices/")
objects = OrderQuerySet.as_manager()
def __str__(self):
@ -334,6 +336,10 @@ class Order(CreatedUpdatedModel):
# nope
return False
@property
def filename(self):
return "bornhack_proforma_invoice_order_%s.pdf" % self.pk
class ProductCategory(CreatedUpdatedModel, UUIDModel):
class Meta:

View File

@ -84,15 +84,28 @@
{% endif %}
{% if order.open %}
{% bootstrap_button "Update order" button_type="submit" button_class="btn-primary" name="update_order" %}
{% endif %}
{% if not order.paid %}
{% bootstrap_button "Cancel order" button_type="submit" button_class="btn-danger" name="cancel_order" %}
{% endif %}
{% if not order.paid %}
{% bootstrap_button "Review and pay" button_type="submit" button_class="btn btn-success btn-lg pull-right" name="review_and_pay" %}
{% bootstrap_button "Update order" button_type="submit" button_class="btn-primary btn-lg" name="update_order" icon="edit" %}
{% endif %}
{% if not order.paid %}
{% bootstrap_button "Cancel order" button_type="submit" button_class="btn-danger btn-lg" name="cancel_order" icon="remove" %}
{% endif %}
{% if not order.paid %}
{% bootstrap_button "Review and pay" button_type="submit" button_class="btn btn-success btn-lg pull-right" name="review_and_pay" icon="check" %}
{% endif %}
{% if order.paid %}
{% if order.invoice.pdf %}
{% url 'shop:download_invoice' pk=order.pk as invoice_download_url %}
{% bootstrap_button "Invoice PDF" icon="save-file" href=invoice_download_url button_class="btn-primary btn-lg pull-right" %}
{% endif %}
{% else %}
{% if order.pdf %}
{% url 'shop:download_invoice' pk=order.pk as invoice_download_url %}
{% bootstrap_button "Proforma Invoice PDF" icon="save-file" href=invoice_download_url button_class="btn-primary btn-lg pull-right" %}
{% endif %}
{% endif %}
</div>
{% if order.paid %}
<div class="panel-footer">

View File

@ -33,11 +33,20 @@
<td class="text-center">{{ order.paid|truefalseicon }}</td>
<td class="text-center">{{ order.handed_out_status }}</td>
<td>
{% if order.invoice.pdf %}
{% url 'shop:download_invoice' pk=order.pk as invoice_download_url %}
{% bootstrap_button "PDF" icon="save-file" href=invoice_download_url button_class="btn-primary btn-xs" %}
{% if order.paid %}
{% if order.invoice.pdf %}
{% url 'shop:download_invoice' pk=order.pk as invoice_download_url %}
{% bootstrap_button "Invoice" icon="save-file" href=invoice_download_url button_class="btn-primary btn-xs" %}
{% else %}
Not generated yet!
{% endif %}
{% else %}
N/A
{% if order.pdf %}
{% url 'shop:download_invoice' pk=order.pk as invoice_download_url %}
{% bootstrap_button "Proforma Invoice" icon="save-file" href=invoice_download_url button_class="btn-primary btn-xs" %}
{% else %}
Not generated yet!
{% endif %}
{% endif %}
</td>
<td>

View File

@ -0,0 +1,95 @@
{% load static from staticfiles %}
{% load shop_tags %}
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<table style="width:100%;">
<tr>
<td style="width: 75%;">&nbsp;</td>
<td>
<h3>
Order Date: {{ order.created|date:"b jS, Y" }}<br>
Order #{{ order.pk }}<br>
Proforma Invoice
</h3>
</td>
</tr>
</table>
{% if order.invoice_address %}
<h2>CUSTOMER</h2>
<p class="lead">{{ order.invoice_address|linebreaks }}</p>
{% else %}
<h3>Customer: {{ order.user.email }}</h3>
{% endif %}
<br>
<h2>PROFORMA INVOICE</h2>
<table style="width:90%; margin:1em;">
<tr>
<td>
<b>Name
<td>
<b>Quantity
<td align="right">
<b>Price
<td align="right">
<b>Total
<tr><td style="height: 1px; line-height: 1px; border-top: 1pt solid black;" colspan="4">&nbsp;</td></tr>
{% for order_product in order.orderproductrelation_set.all %}
<tr>
<td>
{{ order_product.product.name }}
<td>
{{ order_product.quantity }}
<td align="right">
{{ order_product.product.price|currency }}
<td align="right">
{{ order_product.total|currency }}
{% endfor %}
<tr><td style="height: 1px; line-height: 1px; border-top: 1pt solid black;" colspan="4">&nbsp;</td></tr>
<tr>
<td colspan="2">
<td>
<strong>Included Danish VAT (25%)</strong>
<td align="right">
{{ order.vat|currency }}
<tr>
<td colspan="2">
<td>
<strong>Invoice Total</strong>
<td align="right">
{{ order.total|currency }}
<tr><td colspan="2"></td><td style="height: 1px; line-height: 1px; border-top: 1pt solid black; border-bottom: 1pt solid black;" colspan="4">&nbsp;</td></tr>
</table>
<br>
<h3>This is a proforma invoice. The order has not been paid. The payment options are:</h3>
<h4>Bank Transfer</h4>
<ul>
<li>Bank: {{ bank }}
<li>DK Reg. {{ bank_dk_reg }}, DK account no. {{ bank_dk_accno }}</li>
<li>BIC: {{ bank_bic }}, IBAN: {{ bank_iban }}</li>
<li>Please remember to add Order number in the transfer notes, and pay the transfer fees in your end.</li>
</ul>
<h4>Credit Card</h4>
<ul>
<li>https://{{ hostname }}{% url 'shop:epay_form' pk=order.pk %} (requires login)</li>
</ul>
<h4>Blockchain (multiple currencies)</h4>
<ul>
<li>https://{{ hostname }}{% url 'shop:coinify_pay' pk=order.pk %} (requires login)</li>
</ul>
<h4>Cash</h4>
<ul>
<li>https://{{ hostname }}{% url 'shop:cash' pk=order.pk %} (requires geographical proximity to an organiser)</li>
</ul>

View File

@ -148,21 +148,6 @@ class EnsureOrderIsNotCancelledMixin(SingleObjectMixin):
)
class EnsureOrderHasInvoicePDFMixin(SingleObjectMixin):
model = Order
def dispatch(self, request, *args, **kwargs):
if not self.get_object().invoice.pdf:
messages.error(request, "This order has no invoice yet!")
return HttpResponseRedirect(
reverse_lazy("shop:order_detail", kwargs={"pk": self.get_object().pk})
)
return super(EnsureOrderHasInvoicePDFMixin, self).dispatch(
request, *args, **kwargs
)
# Shop views
class ShopIndexView(ListView):
model = Product
@ -402,19 +387,32 @@ class OrderReviewAndPayView(
class DownloadInvoiceView(
LoginRequiredMixin,
EnsureUserOwnsOrderMixin,
EnsurePaidOrderMixin,
EnsureOrderHasInvoicePDFMixin,
SingleObjectMixin,
View,
):
model = Order
def get(self, request, *args, **kwargs):
"""
The file we return is determined by the orders paid status.
If the order is unpaid we return a proforma invoice PDF
If the order is paid we return a normal Invoice PDF
"""
if self.get_object().paid:
pdfobj = self.get_object().invoice
else:
pdfobj = self.get_object()
if not pdfobj.pdf:
messages.error(request, "No PDF has been generated yet!")
return HttpResponseRedirect(
reverse_lazy("shop:order_detail", kwargs={"pk": self.get_object().pk})
)
response = HttpResponse(content_type="application/pdf")
response["Content-Disposition"] = (
'attachment; filename="%s"' % self.get_object().invoice.filename
'attachment; filename="%s"' % pdfobj.filename
)
response.write(self.get_object().invoice.pdf.read())
response.write(pdfobj.pdf.read())
return response

View File

@ -12,6 +12,8 @@ logger = logging.getLogger("bornhack.%s" % __name__)
def generate_pdf_letter(filename, template, formatdict):
logger.debug("Generating PDF with filename %s and template %s" % (filename, template))
# conjure up a fake request for PDFTemplateResponse
request = RequestFactory().get("/")
request.user = AnonymousUser()
@ -63,3 +65,4 @@ def generate_pdf_letter(filename, template, formatdict):
returnfile = io.BytesIO()
finalpdf.write(returnfile)
return returnfile