add proforma invoice support
This commit is contained in:
parent
fef09baa3d
commit
effd900b62
|
@ -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
|
||||
|
|
18
src/shop/migrations/0058_order_pdf.py
Normal file
18
src/shop/migrations/0058_order_pdf.py
Normal 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/'),
|
||||
),
|
||||
]
|
|
@ -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:
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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>
|
||||
|
|
95
src/shop/templates/pdf/proforma_invoice.html
Normal file
95
src/shop/templates/pdf/proforma_invoice.html
Normal 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%;"> </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"> </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"> </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"> </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>
|
||||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in a new issue