Generate bill PDFs from /my/bill
This commit is contained in:
parent
3874165189
commit
ae2bad5754
6 changed files with 96 additions and 115 deletions
|
@ -81,7 +81,6 @@ urlpatterns = [
|
|||
path('', include(router.urls)),
|
||||
# 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('openapi', get_schema_view(
|
||||
title="uncloud",
|
||||
|
|
19
uncloud_pay/migrations/0011_billingaddress_organization.py
Normal file
19
uncloud_pay/migrations/0011_billingaddress_organization.py
Normal 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,
|
||||
),
|
||||
]
|
|
@ -444,6 +444,7 @@ class BillingAddress(models.Model):
|
|||
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||
owner = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
|
||||
|
||||
organization = models.CharField(max_length=100)
|
||||
name = models.CharField(max_length=100)
|
||||
street = models.CharField(max_length=100)
|
||||
city = models.CharField(max_length=50)
|
||||
|
|
|
@ -95,7 +95,7 @@ class BillRecordSerializer(serializers.Serializer):
|
|||
class BillingAddressSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
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):
|
||||
billing_address = BillingAddressSerializer(read_only=True)
|
||||
|
@ -103,7 +103,7 @@ class BillSerializer(serializers.ModelSerializer):
|
|||
|
||||
class Meta:
|
||||
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',
|
||||
'records', 'final', 'billing_address']
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
<title>Bill name</title>
|
||||
<title>{{ bill.reference }} | {{ bill.uuid }}</title>
|
||||
|
||||
<style>
|
||||
body {
|
||||
|
@ -49,6 +49,10 @@
|
|||
-webkit-margin-start: 0px;
|
||||
-webkit-margin-end: 0px;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
}
|
||||
.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
@ -668,11 +672,15 @@ oAsAAAAAAACGQNAFAAAAAAAAQyDoAgAAAAAAgCEQdAEAAAAAAMAQCLoAAAAAAABgCP83AL6WQ1Y7
|
|||
<br>
|
||||
</div>
|
||||
<div class="d1">
|
||||
<b>Faeh+Faeh GmbH </b>
|
||||
<br> Pascal Faeh
|
||||
<span><</span>pascal@faehundfaeh.ch>
|
||||
<br> Via Nova
|
||||
<br> 7017 Flims
|
||||
{% if bill.billing_address.organization != "" %}
|
||||
<b>{{ bill.billing_address.organization }}</b>
|
||||
<br>{{ bill.billing_address.name }} <bill.owner.email>
|
||||
{% else %}
|
||||
<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>
|
||||
</div>
|
||||
<div class="d4">
|
||||
|
@ -683,118 +691,62 @@ oAsAAAAAAACGQNAFAAAAAAAAQyDoAgAAAAAAgCEQdAEAAAAAAMAQCLoAAAAAAABgCP83AL6WQ1Y7
|
|||
|
||||
</div>
|
||||
<div class="b2">
|
||||
2018-04-21<br>
|
||||
20180421FAEH1<br>
|
||||
2018-05-20
|
||||
{{ bill.creation_date.date }}<br>
|
||||
{% if bill.billing_address.vat_number != "" %}
|
||||
{{ bill.billing_address.vat_number %}<br>
|
||||
{% else %}
|
||||
None<br>
|
||||
{% endif %}
|
||||
{{ bill.billing_address.vat_number }}<br>
|
||||
{{ bill.due_date }}
|
||||
</div>
|
||||
</div>
|
||||
<div style="clear: both;"></div>
|
||||
<div class="d5">
|
||||
<h1>RECHNUNG</h1>
|
||||
</div>
|
||||
<div class="wf th">
|
||||
<p class="bold">
|
||||
<span class="tl">Beschreibung</span>
|
||||
<span class="tr">Netto CHF</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="wf">
|
||||
<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">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>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Beschreibung</th>
|
||||
<th>Detail</th>
|
||||
<th>Amount</th>
|
||||
<th>VAT</th>
|
||||
<th class="tr">Total</tH>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for record in bill.records %}
|
||||
<tr class="table-list">
|
||||
<td>{{ record.description }}</td>
|
||||
<td>
|
||||
{{ record.recurring_price }} * {{ record.recurring_count }}
|
||||
{{ record.recurring_period }}
|
||||
{% if record.one_time_price != 0 %}
|
||||
+ one time {{ record.one_time_price }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ record.amount }}</td>
|
||||
<td>{{ record.vat_amount }} ({{ record.vat_rate }})</td>
|
||||
<td class="tr">{{ record.total }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="wf th">
|
||||
<p class="ts">
|
||||
<span class="tl">Total</span>
|
||||
<span class="tr">12345.67</span>
|
||||
<span class="tr">{{ bill.amount }}</span>
|
||||
</p>
|
||||
<p class="ts">
|
||||
<span class="tl">7.70% Mehrwertsteuer</span>
|
||||
<span class="tr">891.00</span>
|
||||
<span class="tl">VAT</span>
|
||||
<span class="tr">{{ bill.vat_amount }}</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="wf pc">
|
||||
<p class="bold">
|
||||
<span class="tl">Gesamtbetrag</span>
|
||||
<span class="tr">23456.78</span>
|
||||
<span class="tr">{{ bill.total }}</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="wf footer">
|
|
@ -9,6 +9,10 @@ from rest_framework.reverse import reverse
|
|||
from rest_framework.decorators import renderer_classes
|
||||
from vat_validator import validate_vat, vies
|
||||
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 logging
|
||||
|
@ -190,6 +194,20 @@ class BillViewSet(viewsets.ReadOnlyModelViewSet):
|
|||
many=True)
|
||||
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):
|
||||
serializer_class = OrderSerializer
|
||||
|
@ -287,19 +305,11 @@ class AdminOrderViewSet(mixins.ListModelMixin,
|
|||
mixins.RetrieveModelMixin,
|
||||
mixins.CreateModelMixin,
|
||||
viewsets.GenericViewSet):
|
||||
serializer_class = OrderSerializer
|
||||
permission_classes = [permissions.IsAdminUser]
|
||||
|
||||
def get_serializer(self, *args, **kwargs):
|
||||
return OrderSerializer(*args, **kwargs, admin=True)
|
||||
return self.serializer_class(*args, **kwargs, admin=True)
|
||||
|
||||
def get_queryset(self):
|
||||
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'))
|
||||
|
|
Loading…
Reference in a new issue