Generate bill PDFs from /my/bill
This commit is contained in:
		
					parent
					
						
							
								3874165189
							
						
					
				
			
			
				commit
				
					
						ae2bad5754
					
				
			
		
					 6 changed files with 96 additions and 115 deletions
				
			
		
							
								
								
									
										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…
	
	Add table
		Add a link
		
	
		Reference in a new issue