forked from uncloud/uncloud
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)),
|
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",
|
||||||
|
|
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)
|
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)
|
||||||
|
|
|
@ -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']
|
||||||
|
|
||||||
|
|
|
@ -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">
|
|
@ -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'))
|
|
||||||
|
|
Loading…
Reference in a new issue