Generate bill PDFs from /my/bill

This commit is contained in:
fnux 2020-05-07 15:38:49 +02:00
parent 3874165189
commit ae2bad5754
6 changed files with 96 additions and 115 deletions

View file

@ -81,7 +81,6 @@ urlpatterns = [
path('', include(router.urls)), path('', include(router.urls)),
# web/ = stuff to view in the browser # web/ = stuff to view in the browser
path('web/pdf/', payviews.MyPDFView.as_view(), name='pdf'),
path('api-auth/', include('rest_framework.urls', namespace='rest_framework')), # for login to REST API path('api-auth/', include('rest_framework.urls', namespace='rest_framework')), # for login to REST API
path('openapi', get_schema_view( path('openapi', get_schema_view(
title="uncloud", title="uncloud",

View file

@ -0,0 +1,19 @@
# Generated by Django 3.0.6 on 2020-05-07 13:07
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('uncloud_pay', '0010_order_description'),
]
operations = [
migrations.AddField(
model_name='billingaddress',
name='organization',
field=models.CharField(default='', max_length=100),
preserve_default=False,
),
]

View file

@ -444,6 +444,7 @@ class BillingAddress(models.Model):
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
owner = models.ForeignKey(get_user_model(), on_delete=models.CASCADE) owner = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
organization = models.CharField(max_length=100)
name = models.CharField(max_length=100) name = models.CharField(max_length=100)
street = models.CharField(max_length=100) street = models.CharField(max_length=100)
city = models.CharField(max_length=50) city = models.CharField(max_length=50)

View file

@ -95,7 +95,7 @@ class BillRecordSerializer(serializers.Serializer):
class BillingAddressSerializer(serializers.ModelSerializer): class BillingAddressSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = BillingAddress model = BillingAddress
fields = ['uuid', 'name', 'street', 'city', 'postal_code', 'country', 'vat_number'] fields = ['uuid', 'organization', 'name', 'street', 'city', 'postal_code', 'country', 'vat_number']
class BillSerializer(serializers.ModelSerializer): class BillSerializer(serializers.ModelSerializer):
billing_address = BillingAddressSerializer(read_only=True) billing_address = BillingAddressSerializer(read_only=True)
@ -103,7 +103,7 @@ class BillSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Bill model = Bill
fields = ['reference', 'owner', 'amount', 'vat_amount', 'total', fields = ['uuid', 'reference', 'owner', 'amount', 'vat_amount', 'total',
'due_date', 'creation_date', 'starting_date', 'ending_date', 'due_date', 'creation_date', 'starting_date', 'ending_date',
'records', 'final', 'billing_address'] 'records', 'final', 'billing_address']

View file

@ -26,7 +26,7 @@
<head> <head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" /> <meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>Bill name</title> <title>{{ bill.reference }} | {{ bill.uuid }}</title>
<style> <style>
body { body {
@ -49,6 +49,10 @@
-webkit-margin-start: 0px; -webkit-margin-start: 0px;
-webkit-margin-end: 0px; -webkit-margin-end: 0px;
} }
table {
width: 100%;
text-align: left;
}
.bold { .bold {
font-weight: bold; font-weight: bold;
} }
@ -668,11 +672,15 @@ oAsAAAAAAACGQNAFAAAAAAAAQyDoAgAAAAAAgCEQdAEAAAAAAMAQCLoAAAAAAABgCP83AL6WQ1Y7
<br> <br>
</div> </div>
<div class="d1"> <div class="d1">
<b>Faeh+Faeh GmbH </b> {% if bill.billing_address.organization != "" %}
<br> Pascal Faeh <b>{{ bill.billing_address.organization }}</b>
<span><</span>pascal@faehundfaeh.ch> <br>{{ bill.billing_address.name }} <bill.owner.email>
<br> Via Nova {% else %}
<br> 7017 Flims <b>{{ bill.billing_address.name }} <bill.owner.email></b>
{% endif %}
<br>{{ bill.billing_address.street }}
<br>{{ bill.billing_address.postal_code }} {{ bill.billing_address.city }}
<br>{{ bill.billing_address.country }}
<br> <br>
</div> </div>
<div class="d4"> <div class="d4">
@ -683,118 +691,62 @@ oAsAAAAAAACGQNAFAAAAAAAAQyDoAgAAAAAAgCEQdAEAAAAAAMAQCLoAAAAAAABgCP83AL6WQ1Y7
</div> </div>
<div class="b2"> <div class="b2">
2018-04-21<br> {{ bill.creation_date.date }}<br>
20180421FAEH1<br> {% if bill.billing_address.vat_number != "" %}
2018-05-20 {{ bill.billing_address.vat_number %}<br>
{% else %}
None<br>
{% endif %}
{{ bill.billing_address.vat_number }}<br>
{{ bill.due_date }}
</div> </div>
</div> </div>
<div style="clear: both;"></div> <div style="clear: both;"></div>
<div class="d5"> <div class="d5">
<h1>RECHNUNG</h1> <h1>RECHNUNG</h1>
</div> </div>
<div class="wf th"> <table>
<p class="bold"> <thead>
<span class="tl">Beschreibung</span> <tr>
<span class="tr">Netto CHF</span> <th>Beschreibung</th>
</p> <th>Detail</th>
</div> <th>Amount</th>
<div class="wf"> <th>VAT</th>
<p class="ts"> <th class="tr">Total</tH>
<span class="tl">NAS Synology DS1817+</span> </tr>
<span class="tr">1234.56</span> </thead>
</p> <tbody>
<p class="ts"> {% for record in bill.records %}
<span class="tl">10Gbit/s card Synology E10G17-F2</span> <tr class="table-list">
<span class="tr">345.67</span> <td>{{ record.description }}</td>
</p> <td>
{{ record.recurring_price }} * {{ record.recurring_count }}
{{ record.recurring_period }}
<p class="ts"> {% if record.one_time_price != 0 %}
<span class="tl">1OGbit/s switch HP</span> + one time {{ record.one_time_price }}
<span class="tr">567.89</span> {% endif %}
</p> </td>
<p class="ts"> <td>{{ record.amount }}</td>
<span class="tl">Festplatten 10 TB NAS RED Pro</span> <td>{{ record.vat_amount }} ({{ record.vat_rate }})</td>
<span class="tr">3456.78</span> <td class="tr">{{ record.total }}</td>
</p> </tr>
<p class="ts"> {% endfor %}
<span class="tl">10Gbit/s Transceiver Synology</span> </tbody>
<span class="tr">123.45</span> </table>
</p>
<p class="ts">
<span class="tl">10Gbit/s Transceiver Switch kompatibel</span>
<span class="tr">123.45</span>
</p>
<p class="ts">
<span class="tl">NAS Synology DS1817+</span>
<span class="tr">1234.56</span>
</p>
<p class="ts">
<span class="tl">10Gbit/s card Synology E10G17-F2</span>
<span class="tr">345.67</span>
</p>
<p class="ts">
<span class="tl">1OGbit/s switch HP</span>
<span class="tr">567.89</span>
</p>
<p class="ts">
<span class="tl">Festplatten 10 TB NAS RED Pro</span>
<span class="tr">3456.78</span>
</p>
<p class="ts">
<span class="tl">10Gbit/s Transceiver Synology</span>
<span class="tr">123.45</span>
</p>
<p class="ts">
<span class="tl">10Gbit/s Transceiver Switch kompatibel</span>
<span class="tr">123.45</span>
</p>
<p class="ts">
<span class="tl">10Gbit/s Transceiver Switch kompatibel</span>
<span class="tr">123.45</span>
</p>
<p class="ts">
<span class="tl">10Gbit/s Transceiver Switch kompatibel</span>
<span class="tr">123.45</span>
</p>
<p class="ts">
<span class="tl">10Gbit/s Transceiver Switch kompatibel</span>
<span class="tr">123.45</span>
</p>
<p class="ts">
<span class="tl">10Gbit/s Transceiver Switch kompatibel</span>
<span class="tr">123.45</span>
</p>
<p class="ts">
<span class="tl">10Gbit/s Transceiver Switch kompatibel</span>
<span class="tr">123.45</span>
</p>
<p class="ts">
<span class="tl">10Gbit/s Transceiver Switch kompatibel</span>
<span class="tr">123.45</span>
</p>
<p class="ts">
<span class="tl">10Gbit/s Transceiver Switch kompatibel</span>
<span class="tr">123.45</span>
</p>
</div>
<div class="wf th"> <div class="wf th">
<p class="ts"> <p class="ts">
<span class="tl">Total</span> <span class="tl">Total</span>
<span class="tr">12345.67</span> <span class="tr">{{ bill.amount }}</span>
</p> </p>
<p class="ts"> <p class="ts">
<span class="tl">7.70% Mehrwertsteuer</span> <span class="tl">VAT</span>
<span class="tr">891.00</span> <span class="tr">{{ bill.vat_amount }}</span>
</p> </p>
</div> </div>
<div class="wf pc"> <div class="wf pc">
<p class="bold"> <p class="bold">
<span class="tl">Gesamtbetrag</span> <span class="tl">Gesamtbetrag</span>
<span class="tr">23456.78</span> <span class="tr">{{ bill.total }}</span>
</p> </p>
</div> </div>
<div class="wf footer"> <div class="wf footer">

View file

@ -9,6 +9,10 @@ from rest_framework.reverse import reverse
from rest_framework.decorators import renderer_classes from rest_framework.decorators import renderer_classes
from vat_validator import validate_vat, vies from vat_validator import validate_vat, vies
from vat_validator.countries import EU_COUNTRY_CODES from vat_validator.countries import EU_COUNTRY_CODES
from hardcopy import bytestring_to_pdf
from django.core.files.temp import NamedTemporaryFile
from django.http import FileResponse
from django.template.loader import render_to_string
import json import json
import logging import logging
@ -190,6 +194,20 @@ class BillViewSet(viewsets.ReadOnlyModelViewSet):
many=True) many=True)
return Response(serializer.data) return Response(serializer.data)
@action(detail=True, methods=['get'])
def download(self, *args, **kwargs):
bill = self.get_object()
output_file = NamedTemporaryFile()
bill_html = render_to_string("bill.html.j2", {'bill': bill})
bytestring_to_pdf(bill_html.encode('utf-8'), output_file)
response = FileResponse(output_file, content_type="application/pdf")
response['Content-Disposition'] = 'filename="{}_{}.pdf"'.format(
bill.reference, bill.uuid
)
return response
class OrderViewSet(viewsets.ReadOnlyModelViewSet): class OrderViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = OrderSerializer serializer_class = OrderSerializer
@ -287,19 +305,11 @@ class AdminOrderViewSet(mixins.ListModelMixin,
mixins.RetrieveModelMixin, mixins.RetrieveModelMixin,
mixins.CreateModelMixin, mixins.CreateModelMixin,
viewsets.GenericViewSet): viewsets.GenericViewSet):
serializer_class = OrderSerializer
permission_classes = [permissions.IsAdminUser] permission_classes = [permissions.IsAdminUser]
def get_serializer(self, *args, **kwargs): def get_serializer(self, *args, **kwargs):
return OrderSerializer(*args, **kwargs, admin=True) return self.serializer_class(*args, **kwargs, admin=True)
def get_queryset(self): def get_queryset(self):
return Order.objects.all() return Order.objects.all()
# PDF tests
from django.views.generic import TemplateView
from hardcopy.views import PDFViewMixin, PNGViewMixin
class MyPDFView(PDFViewMixin, TemplateView):
template_name = "bill.html"
# def get_filename(self):
# return "my_file_{}.pdf".format(now().strftime('Y-m-d'))