From 0e84081880404955b5c2746e731f9ee026ba78e9 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Tue, 2 Apr 2019 09:18:15 +0200
Subject: [PATCH 01/75] Add monthlyhostingbill model + code

---
 hosting/models.py     | 22 ++++++++++++++++++++++
 utils/stripe_utils.py | 20 ++++++++++++++++++++
 2 files changed, 42 insertions(+)

diff --git a/hosting/models.py b/hosting/models.py
index 707b072d..4e4e2a59 100644
--- a/hosting/models.py
+++ b/hosting/models.py
@@ -232,6 +232,28 @@ class HostingBill(AssignPermissionsMixin, models.Model):
         return instance
 
 
+class MonthlyHostingBill(AssignPermissionsMixin, models.Model):
+    customer = models.ForeignKey(StripeCustomer)
+    order = models.ForeignKey(HostingOrder)
+    receipt_number = models.CharField(
+        help_text="The receipt number that is generated on Stripe"
+    )
+    invoice_number = models.CharField(
+        help_text="The invoice number that is generated on Stripe"
+    )
+    billing_period = models.CharField(
+        help_text="The billing period for which the bill is valid"
+    )
+    date_paid = models.DateField(help_text="Date on which the bill was paid")
+
+    permissions = ('view_monthlyhostingbill',)
+
+    class Meta:
+        permissions = (
+            ('view_monthlyhostingbill', 'View Monthly Hosting'),
+        )
+
+
 class VMDetail(models.Model):
     user = models.ForeignKey(CustomUser)
     vm_id = models.IntegerField(default=0)
diff --git a/utils/stripe_utils.py b/utils/stripe_utils.py
index a3224a0e..d412cbd0 100644
--- a/utils/stripe_utils.py
+++ b/utils/stripe_utils.py
@@ -122,6 +122,26 @@ class StripeUtils(object):
         }
         return card_details
 
+    @handleStripeError
+    def get_all_invoices(self, customer_id):
+        invoices = stripe.Invoice.list(limit=100, customer=customer_id)
+        return_list = []
+        for invoice in invoices:
+            invoice_details = {
+                'created': invoice.created,
+                'receipt_number': invoice.receipt_number,
+                'invoice_number': invoice.number,
+                'date_paid': invoice.date_paid,
+                'period_start': invoice.period_start,
+                'period_end': invoice.period_end,
+                'paid': invoice.paid,
+                'billing_reason': invoice.billing_reason,
+                'discount': invoice.discount.coupon.amount_off if invoice.discount else 0,
+                'total': invoice.total
+            }
+            return_list.append(invoice_details)
+        return return_list
+
     @handleStripeError
     def get_cards_details_from_token(self, token):
         stripe_token = stripe.Token.retrieve(token)

From 8dc00c9dd9040d1921bee56c3a67fd908def5ee3 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Tue, 2 Apr 2019 09:18:46 +0200
Subject: [PATCH 02/75] Add management command

---
 .../management/commands/fetch_stripe_bills.py | 28 +++++++++++++++++++
 1 file changed, 28 insertions(+)
 create mode 100644 hosting/management/commands/fetch_stripe_bills.py

diff --git a/hosting/management/commands/fetch_stripe_bills.py b/hosting/management/commands/fetch_stripe_bills.py
new file mode 100644
index 00000000..3a06a1d9
--- /dev/null
+++ b/hosting/management/commands/fetch_stripe_bills.py
@@ -0,0 +1,28 @@
+from django.core.management.base import BaseCommand
+
+from hosting.models import UserCardDetail
+from membership.models import CustomUser
+from utils.stripe_utils import StripeUtils
+
+
+class Command(BaseCommand):
+    help = '''Fetches invoices from Stripe and creates bills for a given 
+    customer in the MonthlyHostingBill model'''
+
+    def add_arguments(self, parser):
+        parser.add_argument('customer_email', nargs='+', type=str)
+
+    def handle(self, *args, **options):
+        try:
+            for email in options['customer_email']:
+                stripe_utils = StripeUtils()
+                user = CustomUser.objects.get(email=email)
+                if hasattr(user, 'stripecustomer'):
+                    self.stdout.write(self.style.SUCCESS('Found %s. Fetching bills for him.' % email))
+                    stripe_utils.get_all_invoices(
+                        user.stripecustomer.stripe_id
+                    )
+                else:
+                    self.stdout.write(self.style.SUCCESS('Customer email %s does not have a stripe customer.' % email))
+        except Exception as e:
+            print(" *** Error occurred. Details {}".format(str(e)))

From 6d42f88be1c2d3955812ac50a3ada803afcabe35 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Wed, 3 Apr 2019 06:12:48 +0200
Subject: [PATCH 03/75] Complete implementation of fetch_stripe_bills

---
 .../management/commands/fetch_stripe_bills.py | 35 +++++++++++--
 hosting/models.py                             | 38 +++++++++++---
 utils/stripe_utils.py                         | 51 +++++++++++++------
 3 files changed, 97 insertions(+), 27 deletions(-)

diff --git a/hosting/management/commands/fetch_stripe_bills.py b/hosting/management/commands/fetch_stripe_bills.py
index 3a06a1d9..6285ec0a 100644
--- a/hosting/management/commands/fetch_stripe_bills.py
+++ b/hosting/management/commands/fetch_stripe_bills.py
@@ -1,9 +1,13 @@
+import logging
+
 from django.core.management.base import BaseCommand
 
-from hosting.models import UserCardDetail
+from hosting.models import MonthlyHostingBill
 from membership.models import CustomUser
 from utils.stripe_utils import StripeUtils
 
+logger = logging.getLogger(__name__)
+
 
 class Command(BaseCommand):
     help = '''Fetches invoices from Stripe and creates bills for a given 
@@ -18,11 +22,32 @@ class Command(BaseCommand):
                 stripe_utils = StripeUtils()
                 user = CustomUser.objects.get(email=email)
                 if hasattr(user, 'stripecustomer'):
-                    self.stdout.write(self.style.SUCCESS('Found %s. Fetching bills for him.' % email))
-                    stripe_utils.get_all_invoices(
-                        user.stripecustomer.stripe_id
+                    self.stdout.write(self.style.SUCCESS(
+                        'Found %s. Fetching bills for him.' % email))
+                    mhb = MonthlyHostingBill.objects.last(
+                        customer=user.stripecustomer
                     )
+                    created_gt = {}
+                    if mhb is not None:
+                        # fetch only invoices which is created after
+                        # mhb.created, because we already have invoices till
+                        # this date
+                        created_gt = {'gt': mhb.created}
+
+                    all_invoices_response = stripe_utils.get_all_invoices(
+                        user.stripecustomer.stripe_id,
+                        created=created_gt
+                    )
+                    all_invoices = all_invoices_response['response_object']
+                    logger.debug(
+                        "Obtained {} invoices".format(len(all_invoices))
+                    )
+                    for invoice in all_invoices:
+                        MonthlyHostingBill.create(
+                            invoice, stripe_customer=user.stripecustomer
+                        )
                 else:
-                    self.stdout.write(self.style.SUCCESS('Customer email %s does not have a stripe customer.' % email))
+                    self.stdout.write(self.style.SUCCESS(
+                        'Customer email %s does not have a stripe customer.' % email))
         except Exception as e:
             print(" *** Error occurred. Details {}".format(str(e)))
diff --git a/hosting/models.py b/hosting/models.py
index 4e4e2a59..856e83ea 100644
--- a/hosting/models.py
+++ b/hosting/models.py
@@ -235,16 +235,24 @@ class HostingBill(AssignPermissionsMixin, models.Model):
 class MonthlyHostingBill(AssignPermissionsMixin, models.Model):
     customer = models.ForeignKey(StripeCustomer)
     order = models.ForeignKey(HostingOrder)
+    created = models.DateTimeField(help_text="When the invoice was created")
     receipt_number = models.CharField(
-        help_text="The receipt number that is generated on Stripe"
+        help_text="The receipt number that is generated on Stripe",
+        max_length=100
     )
     invoice_number = models.CharField(
-        help_text="The invoice number that is generated on Stripe"
+        help_text="The invoice number that is generated on Stripe",
+        max_length=100
     )
-    billing_period = models.CharField(
-        help_text="The billing period for which the bill is valid"
-    )
-    date_paid = models.DateField(help_text="Date on which the bill was paid")
+    paid_at = models.DateTimeField(help_text="Date on which the bill was paid")
+    period_start = models.DateTimeField()
+    period_end = models.DateTimeField()
+    billing_reason = models.CharField(max_length=25)
+    discount = models.PositiveIntegerField()
+    total = models.IntegerField()
+    lines_data_count = models.IntegerField()
+    invoice_id = models.CharField(unique=True, max_length=100)
+    lines_meta_data_csv = models.TextField()
 
     permissions = ('view_monthlyhostingbill',)
 
@@ -253,6 +261,24 @@ class MonthlyHostingBill(AssignPermissionsMixin, models.Model):
             ('view_monthlyhostingbill', 'View Monthly Hosting'),
         )
 
+    @classmethod
+    def create(cls, stripe_customer, **args):
+        instance = cls.objects.create(args)
+        instance.customer = stripe_customer
+        if len(instance.lines_meta_data_csv) > 0:
+            vm_ids = [vm_id.strip() for vm_id in instance.lines_meta_data_csv.split(",")]
+            if len(vm_ids) == 1:
+                instance.order = HostingOrder.objects.get(vm_id=vm_ids[0])
+            else:
+                logger.debug(
+                    "More than one VM_ID"
+                    "for MonthlyHostingBill {}".format(instance.invoice_id)
+                )
+                logger.debug("VM_IDS=".format(','.join(vm_ids)))
+        instance.assign_permissions(stripe_customer.user)
+        instance.save()
+        return instance
+
 
 class VMDetail(models.Model):
     user = models.ForeignKey(CustomUser)
diff --git a/utils/stripe_utils.py b/utils/stripe_utils.py
index d412cbd0..63df0133 100644
--- a/utils/stripe_utils.py
+++ b/utils/stripe_utils.py
@@ -123,23 +123,42 @@ class StripeUtils(object):
         return card_details
 
     @handleStripeError
-    def get_all_invoices(self, customer_id):
-        invoices = stripe.Invoice.list(limit=100, customer=customer_id)
+    def get_all_invoices(self, customer_id, created):
         return_list = []
-        for invoice in invoices:
-            invoice_details = {
-                'created': invoice.created,
-                'receipt_number': invoice.receipt_number,
-                'invoice_number': invoice.number,
-                'date_paid': invoice.date_paid,
-                'period_start': invoice.period_start,
-                'period_end': invoice.period_end,
-                'paid': invoice.paid,
-                'billing_reason': invoice.billing_reason,
-                'discount': invoice.discount.coupon.amount_off if invoice.discount else 0,
-                'total': invoice.total
-            }
-            return_list.append(invoice_details)
+        has_more_invoices = True
+        starting_after = False
+        while has_more_invoices:
+            if starting_after:
+                invoices = stripe.Invoice.list(
+                    limit=10, customer=customer_id, created=created
+                )
+            else:
+                invoices = stripe.Invoice.list(
+                    limit=10, customer=customer_id, created=created,
+                    starting_after=starting_after
+                )
+            has_more_invoices = invoices.has_more
+            for invoice in invoices.data:
+                invoice_details = {
+                    'created': invoice.created,
+                    'receipt_number': invoice.receipt_number,
+                    'invoice_number': invoice.number,
+                    'paid_at': invoice.status_transitions.paid_at if invoice.paid else 0,
+                    'period_start': invoice.period_start,
+                    'period_end': invoice.period_end,
+                    'billing_reason': invoice.billing_reason,
+                    'discount': invoice.discount.coupon.amount_off if invoice.discount else 0,
+                    'total': invoice.total,
+                    # to see how many line items we have in this invoice and
+                    # then later check if we have more than 1
+                    'lines_data_count': len(invoice.lines.data),
+                    'invoice_id': invoice.id,
+                    'lines_meta_data_csv': ','.join(
+                        [line.metadata.VM_ID for line in invoice.lines.data]
+                    )
+                }
+                starting_after = invoice.id
+                return_list.append(invoice_details)
         return return_list
 
     @handleStripeError

From 0bc8c350312216baf3a23ea3d2d1a366eefe6ae8 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Wed, 3 Apr 2019 06:13:12 +0200
Subject: [PATCH 04/75] Add migration

---
 hosting/migrations/0050_monthlyhostingbill.py | 42 +++++++++++++++++++
 1 file changed, 42 insertions(+)
 create mode 100644 hosting/migrations/0050_monthlyhostingbill.py

diff --git a/hosting/migrations/0050_monthlyhostingbill.py b/hosting/migrations/0050_monthlyhostingbill.py
new file mode 100644
index 00000000..34d29e68
--- /dev/null
+++ b/hosting/migrations/0050_monthlyhostingbill.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.4 on 2019-04-03 03:47
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+import utils.mixins
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('membership', '0007_auto_20180213_0128'),
+        ('hosting', '0049_auto_20181005_0736'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='MonthlyHostingBill',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('created', models.DateTimeField(help_text='When the invoice was created')),
+                ('receipt_number', models.CharField(help_text='The receipt number that is generated on Stripe', max_length=100)),
+                ('invoice_number', models.CharField(help_text='The invoice number that is generated on Stripe', max_length=100)),
+                ('paid_at', models.DateTimeField(help_text='Date on which the bill was paid')),
+                ('period_start', models.DateTimeField()),
+                ('period_end', models.DateTimeField()),
+                ('billing_reason', models.CharField(max_length=25)),
+                ('discount', models.PositiveIntegerField()),
+                ('total', models.IntegerField()),
+                ('lines_data_count', models.IntegerField()),
+                ('invoice_id', models.CharField(max_length=100, unique=True)),
+                ('lines_meta_data_csv', models.TextField()),
+                ('customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='membership.StripeCustomer')),
+                ('order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='hosting.HostingOrder')),
+            ],
+            options={
+                'permissions': (('view_monthlyhostingbill', 'View Monthly Hosting'),),
+            },
+            bases=(utils.mixins.AssignPermissionsMixin, models.Model),
+        ),
+    ]

From b1566c4c61605ff5f9122af6a00d10ebfb2b2dfd Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Wed, 3 Apr 2019 06:22:49 +0200
Subject: [PATCH 05/75] Get the last monthly hosting bill

---
 hosting/management/commands/fetch_stripe_bills.py | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/hosting/management/commands/fetch_stripe_bills.py b/hosting/management/commands/fetch_stripe_bills.py
index 6285ec0a..fb4718c7 100644
--- a/hosting/management/commands/fetch_stripe_bills.py
+++ b/hosting/management/commands/fetch_stripe_bills.py
@@ -24,9 +24,8 @@ class Command(BaseCommand):
                 if hasattr(user, 'stripecustomer'):
                     self.stdout.write(self.style.SUCCESS(
                         'Found %s. Fetching bills for him.' % email))
-                    mhb = MonthlyHostingBill.objects.last(
-                        customer=user.stripecustomer
-                    )
+                    mhb = MonthlyHostingBill.objects.filter(
+                        customer=user.stripecustomer).last()
                     created_gt = {}
                     if mhb is not None:
                         # fetch only invoices which is created after

From 6f1449836a654cad5eefe23720d07764017de077 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Wed, 3 Apr 2019 06:27:54 +0200
Subject: [PATCH 06/75] Fix a bug: use starting_after if its defined

---
 utils/stripe_utils.py | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/utils/stripe_utils.py b/utils/stripe_utils.py
index 63df0133..ee6e1b18 100644
--- a/utils/stripe_utils.py
+++ b/utils/stripe_utils.py
@@ -130,12 +130,12 @@ class StripeUtils(object):
         while has_more_invoices:
             if starting_after:
                 invoices = stripe.Invoice.list(
-                    limit=10, customer=customer_id, created=created
+                    limit=10, customer=customer_id, created=created,
+                    starting_after=starting_after
                 )
             else:
                 invoices = stripe.Invoice.list(
-                    limit=10, customer=customer_id, created=created,
-                    starting_after=starting_after
+                    limit=10, customer=customer_id, created=created
                 )
             has_more_invoices = invoices.has_more
             for invoice in invoices.data:

From b7dd4acb0798d655a9becc8c5e09d0a37071f4f4 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Wed, 3 Apr 2019 06:33:28 +0200
Subject: [PATCH 07/75] Correct the way of getting VM_ID meta data

---
 utils/stripe_utils.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/utils/stripe_utils.py b/utils/stripe_utils.py
index ee6e1b18..53346626 100644
--- a/utils/stripe_utils.py
+++ b/utils/stripe_utils.py
@@ -154,7 +154,7 @@ class StripeUtils(object):
                     'lines_data_count': len(invoice.lines.data),
                     'invoice_id': invoice.id,
                     'lines_meta_data_csv': ','.join(
-                        [line.metadata.VM_ID for line in invoice.lines.data]
+                        [line.metadata.VM_ID if line.metadata.VM_ID is not None else '' for line in invoice.lines.data]
                     )
                 }
                 starting_after = invoice.id

From 3eaa53ca78bb7e52027853ce53409d46493fd471 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Wed, 3 Apr 2019 06:36:28 +0200
Subject: [PATCH 08/75] Use stdout instead of logger

---
 hosting/management/commands/fetch_stripe_bills.py | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/hosting/management/commands/fetch_stripe_bills.py b/hosting/management/commands/fetch_stripe_bills.py
index fb4718c7..89fffb27 100644
--- a/hosting/management/commands/fetch_stripe_bills.py
+++ b/hosting/management/commands/fetch_stripe_bills.py
@@ -38,9 +38,7 @@ class Command(BaseCommand):
                         created=created_gt
                     )
                     all_invoices = all_invoices_response['response_object']
-                    logger.debug(
-                        "Obtained {} invoices".format(len(all_invoices))
-                    )
+                    self.stdout.write(self.style.SUCCESS("Obtained {} invoices".format(len(all_invoices))))
                     for invoice in all_invoices:
                         MonthlyHostingBill.create(
                             invoice, stripe_customer=user.stripecustomer

From 033db01810643f9c9129b57103269ee7622b70cf Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Wed, 3 Apr 2019 06:51:19 +0200
Subject: [PATCH 09/75] Correct error in getting lines data count

---
 utils/stripe_utils.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/utils/stripe_utils.py b/utils/stripe_utils.py
index 53346626..834b2201 100644
--- a/utils/stripe_utils.py
+++ b/utils/stripe_utils.py
@@ -151,7 +151,7 @@ class StripeUtils(object):
                     'total': invoice.total,
                     # to see how many line items we have in this invoice and
                     # then later check if we have more than 1
-                    'lines_data_count': len(invoice.lines.data),
+                    'lines_data_count': len(invoice.lines.data) if invoice.lines.data is not None else 0,
                     'invoice_id': invoice.id,
                     'lines_meta_data_csv': ','.join(
                         [line.metadata.VM_ID if line.metadata.VM_ID is not None else '' for line in invoice.lines.data]

From c85a4f379652c8fb19031164b234c84f442db885 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Wed, 3 Apr 2019 06:59:05 +0200
Subject: [PATCH 10/75] Catch error from stripe call

---
 hosting/management/commands/fetch_stripe_bills.py | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/hosting/management/commands/fetch_stripe_bills.py b/hosting/management/commands/fetch_stripe_bills.py
index 89fffb27..7219341b 100644
--- a/hosting/management/commands/fetch_stripe_bills.py
+++ b/hosting/management/commands/fetch_stripe_bills.py
@@ -37,8 +37,11 @@ class Command(BaseCommand):
                         user.stripecustomer.stripe_id,
                         created=created_gt
                     )
+                    if all_invoices_response['error'] is not None:
+                        self.stdout.write(self.style.ERROR(all_invoices_response['error']))
+                        exit(1)
                     all_invoices = all_invoices_response['response_object']
-                    self.stdout.write(self.style.SUCCESS("Obtained {} invoices".format(len(all_invoices))))
+                    self.stdout.write(self.style.SUCCESS("Obtained {} invoices".format(len(all_invoices) if all_invoices is not None else 0)))
                     for invoice in all_invoices:
                         MonthlyHostingBill.create(
                             invoice, stripe_customer=user.stripecustomer

From dbf3b92c063b5b965e50874522cbd8927e32cf58 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Wed, 3 Apr 2019 07:08:39 +0200
Subject: [PATCH 11/75] Add logging and verbosity

---
 .../management/commands/fetch_stripe_bills.py | 23 +++++++++++++++++++
 1 file changed, 23 insertions(+)

diff --git a/hosting/management/commands/fetch_stripe_bills.py b/hosting/management/commands/fetch_stripe_bills.py
index 7219341b..cbb09a2e 100644
--- a/hosting/management/commands/fetch_stripe_bills.py
+++ b/hosting/management/commands/fetch_stripe_bills.py
@@ -1,4 +1,5 @@
 import logging
+import sys
 
 from django.core.management.base import BaseCommand
 
@@ -13,10 +14,32 @@ class Command(BaseCommand):
     help = '''Fetches invoices from Stripe and creates bills for a given 
     customer in the MonthlyHostingBill model'''
 
+    def set_logger(self, verbosity):
+        """
+        Set logger level based on verbosity option
+        """
+        handler = logging.StreamHandler(sys.stdout)
+        formatter = logging.Formatter('%(asctime)s|%(levelname)s|%(module)s| %(message)s')
+        handler.setFormatter(formatter)
+        logger.addHandler(handler)
+
+        if verbosity == 0:
+            self.logger.setLevel(logging.WARN)
+        elif verbosity == 1:  # default
+            self.logger.setLevel(logging.INFO)
+        elif verbosity > 1:
+            self.logger.setLevel(logging.DEBUG)
+
+        # verbosity 3: also enable all logging statements that reach the root
+        # logger
+        if verbosity > 2:
+            logging.getLogger().setLevel(logging.DEBUG)
+
     def add_arguments(self, parser):
         parser.add_argument('customer_email', nargs='+', type=str)
 
     def handle(self, *args, **options):
+        self.set_logger(options.get('verbosity'))
         try:
             for email in options['customer_email']:
                 stripe_utils = StripeUtils()

From 66ffbf38aa729ba753878bcccb6b799fb4f58582 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Wed, 3 Apr 2019 07:32:18 +0200
Subject: [PATCH 12/75] Handle if VM_ID metadata is not set

---
 utils/stripe_utils.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/utils/stripe_utils.py b/utils/stripe_utils.py
index 834b2201..7211465a 100644
--- a/utils/stripe_utils.py
+++ b/utils/stripe_utils.py
@@ -154,7 +154,7 @@ class StripeUtils(object):
                     'lines_data_count': len(invoice.lines.data) if invoice.lines.data is not None else 0,
                     'invoice_id': invoice.id,
                     'lines_meta_data_csv': ','.join(
-                        [line.metadata.VM_ID if line.metadata.VM_ID is not None else '' for line in invoice.lines.data]
+                        [line.metadata.VM_ID if hasattr(line.metadata, 'VM_ID') else '' for line in invoice.lines.data]
                     )
                 }
                 starting_after = invoice.id

From 444f79eab7f03724be7ec5bfef165229895475e0 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Wed, 3 Apr 2019 07:35:43 +0200
Subject: [PATCH 13/75] Remove unwanted logger code

---
 .../management/commands/fetch_stripe_bills.py | 23 -------------------
 1 file changed, 23 deletions(-)

diff --git a/hosting/management/commands/fetch_stripe_bills.py b/hosting/management/commands/fetch_stripe_bills.py
index cbb09a2e..7219341b 100644
--- a/hosting/management/commands/fetch_stripe_bills.py
+++ b/hosting/management/commands/fetch_stripe_bills.py
@@ -1,5 +1,4 @@
 import logging
-import sys
 
 from django.core.management.base import BaseCommand
 
@@ -14,32 +13,10 @@ class Command(BaseCommand):
     help = '''Fetches invoices from Stripe and creates bills for a given 
     customer in the MonthlyHostingBill model'''
 
-    def set_logger(self, verbosity):
-        """
-        Set logger level based on verbosity option
-        """
-        handler = logging.StreamHandler(sys.stdout)
-        formatter = logging.Formatter('%(asctime)s|%(levelname)s|%(module)s| %(message)s')
-        handler.setFormatter(formatter)
-        logger.addHandler(handler)
-
-        if verbosity == 0:
-            self.logger.setLevel(logging.WARN)
-        elif verbosity == 1:  # default
-            self.logger.setLevel(logging.INFO)
-        elif verbosity > 1:
-            self.logger.setLevel(logging.DEBUG)
-
-        # verbosity 3: also enable all logging statements that reach the root
-        # logger
-        if verbosity > 2:
-            logging.getLogger().setLevel(logging.DEBUG)
-
     def add_arguments(self, parser):
         parser.add_argument('customer_email', nargs='+', type=str)
 
     def handle(self, *args, **options):
-        self.set_logger(options.get('verbosity'))
         try:
             for email in options['customer_email']:
                 stripe_utils = StripeUtils()

From 12b8a778623de9121a2246ac0506264070aabe33 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Wed, 3 Apr 2019 09:03:58 +0200
Subject: [PATCH 14/75] Fix issues and also include subscription_id

---
 .../management/commands/fetch_stripe_bills.py |  5 +-
 hosting/migrations/0051_auto_20190403_0703.py | 25 +++++++
 hosting/models.py                             | 65 ++++++++++++++++---
 utils/stripe_utils.py                         |  5 +-
 4 files changed, 86 insertions(+), 14 deletions(-)
 create mode 100644 hosting/migrations/0051_auto_20190403_0703.py

diff --git a/hosting/management/commands/fetch_stripe_bills.py b/hosting/management/commands/fetch_stripe_bills.py
index 7219341b..8f35aa8c 100644
--- a/hosting/management/commands/fetch_stripe_bills.py
+++ b/hosting/management/commands/fetch_stripe_bills.py
@@ -43,9 +43,8 @@ class Command(BaseCommand):
                     all_invoices = all_invoices_response['response_object']
                     self.stdout.write(self.style.SUCCESS("Obtained {} invoices".format(len(all_invoices) if all_invoices is not None else 0)))
                     for invoice in all_invoices:
-                        MonthlyHostingBill.create(
-                            invoice, stripe_customer=user.stripecustomer
-                        )
+                        invoice['customer'] = user.stripecustomer
+                        MonthlyHostingBill.create(invoice)
                 else:
                     self.stdout.write(self.style.SUCCESS(
                         'Customer email %s does not have a stripe customer.' % email))
diff --git a/hosting/migrations/0051_auto_20190403_0703.py b/hosting/migrations/0051_auto_20190403_0703.py
new file mode 100644
index 00000000..d1f87e53
--- /dev/null
+++ b/hosting/migrations/0051_auto_20190403_0703.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.4 on 2019-04-03 07:03
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('hosting', '0050_monthlyhostingbill'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='monthlyhostingbill',
+            name='subscription_ids_csv',
+            field=models.TextField(default=''),
+        ),
+        migrations.AlterField(
+            model_name='monthlyhostingbill',
+            name='lines_meta_data_csv',
+            field=models.TextField(default=''),
+        ),
+    ]
diff --git a/hosting/models.py b/hosting/models.py
index 856e83ea..c976c336 100644
--- a/hosting/models.py
+++ b/hosting/models.py
@@ -1,8 +1,10 @@
 import logging
 import os
+import pytz
 
 from Crypto.PublicKey import RSA
 from dateutil.relativedelta import relativedelta
+from datetime import datetime
 from django.db import models
 from django.utils import timezone
 from django.utils.functional import cached_property
@@ -252,7 +254,8 @@ class MonthlyHostingBill(AssignPermissionsMixin, models.Model):
     total = models.IntegerField()
     lines_data_count = models.IntegerField()
     invoice_id = models.CharField(unique=True, max_length=100)
-    lines_meta_data_csv = models.TextField()
+    lines_meta_data_csv = models.TextField(default="")
+    subscription_ids_csv = models.TextField(default="")
 
     permissions = ('view_monthlyhostingbill',)
 
@@ -262,21 +265,63 @@ class MonthlyHostingBill(AssignPermissionsMixin, models.Model):
         )
 
     @classmethod
-    def create(cls, stripe_customer, **args):
-        instance = cls.objects.create(args)
-        instance.customer = stripe_customer
-        if len(instance.lines_meta_data_csv) > 0:
-            vm_ids = [vm_id.strip() for vm_id in instance.lines_meta_data_csv.split(",")]
+    def create(cls, args):
+        # Try to infer the HostingOrder from subscription id or VM_ID
+        if len(args['subscription_ids_csv']) > 0:
+            sub_ids = [sub_id.strip() for sub_id in args['subscription_ids_csv'].split(",")]
+            if len(sub_ids) == 1:
+                args['order'] = HostingOrder.objects.get(
+                    subscription_id=sub_ids[0]
+                )
+            else:
+                logger.debug(
+                    "More than one subscriptions"
+                    "for MonthlyHostingBill {}".format(args['invoice_id'])
+                )
+                logger.debug("SUB_IDS=".format(','.join(sub_ids)))
+                logger.debug("Not importing invoices")
+                return
+        elif len(args['lines_meta_data_csv']) > 0:
+            vm_ids = [vm_id.strip() for vm_id in args['lines_meta_data_csv'].split(",")]
             if len(vm_ids) == 1:
-                instance.order = HostingOrder.objects.get(vm_id=vm_ids[0])
+                args['order'] = HostingOrder.objects.get(vm_id=vm_ids[0])
             else:
                 logger.debug(
                     "More than one VM_ID"
-                    "for MonthlyHostingBill {}".format(instance.invoice_id)
+                    "for MonthlyHostingBill {}".format(args['invoice_id'])
                 )
                 logger.debug("VM_IDS=".format(','.join(vm_ids)))
-        instance.assign_permissions(stripe_customer.user)
-        instance.save()
+                logger.debug("Not importing invoices")
+                return
+        else:
+            logger.debug("Neither subscription id nor vm_id available")
+            logger.debug("Can't import invoice")
+            return
+
+        instance = cls.objects.create(
+            created=datetime.utcfromtimestamp(
+                args['created']).replace(tzinfo=pytz.utc),
+            receipt_number=(
+                args['receipt_number']
+                if args['receipt_number'] is not None else ''
+            ),
+            paid_at=datetime.utcfromtimestamp(
+                args['paid_at']).replace(tzinfo=pytz.utc),
+            period_start=datetime.utcfromtimestamp(
+                args['period_start']).replace(tzinfo=pytz.utc),
+            period_end=datetime.utcfromtimestamp(
+                args['period_end']).replace(tzinfo=pytz.utc),
+            billing_reason=args['billing_reason'],
+            discount=args['discount'],
+            total=args['total'],
+            lines_data_count=args['lines_data_count'],
+            invoice_id=args['invoice_id'],
+            lines_meta_data_csv=args['lines_meta_data_csv'],
+            stripe_customer=args['customer'],
+            subscription_ids_csv=args['subscription_ids_csv'],
+        )
+
+        instance.assign_permissions(instance.stripe_customer.user)
         return instance
 
 
diff --git a/utils/stripe_utils.py b/utils/stripe_utils.py
index 7211465a..ec430485 100644
--- a/utils/stripe_utils.py
+++ b/utils/stripe_utils.py
@@ -155,7 +155,10 @@ class StripeUtils(object):
                     'invoice_id': invoice.id,
                     'lines_meta_data_csv': ','.join(
                         [line.metadata.VM_ID if hasattr(line.metadata, 'VM_ID') else '' for line in invoice.lines.data]
-                    )
+                    ),
+                    'subscription_ids_csv': ','.join(
+                        [line.subscription if hasattr(line, 'subscription') else '' for line in invoice.lines.data]
+                    ),
                 }
                 starting_after = invoice.id
                 return_list.append(invoice_details)

From 8e1e3e41576baa4c33bb7a23e733999632bba68c Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Wed, 3 Apr 2019 09:12:14 +0200
Subject: [PATCH 15/75] Correct variable names

---
 hosting/models.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/hosting/models.py b/hosting/models.py
index c976c336..9b81addd 100644
--- a/hosting/models.py
+++ b/hosting/models.py
@@ -317,7 +317,8 @@ class MonthlyHostingBill(AssignPermissionsMixin, models.Model):
             lines_data_count=args['lines_data_count'],
             invoice_id=args['invoice_id'],
             lines_meta_data_csv=args['lines_meta_data_csv'],
-            stripe_customer=args['customer'],
+            customer=args['customer'],
+            order=args['order'],
             subscription_ids_csv=args['subscription_ids_csv'],
         )
 

From 2c3146111f5113116bf4dfa52772cbf26924b448 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Wed, 3 Apr 2019 09:20:38 +0200
Subject: [PATCH 16/75] Fix getting subscription id

---
 utils/stripe_utils.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/utils/stripe_utils.py b/utils/stripe_utils.py
index ec430485..b43470fa 100644
--- a/utils/stripe_utils.py
+++ b/utils/stripe_utils.py
@@ -157,7 +157,7 @@ class StripeUtils(object):
                         [line.metadata.VM_ID if hasattr(line.metadata, 'VM_ID') else '' for line in invoice.lines.data]
                     ),
                     'subscription_ids_csv': ','.join(
-                        [line.subscription if hasattr(line, 'subscription') else '' for line in invoice.lines.data]
+                        [line.id if line.type == 'subscription' else '' for line in invoice.lines.data]
                     ),
                 }
                 starting_after = invoice.id

From a690ef421f8f55320c7c05121f715e1b67102347 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Wed, 3 Apr 2019 09:24:25 +0200
Subject: [PATCH 17/75] Fix variable name

---
 hosting/models.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/hosting/models.py b/hosting/models.py
index 9b81addd..fb2a805b 100644
--- a/hosting/models.py
+++ b/hosting/models.py
@@ -322,7 +322,7 @@ class MonthlyHostingBill(AssignPermissionsMixin, models.Model):
             subscription_ids_csv=args['subscription_ids_csv'],
         )
 
-        instance.assign_permissions(instance.stripe_customer.user)
+        instance.assign_permissions(instance.customer.user)
         return instance
 
 

From cc6afa8d2abd1d7a540fd9f471b09228cf4ceaaa Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Wed, 3 Apr 2019 19:22:27 +0200
Subject: [PATCH 18/75] Fix datetime issue: pass unix timestamp instead of
 datetime

---
 hosting/management/commands/fetch_stripe_bills.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/hosting/management/commands/fetch_stripe_bills.py b/hosting/management/commands/fetch_stripe_bills.py
index 8f35aa8c..e6dd9536 100644
--- a/hosting/management/commands/fetch_stripe_bills.py
+++ b/hosting/management/commands/fetch_stripe_bills.py
@@ -31,7 +31,7 @@ class Command(BaseCommand):
                         # fetch only invoices which is created after
                         # mhb.created, because we already have invoices till
                         # this date
-                        created_gt = {'gt': mhb.created}
+                        created_gt = {'gt': mhb.created.timestamp()}
 
                     all_invoices_response = stripe_utils.get_all_invoices(
                         user.stripecustomer.stripe_id,

From 5c31417a371c0376bc747208481da4e6ee9b69a0 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Wed, 3 Apr 2019 19:34:57 +0200
Subject: [PATCH 19/75] Convert timestamp to int

---
 hosting/management/commands/fetch_stripe_bills.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/hosting/management/commands/fetch_stripe_bills.py b/hosting/management/commands/fetch_stripe_bills.py
index e6dd9536..2a37ed61 100644
--- a/hosting/management/commands/fetch_stripe_bills.py
+++ b/hosting/management/commands/fetch_stripe_bills.py
@@ -31,7 +31,7 @@ class Command(BaseCommand):
                         # fetch only invoices which is created after
                         # mhb.created, because we already have invoices till
                         # this date
-                        created_gt = {'gt': mhb.created.timestamp()}
+                        created_gt = {'gt': int(mhb.created.timestamp())}
 
                     all_invoices_response = stripe_utils.get_all_invoices(
                         user.stripecustomer.stripe_id,

From 147fd0fe5edbb4566fcb043ef55decbd2792a44e Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Wed, 3 Apr 2019 20:29:06 +0200
Subject: [PATCH 20/75] Add invoices.html

---
 hosting/templates/hosting/invoices.html | 57 +++++++++++++++++++++++++
 1 file changed, 57 insertions(+)
 create mode 100644 hosting/templates/hosting/invoices.html

diff --git a/hosting/templates/hosting/invoices.html b/hosting/templates/hosting/invoices.html
new file mode 100644
index 00000000..96d9e9e3
--- /dev/null
+++ b/hosting/templates/hosting/invoices.html
@@ -0,0 +1,57 @@
+{% extends "hosting/base_short.html" %}
+{% load staticfiles bootstrap3 humanize i18n %}
+
+{% block content %}
+<div class="dashboard-container">
+    <div class="dashboard-container-head">
+        <h3 class="dashboard-title-thin"><img src="{% static 'hosting/img/shopping-cart.svg' %}" class="un-icon" style="margin-top: -4px; width: 30px;"> {% trans "My Bills" %}</h3>
+        {% if messages %}
+            <div class="alert alert-warning">
+                {% for message in messages %}
+                <span>{{ message }}</span>
+                {% endfor %}
+            </div>
+        {% endif %}
+        <div class="dashboard-subtitle"></div>
+    </div>
+
+    <table class="table table-switch">
+        <thead>
+            <tr>
+                <th>{% trans "Order Nr." %}</th>
+                <th>{% trans "Date" %}</th>
+                <th>{% trans "Amount" %}</th>
+                <th></th>
+            </tr>
+        </thead>
+        <tbody>
+            {% for order in orders %}
+                <tr>
+                    <td class="xs-td-inline" data-header="{% trans 'Order Nr.' %}">{{ order.id }}</td>
+                    <td class="xs-td-bighalf locale_date" data-header="{% trans 'Date' %}">{{ order.created_at | date:'Y-m-d h:i a' }}</td>
+                    <td class="xs-td-smallhalf" data-header="{% trans 'Amount' %}">{{ order.price|floatformat:2|intcomma }}</td>
+                    <td class="text-right last-td">
+                        <a class="btn btn-order-detail" href="{% url 'hosting:orders' order.pk %}">{% trans 'See Invoice' %}</a>
+                    </td>
+                </tr>
+            {% endfor %}
+        </tbody>
+    </table>
+
+    {% if is_paginated %}
+        <div class="pagination">
+            <span class="page-links">
+                {% if page_obj.has_previous %}
+                    <a href="{{request.path}}?page={{ page_obj.previous_page_number }}">{% trans "previous" %}</a>
+                {% endif %}
+                <span class="page-current">
+                    {% trans "Page" %} {{ page_obj.number }} {% trans "of" %} {{ page_obj.paginator.num_pages }}.
+                </span>
+                {% if page_obj.has_next %}
+                    <a href="{{request.path}}?page={{ page_obj.next_page_number }}">{% trans "next" %}</a>
+                {% endif %}
+            </span>
+        </div>
+    {% endif %}
+</div>
+{% endblock %}

From de3734bf2000ba0c2db170342cb055f0a960b48b Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Wed, 3 Apr 2019 20:29:33 +0200
Subject: [PATCH 21/75] Add total_in_chf utility method

---
 hosting/models.py | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/hosting/models.py b/hosting/models.py
index fb2a805b..b735bb8f 100644
--- a/hosting/models.py
+++ b/hosting/models.py
@@ -325,6 +325,15 @@ class MonthlyHostingBill(AssignPermissionsMixin, models.Model):
         instance.assign_permissions(instance.customer.user)
         return instance
 
+    def total_in_chf(self):
+        """
+        Returns amount in chf. The total amount in this model is in cents.
+        Hence we multiply it by 0.01 to obtain the result
+
+        :return:
+        """
+        return self.total * 0.01
+
 
 class VMDetail(models.Model):
     user = models.ForeignKey(CustomUser)

From 71832f8afc277cb1ebf1ea4d710d353d3d6651f3 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Wed, 3 Apr 2019 20:31:24 +0200
Subject: [PATCH 22/75] invoices.html: Replace all order instances by invoice

---
 hosting/templates/hosting/invoices.html | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/hosting/templates/hosting/invoices.html b/hosting/templates/hosting/invoices.html
index 96d9e9e3..2fa2e3f4 100644
--- a/hosting/templates/hosting/invoices.html
+++ b/hosting/templates/hosting/invoices.html
@@ -18,20 +18,20 @@
     <table class="table table-switch">
         <thead>
             <tr>
-                <th>{% trans "Order Nr." %}</th>
+                <th>{% trans "Invoice Nr." %}</th>
                 <th>{% trans "Date" %}</th>
                 <th>{% trans "Amount" %}</th>
                 <th></th>
             </tr>
         </thead>
         <tbody>
-            {% for order in orders %}
+            {% for invoice in invoices %}
                 <tr>
-                    <td class="xs-td-inline" data-header="{% trans 'Order Nr.' %}">{{ order.id }}</td>
-                    <td class="xs-td-bighalf locale_date" data-header="{% trans 'Date' %}">{{ order.created_at | date:'Y-m-d h:i a' }}</td>
-                    <td class="xs-td-smallhalf" data-header="{% trans 'Amount' %}">{{ order.price|floatformat:2|intcomma }}</td>
+                    <td class="xs-td-inline" data-header="{% trans 'Invoice Nr.' %}">{{ invoice.invoice_number }}</td>
+                    <td class="xs-td-bighalf locale_date" data-header="{% trans 'Date' %}">{{ invoice.paid_at | date:'Y-m-d h:i a' }}</td>
+                    <td class="xs-td-smallhalf" data-header="{% trans 'Amount' %}">{{ invoice.total_in_chf|floatformat:2|intcomma }}</td>
                     <td class="text-right last-td">
-                        <a class="btn btn-order-detail" href="{% url 'hosting:orders' order.pk %}">{% trans 'See Invoice' %}</a>
+                        <a class="btn btn-order-detail" href="{% url 'hosting:invoices' invoice.pk %}">{% trans 'See Invoice' %}</a>
                     </td>
                 </tr>
             {% endfor %}

From dbe3b2558cd132270fabd46ff89aef7d5f32f487 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Wed, 3 Apr 2019 20:31:54 +0200
Subject: [PATCH 23/75] Create an InvoiceListView

---
 hosting/views.py | 18 +++++++++++++++++-
 1 file changed, 17 insertions(+), 1 deletion(-)

diff --git a/hosting/views.py b/hosting/views.py
index 32de4e54..043bad99 100644
--- a/hosting/views.py
+++ b/hosting/views.py
@@ -61,7 +61,7 @@ from .forms import (
 from .mixins import ProcessVMSelectionMixin, HostingContextMixin
 from .models import (
     HostingOrder, HostingBill, HostingPlan, UserHostingKey, VMDetail,
-    GenericProduct
+    GenericProduct, MonthlyHostingBill
 )
 
 logger = logging.getLogger(__name__)
@@ -1146,6 +1146,22 @@ class OrdersHostingListView(LoginRequiredMixin, ListView):
         return super(OrdersHostingListView, self).get(request, *args, **kwargs)
 
 
+class InvoiceListView(OrdersHostingListView):
+    template_name = "hosting/invoices.html"
+    context_object_name = "invoices"
+    model = MonthlyHostingBill
+    ordering = '-created'
+
+    def get_queryset(self):
+        user = self.request.user
+        self.queryset = MonthlyHostingBill.objects.filter(customer__user=user)
+        return super(InvoiceListView, self).get_queryset()
+
+    @method_decorator(decorators)
+    def get(self, request, *args, **kwargs):
+        return super(InvoiceListView, self).get(request, *args, **kwargs)
+
+
 class OrdersHostingDeleteView(LoginRequiredMixin, DeleteView):
     login_url = reverse_lazy('hosting:login')
     success_url = reverse_lazy('hosting:orders')

From def5a3a0115c70bbbdca5daef51a4fbaffe8462b Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Wed, 3 Apr 2019 20:34:04 +0200
Subject: [PATCH 24/75] Add invoice urls

---
 hosting/urls.py | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/hosting/urls.py b/hosting/urls.py
index 32ef8400..3a0dd72f 100644
--- a/hosting/urls.py
+++ b/hosting/urls.py
@@ -8,7 +8,8 @@ from .views import (
     MarkAsReadNotificationView, PasswordResetView, PasswordResetConfirmView,
     HostingPricingView, CreateVirtualMachinesView, HostingBillListView,
     HostingBillDetailView, SSHKeyDeleteView, SSHKeyCreateView, SSHKeyListView,
-    SSHKeyChoiceView, DashboardView, SettingsView, ResendActivationEmailView
+    SSHKeyChoiceView, DashboardView, SettingsView, ResendActivationEmailView,
+    InvoiceListView
 )
 
 
@@ -22,10 +23,13 @@ urlpatterns = [
     url(r'payment/?$', PaymentVMView.as_view(), name='payment'),
     url(r'settings/?$', SettingsView.as_view(), name='settings'),
     url(r'orders/?$', OrdersHostingListView.as_view(), name='orders'),
+    url(r'invoices/?$', InvoiceListView.as_view(), name='invoices'),
     url(r'order-confirmation/?$', OrdersHostingDetailView.as_view(),
         name='order-confirmation'),
     url(r'orders/(?P<pk>\d+)/?$', OrdersHostingDetailView.as_view(),
         name='orders'),
+    url(r'invoices/(?P<pk>\d+)/?$', OrdersHostingDetailView.as_view(),
+        name='invoices'),
     url(r'bills/?$', HostingBillListView.as_view(), name='bills'),
     url(r'bills/(?P<pk>\d+)/?$', HostingBillDetailView.as_view(),
         name='bills'),

From e843a6f85753292e803dc6d373e7ca36490094e5 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Wed, 3 Apr 2019 21:16:19 +0200
Subject: [PATCH 25/75] Make invoicelistview not inherit OrderHostingListView

---
 hosting/views.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/hosting/views.py b/hosting/views.py
index 043bad99..47456574 100644
--- a/hosting/views.py
+++ b/hosting/views.py
@@ -1146,8 +1146,9 @@ class OrdersHostingListView(LoginRequiredMixin, ListView):
         return super(OrdersHostingListView, self).get(request, *args, **kwargs)
 
 
-class InvoiceListView(OrdersHostingListView):
+class InvoiceListView(LoginRequiredMixin, ListView):
     template_name = "hosting/invoices.html"
+    login_url = reverse_lazy('hosting:login')
     context_object_name = "invoices"
     model = MonthlyHostingBill
     ordering = '-created'

From 247bbe622fb47352e457faa5f6a370199fb941b4 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Wed, 3 Apr 2019 21:29:49 +0200
Subject: [PATCH 26/75] Add missing invoice_number argument to MHB create

---
 hosting/models.py | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/hosting/models.py b/hosting/models.py
index b735bb8f..5b48abbf 100644
--- a/hosting/models.py
+++ b/hosting/models.py
@@ -305,6 +305,10 @@ class MonthlyHostingBill(AssignPermissionsMixin, models.Model):
                 args['receipt_number']
                 if args['receipt_number'] is not None else ''
             ),
+            invoice_number=(
+                args['invoice_number']
+                if args['invoice_number'] is not None else ''
+            ),
             paid_at=datetime.utcfromtimestamp(
                 args['paid_at']).replace(tzinfo=pytz.utc),
             period_start=datetime.utcfromtimestamp(

From ba9e5548811bb1e4160ac777b294b5268c9e9fd4 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Wed, 3 Apr 2019 21:52:07 +0200
Subject: [PATCH 27/75] Implement get_object for invoice detail + url fix

---
 hosting/urls.py  |  4 ++--
 hosting/views.py | 29 +++++++++++++++++++++++++++++
 2 files changed, 31 insertions(+), 2 deletions(-)

diff --git a/hosting/urls.py b/hosting/urls.py
index 3a0dd72f..3f5a6f50 100644
--- a/hosting/urls.py
+++ b/hosting/urls.py
@@ -9,7 +9,7 @@ from .views import (
     HostingPricingView, CreateVirtualMachinesView, HostingBillListView,
     HostingBillDetailView, SSHKeyDeleteView, SSHKeyCreateView, SSHKeyListView,
     SSHKeyChoiceView, DashboardView, SettingsView, ResendActivationEmailView,
-    InvoiceListView
+    InvoiceListView, InvoiceDetailView
 )
 
 
@@ -28,7 +28,7 @@ urlpatterns = [
         name='order-confirmation'),
     url(r'orders/(?P<pk>\d+)/?$', OrdersHostingDetailView.as_view(),
         name='orders'),
-    url(r'invoices/(?P<pk>\d+)/?$', OrdersHostingDetailView.as_view(),
+    url(r'invoices/(?P<invoice_id>[-\w]+)/?$', InvoiceDetailView.as_view(),
         name='invoices'),
     url(r'bills/?$', HostingBillListView.as_view(), name='bills'),
     url(r'bills/(?P<pk>\d+)/?$', HostingBillDetailView.as_view(),
diff --git a/hosting/views.py b/hosting/views.py
index 47456574..17f63039 100644
--- a/hosting/views.py
+++ b/hosting/views.py
@@ -1163,6 +1163,35 @@ class InvoiceListView(LoginRequiredMixin, ListView):
         return super(InvoiceListView, self).get(request, *args, **kwargs)
 
 
+class InvoiceDetailView(LoginRequiredMixin, DetailView):
+    template_name = "hosting/invoice-detail.html"
+    context_object_name = "invoice"
+    login_url = reverse_lazy('hosting:login')
+    permission_required = ['view_monthlyhostingbill']
+    model = MonthlyHostingBill
+
+    def get_object(self, queryset=None):
+        invoice_id = self.kwargs.get('invoice_id')
+        try:
+            invoice_obj = MonthlyHostingBill.objects.get(invoice_number=invoice_id)
+            logger.debug("Found MHB for id {invoice_id}".format(
+                invoice_id=invoice_id
+            ))
+            if self.request.user.has_perm(
+                    self.permission_required[0], invoice_obj
+            ) or self.request.user.email == settings.ADMIN_EMAIL:
+                logger.debug("User has permission to invoice_obj")
+            else:
+                logger.error("User does not have permission to access")
+                invoice_obj = None
+        except HostingOrder.DoesNotExist:
+            logger.debug("MHB not found for id {invoice_id}".format(
+                invoice_id=invoice_id
+            ))
+            invoice_obj = None
+        return invoice_obj
+
+
 class OrdersHostingDeleteView(LoginRequiredMixin, DeleteView):
     login_url = reverse_lazy('hosting:login')
     success_url = reverse_lazy('hosting:orders')

From 94586c854a47f7569f3ef35371e50fec9d589414 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Wed, 3 Apr 2019 22:48:23 +0200
Subject: [PATCH 28/75] Add invoice detail

---
 hosting/templates/hosting/invoice_detail.html | 235 ++++++++++++++++++
 1 file changed, 235 insertions(+)
 create mode 100644 hosting/templates/hosting/invoice_detail.html

diff --git a/hosting/templates/hosting/invoice_detail.html b/hosting/templates/hosting/invoice_detail.html
new file mode 100644
index 00000000..ff6ec31d
--- /dev/null
+++ b/hosting/templates/hosting/invoice_detail.html
@@ -0,0 +1,235 @@
+{% extends "hosting/base_short.html" %}
+{% load staticfiles bootstrap3 humanize i18n custom_tags %}
+
+
+{% block content %}
+<div id="order-detail{{invoice.pk}}" class="order-detail-container">
+    {% if messages %}
+    <div class="alert alert-warning">
+        {% for message in messages %}
+        <span>{{ message }}</span>
+        {% endfor %}
+    </div>
+    {% endif %}
+    {% if not error %}
+    <div class="dashboard-container-head">
+        <h1 class="dashboard-title-thin">
+            <img src="{% static 'hosting/img/billing.svg' %}" class="un-icon">{%
+            blocktrans with page_header_text=page_header_text|default:"Invoice"
+            %}{{page_header_text}}{% endblocktrans %}
+        </h1>
+        {% if invoice %}
+        <div class="dashboard-container-options">
+            <button type="button" class="btn-plain btn-pdf"
+                    data-target="#order-detail{{invoice.pk}}"><img
+                    src="{% static 'hosting/img/icon-pdf.svg' %}"
+                    class="svg-img"></button>
+            <button type="button" class="btn-plain btn-print"><img
+                    src="{% static 'hosting/img/icon-print.svg' %}"
+                    class="svg-img"></button>
+        </div>
+        {% endif %}
+    </div>
+    <div class="order-details">
+        {% if invoice %}
+        <p>
+            <strong>{% trans "Invoice #" %} {{invoice.invoice_number}}</strong>
+        </p>
+        {% endif %}
+        <p>
+            <strong>{% trans "Date" %}:</strong>
+            <span class="locale_date">
+                    {% if invoice %}
+                        {{invoice.paid_at|date:'Y-m-d h:i a'}}
+                    {% else %}
+                        {% now "Y-m-d h:i a" %}
+                    {% endif %}
+                </span>
+        </p>
+        {% if invoice and vm %}
+        <p>
+            <strong>{% trans "Status" %}: </strong>
+            <strong>
+                {% if vm.terminated_at %}
+                <span class="vm-color-failed">{% trans "Terminated" %}</span>
+                {% elif invoice.order.status == 'Approved' %}
+                <span class="vm-color-online">{% trans "Approved" %}</span>
+                {% else %}
+                <span class="vm-status-failed">{% trans "Declined" %}</span>
+                {% endif %}
+            </strong>
+        </p>
+        {% endif %}
+        <hr>
+        <div>
+            <address>
+                <h4>{% trans "Billed to" %}:</h4>
+                <p>
+                    {% if invoice.order %}
+                    {{user.name}}<br>
+                    {{invoice.order.billing_address.street_address}},
+                    {{invoice.order.billing_address.postal_code}}<br>
+                    {{invoice.order.billing_address.city}},
+                    {{invoice.order.billing_address.country}}
+                    {% endif %}
+                </p>
+            </address>
+        </div>
+        <hr>
+        <div>
+            <h4>{% trans "Payment method" %}:</h4>
+            <p>
+                {% if invoice.order %}
+                {{invoice.order.cc_brand}} {% trans "ending in" %} ****
+                {{invoice.order.last4}}<br>
+                {{user.email}}
+                {% endif %}
+            </p>
+        </div>
+        <hr>
+        <div>
+            <h4>{% trans "Invoice summary" %}</h4>
+            {% if vm %}
+            <p>
+                <strong>{% trans "Product" %}:</strong>&nbsp;
+                {% if vm.name %}
+                {{ vm.name }}
+                {% endif %}
+            </p>
+            <div class="row">
+                <div class="col-sm-6">
+                    {% if period_start %}
+                    <p>
+                        <span>{% trans "Period" %}: </span>
+                        <span>
+                                        <span class="locale_date"
+                                              data-format="YYYY/MM/DD">{{ period_start|date:'Y-m-d h:i a' }}</span> - <span
+                                class="locale_date" data-format="YYYY/MM/DD">{{ period_end|date:'Y-m-d h:i a' }}</span>
+                                    </span>
+                    </p>
+                    {% endif %}
+                    <p>
+                        <span>{% trans "Cores" %}: </span>
+                        {% if vm.cores %}
+                        <strong class="pull-right">{{vm.cores|floatformat}}</strong>
+                        {% else %}
+                        <strong class="pull-right">{{vm.cpu|floatformat}}</strong>
+                        {% endif %}
+                    </p>
+                    <p>
+                        <span>{% trans "Memory" %}: </span>
+                        <strong class="pull-right">{{vm.memory}} GB</strong>
+                    </p>
+                    <p>
+                        <span>{% trans "Disk space" %}: </span>
+                        <strong class="pull-right">{{vm.disk_size}} GB</strong>
+                    </p>
+                </div>
+                <div class="col-sm-12">
+                    <hr class="thin-hr">
+                </div>
+                {% if vm.vat > 0 or vm.discount.amount > 0 %}
+                <div class="col-sm-6">
+                    <div class="subtotal-price">
+                        {% if vm.vat > 0 %}
+                        <p>
+                            <strong>{% trans "Subtotal" %} </strong>
+                            <strong class="pull-right">{{vm.price|floatformat:2|intcomma}}
+                                CHF</strong>
+                        </p>
+                        <p>
+                            <small>{% trans "VAT" %} ({{
+                                vm.vat_percent|floatformat:2|intcomma }}%)
+                            </small>
+                            <strong class="pull-right">{{vm.vat|floatformat:2|intcomma}}
+                                CHF</strong>
+                        </p>
+                        {% endif %}
+                        {% if vm.discount.amount > 0 %}
+                        <p class="text-primary">
+                            {%trans "Discount" as discount_name %}
+                            <strong>{{ vm.discount.name|default:discount_name
+                                }} </strong>
+                            <strong class="pull-right">- {{ vm.discount.amount
+                                }} CHF</strong>
+                        </p>
+                        {% endif %}
+                    </div>
+                </div>
+                <div class="col-sm-12">
+                    <hr class="thin-hr">
+                </div>
+                {% endif %}
+                <div class="col-sm-6">
+                    <p class="total-price">
+                        <strong>{% trans "Total" %} </strong>
+                        <strong class="pull-right">{% if vm.total_price
+                            %}{{vm.total_price|floatformat:2|intcomma}}{% else
+                            %}{{vm.price|floatformat:2|intcomma}}{% endif %}
+                            CHF</strong>
+                    </p>
+                </div>
+            </div>
+            {% else %}
+            <p>
+                <strong>{% trans "Product" %}:</strong>&nbsp;
+                {{ product_name }}
+            </p>
+            <div class="row">
+                <div class="col-sm-6">
+                    <p>
+                        <span>{% trans "Amount" %}: </span>
+                        <strong class="pull-right">{{total_in_chf|floatformat:2|intcomma}}
+                            CHF</strong>
+                    </p>
+                    {% if invoice.order.generic_payment_description %}
+                    <p>
+                        <span>{% trans "Description" %}: </span>
+                        <strong class="pull-right">{{invoice.order.generic_payment_description}}</strong>
+                    </p>
+                    {% endif %}
+                    {% if invoice.order.subscription_id %}
+                    <p>
+                        <span>{% trans "Recurring" %}: </span>
+                        <strong class="pull-right">{{invoice.order.created_at|date:'d'|ordinal}}
+                            {% trans "of every month" %}</strong>
+                    </p>
+                    {% endif %}
+                </div>
+            </div>
+            {% endif %}
+        </div>
+        <hr class="thin-hr">
+    </div>
+    <div class="order_detail_footer">
+        <strong>ungleich glarus ag</strong>&nbsp;&nbsp;Bahnhofstrasse 1, 8783
+        Linthal, Switzerland<br>
+        www.datacenterlight.ch&nbsp;&nbsp;|&nbsp;&nbsp;info@datacenterlight.ch&nbsp;&nbsp;|&nbsp;&nbsp;<small>
+        CHE-156.970.649 MWST
+    </small>
+    </div>
+    {% endif %}
+</div>
+
+<div class="text-center" style="margin-bottom: 50px;">
+    <a class="btn btn-vm-back" href="{% url 'hosting:invoices' %}">{% trans
+        "BACK TO LIST" %}</a>
+</div>
+
+<script type="text/javascript">
+    {%
+        trans
+        "Some problem encountered. Please try again later." as err_msg %
+    }
+    var create_vm_error_message = '{{err_msg|safe}}';
+</script>
+{%endblock%}
+
+{% block js_extra %}
+{% if invoice.order %}
+<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/1.3.5/jspdf.min.js"></script>
+<script src="{% static 'hosting/js/html2canvas.min.js' %}"></script>
+<script src="{% static 'hosting/js/html2pdf.min.js' %}"></script>
+<script src="{% static 'hosting/js/order.js' %}"></script>
+{% endif %}
+{% endblock js_extra %}

From d37a2de6eb5dec8c935b7b4692d87875cf60775a Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Wed, 3 Apr 2019 22:48:56 +0200
Subject: [PATCH 29/75] Add utility functions

---
 hosting/models.py | 27 +++++++++++++++++++++++++++
 1 file changed, 27 insertions(+)

diff --git a/hosting/models.py b/hosting/models.py
index 5b48abbf..d58e2fce 100644
--- a/hosting/models.py
+++ b/hosting/models.py
@@ -338,6 +338,33 @@ class MonthlyHostingBill(AssignPermissionsMixin, models.Model):
         """
         return self.total * 0.01
 
+    def discount_in_chf(self):
+        """
+        Returns discount in chf.
+
+        :return:
+        """
+        return self.discount * 0.01
+
+    def get_vm_id(self):
+        """
+        Returns the VM_ID metadata if set in this MHB else returns None
+        :return:
+        """
+        return_value = None
+        if len(self.lines_meta_data_csv) > 0:
+            vm_ids = [vm_id.strip() for vm_id in
+                      self.lines_meta_data_csv.split(",")]
+            if len(vm_ids) == 1:
+                return vm_ids[0]
+            else:
+                logger.debug(
+                    "More than one VM_ID"
+                    "for MonthlyHostingBill {}".format(self.invoice_id)
+                )
+                logger.debug("VM_IDS=".format(','.join(vm_ids)))
+        return return_value
+
 
 class VMDetail(models.Model):
     user = models.ForeignKey(CustomUser)

From ba6fa531db76bb7a8683f45cf9b76fd973f4a393 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Wed, 3 Apr 2019 22:49:25 +0200
Subject: [PATCH 30/75] Correct the name of the layout

---
 hosting/views.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/hosting/views.py b/hosting/views.py
index 17f63039..5a8f45c7 100644
--- a/hosting/views.py
+++ b/hosting/views.py
@@ -1164,7 +1164,7 @@ class InvoiceListView(LoginRequiredMixin, ListView):
 
 
 class InvoiceDetailView(LoginRequiredMixin, DetailView):
-    template_name = "hosting/invoice-detail.html"
+    template_name = "hosting/invoice_detail.html"
     context_object_name = "invoice"
     login_url = reverse_lazy('hosting:login')
     permission_required = ['view_monthlyhostingbill']

From 7de2129a0053fd799e568a7508b791a2345d2ee4 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Wed, 3 Apr 2019 22:49:45 +0200
Subject: [PATCH 31/75] Implement get invoice

---
 hosting/views.py | 76 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 76 insertions(+)

diff --git a/hosting/views.py b/hosting/views.py
index 5a8f45c7..49c78b7e 100644
--- a/hosting/views.py
+++ b/hosting/views.py
@@ -1191,6 +1191,82 @@ class InvoiceDetailView(LoginRequiredMixin, DetailView):
             invoice_obj = None
         return invoice_obj
 
+    def get_context_data(self, **kwargs):
+        # Get context
+        context = super(InvoiceDetailView, self).get_context_data(**kwargs)
+        obj = self.get_object()
+
+        if obj is not None:
+            vm_id = obj.get_vm_id()
+            try:
+                # Try to get vm details from database
+                vm_detail = VMDetail.objects.get(vm_id=vm_id)
+                context['vm'] = vm_detail.__dict__
+                context['vm']['name'] = '{}-{}'.format(
+                    context['vm']['configuration'], context['vm']['vm_id'])
+                price, vat, vat_percent, discount = get_vm_price_with_vat(
+                    cpu=context['vm']['cores'],
+                    ssd_size=context['vm']['disk_size'],
+                    memory=context['vm']['memory'],
+                    pricing_name=(obj.vm_pricing.name
+                                  if obj.vm_pricing else 'default')
+                )
+                context['vm']['vat'] = vat
+                context['vm']['price'] = price
+                context['vm']['discount'] = discount
+                context['vm']['vat_percent'] = vat_percent
+                context['vm']['total_price'] = price + vat - discount['amount']
+            except VMDetail.DoesNotExist:
+                # fallback to get it from the infrastructure
+                try:
+                    manager = OpenNebulaManager(
+                        email=self.request.email,
+                        password=self.request.password
+                    )
+                    vm = manager.get_vm(vm_id)
+                    context['vm'] = VirtualMachineSerializer(vm).data
+                    price, vat, vat_percent, discount = get_vm_price_with_vat(
+                        cpu=context['vm']['cores'],
+                        ssd_size=context['vm']['disk_size'],
+                        memory=context['vm']['memory'],
+                        pricing_name=(obj.vm_pricing.name
+                                      if obj.vm_pricing else 'default')
+                    )
+                    context['vm']['vat'] = vat
+                    context['vm']['price'] = price
+                    context['vm']['discount'] = discount
+                    context['vm']['vat_percent'] = vat_percent
+                    context['vm']['total_price'] = (
+                            price + vat - discount['amount']
+                    )
+                except WrongIdError:
+                    logger.error("WrongIdError while accessing "
+                                 "invoice {}".format(obj.invoice_id))
+                    messages.error(
+                        self.request,
+                        _('The VM you are looking for is unavailable at the '
+                          'moment. Please contact Data Center Light support.')
+                    )
+                    self.kwargs['error'] = 'WrongIdError'
+                    context['error'] = 'WrongIdError'
+                    return context
+
+                # add context params from monthly hosting bill
+                context['period_start'] = obj.period_start
+                context['period_end'] = obj.period_end
+                context['paid_at'] = obj.paid_at
+                context['total_in_chf'] = obj.total_in_chf()
+                context['invoice_number'] = obj.invoice_number
+                context['discount_on_stripe'] = obj.discount_in_chf()
+                return context
+        else:
+            raise Http404
+
+    @method_decorator(decorators)
+    def get(self, request, *args, **kwargs):
+        context = self.get_context_data()
+        return self.render_to_response(context)
+
 
 class OrdersHostingDeleteView(LoginRequiredMixin, DeleteView):
     login_url = reverse_lazy('hosting:login')

From d07f3d7eba1d54c552bfaea0d56d94b2220bec62 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Wed, 3 Apr 2019 22:59:01 +0200
Subject: [PATCH 32/75] Add missing object param

---
 hosting/views.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/hosting/views.py b/hosting/views.py
index 49c78b7e..39e2a7a9 100644
--- a/hosting/views.py
+++ b/hosting/views.py
@@ -1264,7 +1264,7 @@ class InvoiceDetailView(LoginRequiredMixin, DetailView):
 
     @method_decorator(decorators)
     def get(self, request, *args, **kwargs):
-        context = self.get_context_data()
+        context = self.get_context_data(object=self.get_object())
         return self.render_to_response(context)
 
 

From 76e3d951354f4d2278f322792be90d221566e1f3 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Wed, 3 Apr 2019 22:59:39 +0200
Subject: [PATCH 33/75] Use invoice_number of invoice pk

---
 hosting/templates/hosting/invoices.html | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/hosting/templates/hosting/invoices.html b/hosting/templates/hosting/invoices.html
index 2fa2e3f4..6a0aeb41 100644
--- a/hosting/templates/hosting/invoices.html
+++ b/hosting/templates/hosting/invoices.html
@@ -31,7 +31,7 @@
                     <td class="xs-td-bighalf locale_date" data-header="{% trans 'Date' %}">{{ invoice.paid_at | date:'Y-m-d h:i a' }}</td>
                     <td class="xs-td-smallhalf" data-header="{% trans 'Amount' %}">{{ invoice.total_in_chf|floatformat:2|intcomma }}</td>
                     <td class="text-right last-td">
-                        <a class="btn btn-order-detail" href="{% url 'hosting:invoices' invoice.pk %}">{% trans 'See Invoice' %}</a>
+                        <a class="btn btn-order-detail" href="{% url 'hosting:invoices' invoice.invoice_number %}">{% trans 'See Invoice' %}</a>
                     </td>
                 </tr>
             {% endfor %}

From 3ed5823c93651e25bf1e0749d5b0045b2b0459fe Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Wed, 3 Apr 2019 23:04:35 +0200
Subject: [PATCH 34/75] Add missing self.object initializer

---
 hosting/views.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/hosting/views.py b/hosting/views.py
index 39e2a7a9..3a8997a9 100644
--- a/hosting/views.py
+++ b/hosting/views.py
@@ -1264,6 +1264,7 @@ class InvoiceDetailView(LoginRequiredMixin, DetailView):
 
     @method_decorator(decorators)
     def get(self, request, *args, **kwargs):
+        self.object = self.get_object()
         context = self.get_context_data(object=self.get_object())
         return self.render_to_response(context)
 

From ef09ae4dab1b355630af25edd4aeefa5a0f6062a Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Wed, 3 Apr 2019 23:07:37 +0200
Subject: [PATCH 35/75] Obtaing pricing from order

---
 hosting/views.py | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/hosting/views.py b/hosting/views.py
index 3a8997a9..12b0027f 100644
--- a/hosting/views.py
+++ b/hosting/views.py
@@ -1208,8 +1208,8 @@ class InvoiceDetailView(LoginRequiredMixin, DetailView):
                     cpu=context['vm']['cores'],
                     ssd_size=context['vm']['disk_size'],
                     memory=context['vm']['memory'],
-                    pricing_name=(obj.vm_pricing.name
-                                  if obj.vm_pricing else 'default')
+                    pricing_name=(obj.order.vm_pricing.name
+                                  if obj.order.vm_pricing else 'default')
                 )
                 context['vm']['vat'] = vat
                 context['vm']['price'] = price
@@ -1229,8 +1229,8 @@ class InvoiceDetailView(LoginRequiredMixin, DetailView):
                         cpu=context['vm']['cores'],
                         ssd_size=context['vm']['disk_size'],
                         memory=context['vm']['memory'],
-                        pricing_name=(obj.vm_pricing.name
-                                      if obj.vm_pricing else 'default')
+                        pricing_name=(obj.order.vm_pricing.name
+                                      if obj.order.vm_pricing else 'default')
                     )
                     context['vm']['vat'] = vat
                     context['vm']['price'] = price

From ddd3cebc39f49aca62973921be9f7d716001332b Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Wed, 3 Apr 2019 23:09:57 +0200
Subject: [PATCH 36/75] Fix blocktrans reformatted mistakenly

---
 hosting/templates/hosting/invoice_detail.html | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/hosting/templates/hosting/invoice_detail.html b/hosting/templates/hosting/invoice_detail.html
index ff6ec31d..8a094519 100644
--- a/hosting/templates/hosting/invoice_detail.html
+++ b/hosting/templates/hosting/invoice_detail.html
@@ -14,9 +14,8 @@
     {% if not error %}
     <div class="dashboard-container-head">
         <h1 class="dashboard-title-thin">
-            <img src="{% static 'hosting/img/billing.svg' %}" class="un-icon">{%
-            blocktrans with page_header_text=page_header_text|default:"Invoice"
-            %}{{page_header_text}}{% endblocktrans %}
+            <img src="{% static 'hosting/img/billing.svg' %}" class="un-icon">
+            {% blocktrans with page_header_text=page_header_text|default:"Invoice" %}{{page_header_text}}{% endblocktrans %}
         </h1>
         {% if invoice %}
         <div class="dashboard-container-options">

From 47422a99afa1b6cccd0533915d2fefe565c1bbf7 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Wed, 3 Apr 2019 23:11:59 +0200
Subject: [PATCH 37/75] Fix more autoformatting related errors

---
 hosting/templates/hosting/invoice_detail.html | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/hosting/templates/hosting/invoice_detail.html b/hosting/templates/hosting/invoice_detail.html
index 8a094519..22d1d87a 100644
--- a/hosting/templates/hosting/invoice_detail.html
+++ b/hosting/templates/hosting/invoice_detail.html
@@ -207,7 +207,6 @@
         CHE-156.970.649 MWST
     </small>
     </div>
-    {% endif %}
 </div>
 
 <div class="text-center" style="margin-bottom: 50px;">
@@ -216,10 +215,7 @@
 </div>
 
 <script type="text/javascript">
-    {%
-        trans
-        "Some problem encountered. Please try again later." as err_msg %
-    }
+    {% trans "Some problem encountered. Please try again later." as err_msg %}
     var create_vm_error_message = '{{err_msg|safe}}';
 </script>
 {%endblock%}

From d00e84a4b69508904225ce69934a671c5cf05db5 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Wed, 3 Apr 2019 23:24:56 +0200
Subject: [PATCH 38/75] Fix bug related to proper alignment

---
 hosting/views.py | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/hosting/views.py b/hosting/views.py
index 12b0027f..ce73ad3c 100644
--- a/hosting/views.py
+++ b/hosting/views.py
@@ -1251,14 +1251,14 @@ class InvoiceDetailView(LoginRequiredMixin, DetailView):
                     context['error'] = 'WrongIdError'
                     return context
 
-                # add context params from monthly hosting bill
-                context['period_start'] = obj.period_start
-                context['period_end'] = obj.period_end
-                context['paid_at'] = obj.paid_at
-                context['total_in_chf'] = obj.total_in_chf()
-                context['invoice_number'] = obj.invoice_number
-                context['discount_on_stripe'] = obj.discount_in_chf()
-                return context
+            # add context params from monthly hosting bill
+            context['period_start'] = obj.period_start
+            context['period_end'] = obj.period_end
+            context['paid_at'] = obj.paid_at
+            context['total_in_chf'] = obj.total_in_chf()
+            context['invoice_number'] = obj.invoice_number
+            context['discount_on_stripe'] = obj.discount_in_chf()
+            return context
         else:
             raise Http404
 

From 903fee4db198be5a4e35ba5c068a9ea97cf558f2 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Wed, 3 Apr 2019 23:31:52 +0200
Subject: [PATCH 39/75] Fix more autoformatting issues

---
 hosting/templates/hosting/invoice_detail.html | 13 ++++---------
 1 file changed, 4 insertions(+), 9 deletions(-)

diff --git a/hosting/templates/hosting/invoice_detail.html b/hosting/templates/hosting/invoice_detail.html
index 22d1d87a..9c300b3f 100644
--- a/hosting/templates/hosting/invoice_detail.html
+++ b/hosting/templates/hosting/invoice_detail.html
@@ -137,8 +137,7 @@
                                 CHF</strong>
                         </p>
                         <p>
-                            <small>{% trans "VAT" %} ({{
-                                vm.vat_percent|floatformat:2|intcomma }}%)
+                            <small>{% trans "VAT" %} ({{ vm.vat_percent|floatformat:2|intcomma }}%)
                             </small>
                             <strong class="pull-right">{{vm.vat|floatformat:2|intcomma}}
                                 CHF</strong>
@@ -147,10 +146,8 @@
                         {% if vm.discount.amount > 0 %}
                         <p class="text-primary">
                             {%trans "Discount" as discount_name %}
-                            <strong>{{ vm.discount.name|default:discount_name
-                                }} </strong>
-                            <strong class="pull-right">- {{ vm.discount.amount
-                                }} CHF</strong>
+                            <strong>{{ vm.discount.name|default:discount_name }} </strong>
+                            <strong class="pull-right">- {{ vm.discount.amount }} CHF</strong>
                         </p>
                         {% endif %}
                     </div>
@@ -162,9 +159,7 @@
                 <div class="col-sm-6">
                     <p class="total-price">
                         <strong>{% trans "Total" %} </strong>
-                        <strong class="pull-right">{% if vm.total_price
-                            %}{{vm.total_price|floatformat:2|intcomma}}{% else
-                            %}{{vm.price|floatformat:2|intcomma}}{% endif %}
+                        <strong class="pull-right">{% if vm.total_price %}{{vm.total_price|floatformat:2|intcomma}}{% else %}{{vm.price|floatformat:2|intcomma}}{% endif %}
                             CHF</strong>
                     </p>
                 </div>

From f1a7958f03e3c61eef6e4b3dd4028066c273c353 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Wed, 3 Apr 2019 23:34:26 +0200
Subject: [PATCH 40/75] Use correct class

---
 hosting/views.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/hosting/views.py b/hosting/views.py
index ce73ad3c..34e720f6 100644
--- a/hosting/views.py
+++ b/hosting/views.py
@@ -1184,7 +1184,7 @@ class InvoiceDetailView(LoginRequiredMixin, DetailView):
             else:
                 logger.error("User does not have permission to access")
                 invoice_obj = None
-        except HostingOrder.DoesNotExist:
+        except MonthlyHostingBill.DoesNotExist:
             logger.debug("MHB not found for id {invoice_id}".format(
                 invoice_id=invoice_id
             ))

From baf62f1924c4079bb01bbb8fb0d629223fdd4661 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Wed, 3 Apr 2019 23:54:52 +0200
Subject: [PATCH 41/75] Simplify showing total price

---
 hosting/templates/hosting/invoice_detail.html | 6 ++----
 hosting/views.py                              | 6 ++++--
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/hosting/templates/hosting/invoice_detail.html b/hosting/templates/hosting/invoice_detail.html
index 9c300b3f..80226123 100644
--- a/hosting/templates/hosting/invoice_detail.html
+++ b/hosting/templates/hosting/invoice_detail.html
@@ -139,8 +139,7 @@
                         <p>
                             <small>{% trans "VAT" %} ({{ vm.vat_percent|floatformat:2|intcomma }}%)
                             </small>
-                            <strong class="pull-right">{{vm.vat|floatformat:2|intcomma}}
-                                CHF</strong>
+                            <strong class="pull-right">{{vm.vat|floatformat:2|intcomma}} CHF</strong>
                         </p>
                         {% endif %}
                         {% if vm.discount.amount > 0 %}
@@ -159,8 +158,7 @@
                 <div class="col-sm-6">
                     <p class="total-price">
                         <strong>{% trans "Total" %} </strong>
-                        <strong class="pull-right">{% if vm.total_price %}{{vm.total_price|floatformat:2|intcomma}}{% else %}{{vm.price|floatformat:2|intcomma}}{% endif %}
-                            CHF</strong>
+                        <strong class="pull-right">{{total_in_chf}} CHF</strong>
                     </p>
                 </div>
             </div>
diff --git a/hosting/views.py b/hosting/views.py
index 34e720f6..af01ae86 100644
--- a/hosting/views.py
+++ b/hosting/views.py
@@ -1173,7 +1173,9 @@ class InvoiceDetailView(LoginRequiredMixin, DetailView):
     def get_object(self, queryset=None):
         invoice_id = self.kwargs.get('invoice_id')
         try:
-            invoice_obj = MonthlyHostingBill.objects.get(invoice_number=invoice_id)
+            invoice_obj = MonthlyHostingBill.objects.get(
+                invoice_number=invoice_id
+            )
             logger.debug("Found MHB for id {invoice_id}".format(
                 invoice_id=invoice_id
             ))
@@ -1184,7 +1186,7 @@ class InvoiceDetailView(LoginRequiredMixin, DetailView):
             else:
                 logger.error("User does not have permission to access")
                 invoice_obj = None
-        except MonthlyHostingBill.DoesNotExist:
+        except MonthlyHostingBill.DoesNotExist as dne:
             logger.debug("MHB not found for id {invoice_id}".format(
                 invoice_id=invoice_id
             ))

From 13f84a8580cda21cf55675fb1cf1e6d3a41ee248 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Thu, 4 Apr 2019 00:05:20 +0200
Subject: [PATCH 42/75] Add missing endif

---
 hosting/templates/hosting/invoice_detail.html | 1 +
 1 file changed, 1 insertion(+)

diff --git a/hosting/templates/hosting/invoice_detail.html b/hosting/templates/hosting/invoice_detail.html
index 80226123..0a2473e3 100644
--- a/hosting/templates/hosting/invoice_detail.html
+++ b/hosting/templates/hosting/invoice_detail.html
@@ -200,6 +200,7 @@
         CHE-156.970.649 MWST
     </small>
     </div>
+    {% endif %}
 </div>
 
 <div class="text-center" style="margin-bottom: 50px;">

From ef1bdee9a7da7e341f6e44d7046f7b544cc0f5cd Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Thu, 4 Apr 2019 00:05:45 +0200
Subject: [PATCH 43/75] Remove more autoformatting

---
 hosting/templates/hosting/invoice_detail.html | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/hosting/templates/hosting/invoice_detail.html b/hosting/templates/hosting/invoice_detail.html
index 0a2473e3..675962fa 100644
--- a/hosting/templates/hosting/invoice_detail.html
+++ b/hosting/templates/hosting/invoice_detail.html
@@ -204,8 +204,7 @@
 </div>
 
 <div class="text-center" style="margin-bottom: 50px;">
-    <a class="btn btn-vm-back" href="{% url 'hosting:invoices' %}">{% trans
-        "BACK TO LIST" %}</a>
+    <a class="btn btn-vm-back" href="{% url 'hosting:invoices' %}">{% trans "BACK TO LIST" %}</a>
 </div>
 
 <script type="text/javascript">

From 8f4a02738f36088e4c3c52d0b13ee6d7a0d6926e Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Thu, 4 Apr 2019 00:15:04 +0200
Subject: [PATCH 44/75] Revert back to old price values

---
 hosting/templates/hosting/invoice_detail.html | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/hosting/templates/hosting/invoice_detail.html b/hosting/templates/hosting/invoice_detail.html
index 675962fa..d3e8b797 100644
--- a/hosting/templates/hosting/invoice_detail.html
+++ b/hosting/templates/hosting/invoice_detail.html
@@ -158,7 +158,8 @@
                 <div class="col-sm-6">
                     <p class="total-price">
                         <strong>{% trans "Total" %} </strong>
-                        <strong class="pull-right">{{total_in_chf}} CHF</strong>
+                        <strong class="pull-right">{% if vm.total_price %}{{vm.total_price|floatformat:2|intcomma}}{% else %}{{vm.price|floatformat:2|intcomma}}{% endif %}
+                            CHF</strong>
                     </p>
                 </div>
             </div>

From e58dcbb44c5786ae72fff1f0baf1de0cf4b5615c Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Thu, 4 Apr 2019 06:41:23 +0200
Subject: [PATCH 45/75] hosting urls: Change invoices/<pk>/ to invoice/<pk>

---
 hosting/urls.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/hosting/urls.py b/hosting/urls.py
index 3f5a6f50..a3579f06 100644
--- a/hosting/urls.py
+++ b/hosting/urls.py
@@ -28,7 +28,7 @@ urlpatterns = [
         name='order-confirmation'),
     url(r'orders/(?P<pk>\d+)/?$', OrdersHostingDetailView.as_view(),
         name='orders'),
-    url(r'invoices/(?P<invoice_id>[-\w]+)/?$', InvoiceDetailView.as_view(),
+    url(r'invoice/(?P<invoice_id>[-\w]+)/?$', InvoiceDetailView.as_view(),
         name='invoices'),
     url(r'bills/?$', HostingBillListView.as_view(), name='bills'),
     url(r'bills/(?P<pk>\d+)/?$', HostingBillDetailView.as_view(),

From f907837f04c5557e04188398d352a8681477ba4f Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Thu, 4 Apr 2019 06:43:47 +0200
Subject: [PATCH 46/75] dashboard: Link 'My bills' to invoices

---
 hosting/templates/hosting/dashboard.html | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/hosting/templates/hosting/dashboard.html b/hosting/templates/hosting/dashboard.html
index d12f75ee..d0204820 100644
--- a/hosting/templates/hosting/dashboard.html
+++ b/hosting/templates/hosting/dashboard.html
@@ -26,7 +26,7 @@
                         <img class="svg-img" src="{% static 'hosting/img/key.svg' %}">
                     </div>
                 </a>
-                <a href="{% url 'hosting:orders' %}" class="hosting-dashboard-item">
+                <a href="{% url 'hosting:invoices' %}" class="hosting-dashboard-item">
                     <h2>{% trans "My Bills" %}</h2>
                     <div class="hosting-dashboard-image">
                         <img class="svg-img" src="{% static 'hosting/img/billing.svg' %}">

From f50e5dcaa47cb0eea0f4024016c4048c1fbd5a91 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Thu, 4 Apr 2019 06:49:46 +0200
Subject: [PATCH 47/75] Add ADMIN_EMAIL setting

---
 dynamicweb/settings/base.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/dynamicweb/settings/base.py b/dynamicweb/settings/base.py
index 49570143..40b8fd69 100644
--- a/dynamicweb/settings/base.py
+++ b/dynamicweb/settings/base.py
@@ -648,6 +648,7 @@ CELERY_RESULT_SERIALIZER = 'json'
 CELERY_MAX_RETRIES = int_env('CELERY_MAX_RETRIES', 5)
 
 DCL_ERROR_EMAILS_TO = env('DCL_ERROR_EMAILS_TO')
+ADMIN_EMAIL = env('ADMIN_EMAIL')
 
 DCL_ERROR_EMAILS_TO_LIST = []
 if DCL_ERROR_EMAILS_TO is not None:

From d4d31dced97aed83cca85c275791b7f307ba8d54 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Thu, 4 Apr 2019 06:50:13 +0200
Subject: [PATCH 48/75] Attempt invoice pdf with invoice_id instead of pk

---
 hosting/templates/hosting/invoice_detail.html | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/hosting/templates/hosting/invoice_detail.html b/hosting/templates/hosting/invoice_detail.html
index d3e8b797..1459bf04 100644
--- a/hosting/templates/hosting/invoice_detail.html
+++ b/hosting/templates/hosting/invoice_detail.html
@@ -3,7 +3,7 @@
 
 
 {% block content %}
-<div id="order-detail{{invoice.pk}}" class="order-detail-container">
+<div id="order-detail{{invoice.invoice_id}}" class="order-detail-container">
     {% if messages %}
     <div class="alert alert-warning">
         {% for message in messages %}
@@ -20,7 +20,7 @@
         {% if invoice %}
         <div class="dashboard-container-options">
             <button type="button" class="btn-plain btn-pdf"
-                    data-target="#order-detail{{invoice.pk}}"><img
+                    data-target="#order-detail{{invoice.invoice_id}}"><img
                     src="{% static 'hosting/img/icon-pdf.svg' %}"
                     class="svg-img"></button>
             <button type="button" class="btn-plain btn-print"><img

From 8b6619f7883a12ec9e04415284cb317963d2871b Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Thu, 4 Apr 2019 06:52:34 +0200
Subject: [PATCH 49/75] Invoice pdf generation: use invoice number and not id

---
 hosting/templates/hosting/invoice_detail.html | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/hosting/templates/hosting/invoice_detail.html b/hosting/templates/hosting/invoice_detail.html
index 1459bf04..335dfcfb 100644
--- a/hosting/templates/hosting/invoice_detail.html
+++ b/hosting/templates/hosting/invoice_detail.html
@@ -3,7 +3,7 @@
 
 
 {% block content %}
-<div id="order-detail{{invoice.invoice_id}}" class="order-detail-container">
+<div id="order-detail{{invoice.invoice_number}}" class="order-detail-container">
     {% if messages %}
     <div class="alert alert-warning">
         {% for message in messages %}
@@ -20,7 +20,7 @@
         {% if invoice %}
         <div class="dashboard-container-options">
             <button type="button" class="btn-plain btn-pdf"
-                    data-target="#order-detail{{invoice.invoice_id}}"><img
+                    data-target="#order-detail{{invoice.invoice_number}}"><img
                     src="{% static 'hosting/img/icon-pdf.svg' %}"
                     class="svg-img"></button>
             <button type="button" class="btn-plain btn-print"><img

From 0c4e0f1070f64f1b8eb7337002714c88b46a881c Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Thu, 4 Apr 2019 07:14:12 +0200
Subject: [PATCH 50/75] Show period in invoices page

---
 hosting/templates/hosting/invoices.html | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/hosting/templates/hosting/invoices.html b/hosting/templates/hosting/invoices.html
index 6a0aeb41..590d061d 100644
--- a/hosting/templates/hosting/invoices.html
+++ b/hosting/templates/hosting/invoices.html
@@ -19,6 +19,7 @@
         <thead>
             <tr>
                 <th>{% trans "Invoice Nr." %}</th>
+                <th>{% trans "Period" %}</th>
                 <th>{% trans "Date" %}</th>
                 <th>{% trans "Amount" %}</th>
                 <th></th>
@@ -28,6 +29,7 @@
             {% for invoice in invoices %}
                 <tr>
                     <td class="xs-td-inline" data-header="{% trans 'Invoice Nr.' %}">{{ invoice.invoice_number }}</td>
+                    <td class="xs-td-inline" data-header="{% trans 'Period' %}">{{ invoice.period_start | date: 'Y-m-d' }} &mdash; {{ invoice.period_end | date: 'Y-m-d' }}</td>
                     <td class="xs-td-bighalf locale_date" data-header="{% trans 'Date' %}">{{ invoice.paid_at | date:'Y-m-d h:i a' }}</td>
                     <td class="xs-td-smallhalf" data-header="{% trans 'Amount' %}">{{ invoice.total_in_chf|floatformat:2|intcomma }}</td>
                     <td class="text-right last-td">

From bb7107fe808a4ea1e670b281ace0c8f8c484fc47 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Thu, 4 Apr 2019 07:28:50 +0200
Subject: [PATCH 51/75] Remove erroneous space before filter param

---
 hosting/templates/hosting/invoices.html | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/hosting/templates/hosting/invoices.html b/hosting/templates/hosting/invoices.html
index 590d061d..073eb750 100644
--- a/hosting/templates/hosting/invoices.html
+++ b/hosting/templates/hosting/invoices.html
@@ -29,7 +29,7 @@
             {% for invoice in invoices %}
                 <tr>
                     <td class="xs-td-inline" data-header="{% trans 'Invoice Nr.' %}">{{ invoice.invoice_number }}</td>
-                    <td class="xs-td-inline" data-header="{% trans 'Period' %}">{{ invoice.period_start | date: 'Y-m-d' }} &mdash; {{ invoice.period_end | date: 'Y-m-d' }}</td>
+                    <td class="xs-td-inline" data-header="{% trans 'Period' %}">{{ invoice.period_start | date:'Y-m-d' }} &mdash; {{ invoice.period_end | date:'Y-m-d' }}</td>
                     <td class="xs-td-bighalf locale_date" data-header="{% trans 'Date' %}">{{ invoice.paid_at | date:'Y-m-d h:i a' }}</td>
                     <td class="xs-td-smallhalf" data-header="{% trans 'Amount' %}">{{ invoice.total_in_chf|floatformat:2|intcomma }}</td>
                     <td class="text-right last-td">

From 316646465df8d4eb3ee150a6453937f294e53ae7 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Thu, 4 Apr 2019 07:46:28 +0200
Subject: [PATCH 52/75] Use invoice's username and email

---
 hosting/templates/hosting/invoice_detail.html | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/hosting/templates/hosting/invoice_detail.html b/hosting/templates/hosting/invoice_detail.html
index 335dfcfb..e63f25a7 100644
--- a/hosting/templates/hosting/invoice_detail.html
+++ b/hosting/templates/hosting/invoice_detail.html
@@ -65,7 +65,7 @@
                 <h4>{% trans "Billed to" %}:</h4>
                 <p>
                     {% if invoice.order %}
-                    {{user.name}}<br>
+                    {{invoice.customer.user.name}}<br>
                     {{invoice.order.billing_address.street_address}},
                     {{invoice.order.billing_address.postal_code}}<br>
                     {{invoice.order.billing_address.city}},
@@ -81,7 +81,7 @@
                 {% if invoice.order %}
                 {{invoice.order.cc_brand}} {% trans "ending in" %} ****
                 {{invoice.order.last4}}<br>
-                {{user.email}}
+                {{invoice.customer.user.email}}
                 {% endif %}
             </p>
         </div>

From 8dfee2bd83b4e2ff10d0f3b8e9dc65b6529d960f Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Thu, 4 Apr 2019 07:55:53 +0200
Subject: [PATCH 53/75] Show invoices only if the user's invoice were imported

Else fallback to orders url as before
---
 hosting/templates/hosting/dashboard.html | 2 +-
 hosting/views.py                         | 8 ++++++++
 2 files changed, 9 insertions(+), 1 deletion(-)

diff --git a/hosting/templates/hosting/dashboard.html b/hosting/templates/hosting/dashboard.html
index d0204820..35ee9b6e 100644
--- a/hosting/templates/hosting/dashboard.html
+++ b/hosting/templates/hosting/dashboard.html
@@ -26,7 +26,7 @@
                         <img class="svg-img" src="{% static 'hosting/img/key.svg' %}">
                     </div>
                 </a>
-                <a href="{% url 'hosting:invoices' %}" class="hosting-dashboard-item">
+                <a href="{% if has_invoices %}{% url 'hosting:invoices' %}{% else %}{% url 'hosting:orders' %}{% endif %}" class="hosting-dashboard-item">
                     <h2>{% trans "My Bills" %}</h2>
                     <div class="hosting-dashboard-image">
                         <img class="svg-img" src="{% static 'hosting/img/billing.svg' %}">
diff --git a/hosting/views.py b/hosting/views.py
index af01ae86..6cdabecf 100644
--- a/hosting/views.py
+++ b/hosting/views.py
@@ -83,6 +83,14 @@ class DashboardView(LoginRequiredMixin, View):
     @method_decorator(decorators)
     def get(self, request, *args, **kwargs):
         context = self.get_context_data()
+        try:
+            MonthlyHostingBill.objects.get(email=self.request.user.email)
+            context['has_invoices'] = True
+        except MonthlyHostingBill.DoesNotExist as dne:
+            logger.error("{}'s monthly hosting bill not imported ?".format(
+                self.request.user.email
+            ))
+            context['has_invoices'] = False
         return render(request, self.template_name, context)
 
 

From a59cd86fee87db3e63db1b33544d8af4d40dc60e Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Thu, 4 Apr 2019 07:59:38 +0200
Subject: [PATCH 54/75] Fix error: get mab using customer and not email

---
 hosting/views.py | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/hosting/views.py b/hosting/views.py
index 6cdabecf..45fd845f 100644
--- a/hosting/views.py
+++ b/hosting/views.py
@@ -84,7 +84,9 @@ class DashboardView(LoginRequiredMixin, View):
     def get(self, request, *args, **kwargs):
         context = self.get_context_data()
         try:
-            MonthlyHostingBill.objects.get(email=self.request.user.email)
+            MonthlyHostingBill.objects.get(
+                customer=self.request.user.stripecustomer
+            )
             context['has_invoices'] = True
         except MonthlyHostingBill.DoesNotExist as dne:
             logger.error("{}'s monthly hosting bill not imported ?".format(

From 6d4af0c193dd871a054adeb77b741296608a508b Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Thu, 4 Apr 2019 08:10:08 +0200
Subject: [PATCH 55/75] Fix getting number of mabs of a user

---
 hosting/views.py | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/hosting/views.py b/hosting/views.py
index 45fd845f..b3b5c1f8 100644
--- a/hosting/views.py
+++ b/hosting/views.py
@@ -83,16 +83,17 @@ class DashboardView(LoginRequiredMixin, View):
     @method_decorator(decorators)
     def get(self, request, *args, **kwargs):
         context = self.get_context_data()
+        context['has_invoices'] = False
         try:
-            MonthlyHostingBill.objects.get(
+            bills = MonthlyHostingBill.objects.filter(
                 customer=self.request.user.stripecustomer
             )
-            context['has_invoices'] = True
+            if len(bills) > 0:
+                context['has_invoices'] = True
         except MonthlyHostingBill.DoesNotExist as dne:
             logger.error("{}'s monthly hosting bill not imported ?".format(
                 self.request.user.email
             ))
-            context['has_invoices'] = False
         return render(request, self.template_name, context)
 
 

From 73f783174495727bfd3cc1bd9b00a4e5f1fe8c4e Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Thu, 4 Apr 2019 23:14:24 +0200
Subject: [PATCH 56/75] Attempt to show IP address instead of invoice number

---
 hosting/templates/hosting/invoices.html |  4 ++--
 hosting/views.py                        | 18 ++++++++++++++++++
 2 files changed, 20 insertions(+), 2 deletions(-)

diff --git a/hosting/templates/hosting/invoices.html b/hosting/templates/hosting/invoices.html
index 073eb750..67159e54 100644
--- a/hosting/templates/hosting/invoices.html
+++ b/hosting/templates/hosting/invoices.html
@@ -18,7 +18,7 @@
     <table class="table table-switch">
         <thead>
             <tr>
-                <th>{% trans "Invoice Nr." %}</th>
+                <th>{% trans "IP Address" %}</th>
                 <th>{% trans "Period" %}</th>
                 <th>{% trans "Date" %}</th>
                 <th>{% trans "Amount" %}</th>
@@ -28,7 +28,7 @@
         <tbody>
             {% for invoice in invoices %}
                 <tr>
-                    <td class="xs-td-inline" data-header="{% trans 'Invoice Nr.' %}">{{ invoice.invoice_number }}</td>
+                    <td class="xs-td-inline" data-header="{% trans 'IP Address' %}">{{ ips|get_value_from_dict:invoice.invoice_number|join:"<br/>" }}</td>
                     <td class="xs-td-inline" data-header="{% trans 'Period' %}">{{ invoice.period_start | date:'Y-m-d' }} &mdash; {{ invoice.period_end | date:'Y-m-d' }}</td>
                     <td class="xs-td-bighalf locale_date" data-header="{% trans 'Date' %}">{{ invoice.paid_at | date:'Y-m-d h:i a' }}</td>
                     <td class="xs-td-smallhalf" data-header="{% trans 'Amount' %}">{{ invoice.total_in_chf|floatformat:2|intcomma }}</td>
diff --git a/hosting/views.py b/hosting/views.py
index b3b5c1f8..75f273ed 100644
--- a/hosting/views.py
+++ b/hosting/views.py
@@ -1164,6 +1164,24 @@ class InvoiceListView(LoginRequiredMixin, ListView):
     model = MonthlyHostingBill
     ordering = '-created'
 
+    def get_context_data(self, **kwargs):
+        context = super(InvoiceListView, self).get_context_data(**kwargs)
+        mabs = MonthlyHostingBill.objects.filter(
+            customer__user=self.request.user
+        )
+        ips_dict = {}
+        for mab in mabs:
+            try:
+                vm_detail = VMDetail.get(vm_id=mab.order.vm_id)
+                ips_dict[mab.invoice_number] = [vm_detail.ipv6, vm_detail.ipv4]
+            except VMDetail.DoesNotExist as dne:
+                ips_dict[mab.invoice_number] = ['--']
+                logger.debug("VMDetail for {} doesn't exist".format(
+                    mab.order.vm_id
+                ))
+        context['ips'] = ips_dict
+        return context
+
     def get_queryset(self):
         user = self.request.user
         self.queryset = MonthlyHostingBill.objects.filter(customer__user=user)

From 3f3f47888d5ef75aa73a1ac11ce765a0c0dac0e2 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Thu, 4 Apr 2019 23:20:11 +0200
Subject: [PATCH 57/75] Fix missing codes

---
 hosting/templates/hosting/invoices.html | 2 +-
 hosting/views.py                        | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/hosting/templates/hosting/invoices.html b/hosting/templates/hosting/invoices.html
index 67159e54..ed4e5ab9 100644
--- a/hosting/templates/hosting/invoices.html
+++ b/hosting/templates/hosting/invoices.html
@@ -1,5 +1,5 @@
 {% extends "hosting/base_short.html" %}
-{% load staticfiles bootstrap3 humanize i18n %}
+{% load staticfiles bootstrap3 humanize i18n custom_tags %}
 
 {% block content %}
 <div class="dashboard-container">
diff --git a/hosting/views.py b/hosting/views.py
index 75f273ed..0afdc909 100644
--- a/hosting/views.py
+++ b/hosting/views.py
@@ -1172,7 +1172,7 @@ class InvoiceListView(LoginRequiredMixin, ListView):
         ips_dict = {}
         for mab in mabs:
             try:
-                vm_detail = VMDetail.get(vm_id=mab.order.vm_id)
+                vm_detail = VMDetail.objects.get(vm_id=mab.order.vm_id)
                 ips_dict[mab.invoice_number] = [vm_detail.ipv6, vm_detail.ipv4]
             except VMDetail.DoesNotExist as dne:
                 ips_dict[mab.invoice_number] = ['--']

From 5987962414e2536ac9e4e98b4807797c8a340a0b Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Thu, 4 Apr 2019 23:50:49 +0200
Subject: [PATCH 58/75] Add VM ID to invoices list

---
 hosting/templates/hosting/invoices.html | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/hosting/templates/hosting/invoices.html b/hosting/templates/hosting/invoices.html
index ed4e5ab9..eaf5d22d 100644
--- a/hosting/templates/hosting/invoices.html
+++ b/hosting/templates/hosting/invoices.html
@@ -18,9 +18,9 @@
     <table class="table table-switch">
         <thead>
             <tr>
+                <th>{% trans "VM ID" %}</th>
                 <th>{% trans "IP Address" %}</th>
                 <th>{% trans "Period" %}</th>
-                <th>{% trans "Date" %}</th>
                 <th>{% trans "Amount" %}</th>
                 <th></th>
             </tr>
@@ -28,10 +28,10 @@
         <tbody>
             {% for invoice in invoices %}
                 <tr>
+                    <td class="xs-td-inline" data-header="{% trans 'VM ID' %}">{{ invoice.order.vm_id }}</td>
                     <td class="xs-td-inline" data-header="{% trans 'IP Address' %}">{{ ips|get_value_from_dict:invoice.invoice_number|join:"<br/>" }}</td>
                     <td class="xs-td-inline" data-header="{% trans 'Period' %}">{{ invoice.period_start | date:'Y-m-d' }} &mdash; {{ invoice.period_end | date:'Y-m-d' }}</td>
-                    <td class="xs-td-bighalf locale_date" data-header="{% trans 'Date' %}">{{ invoice.paid_at | date:'Y-m-d h:i a' }}</td>
-                    <td class="xs-td-smallhalf" data-header="{% trans 'Amount' %}">{{ invoice.total_in_chf|floatformat:2|intcomma }}</td>
+                    <td class="xs-td-inline" data-header="{% trans 'Amount' %}">{{ invoice.total_in_chf|floatformat:2|intcomma }}</td>
                     <td class="text-right last-td">
                         <a class="btn btn-order-detail" href="{% url 'hosting:invoices' invoice.invoice_number %}">{% trans 'See Invoice' %}</a>
                     </td>

From 92b2504f1e995d901e70aac2e29184e11d275861 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Fri, 5 Apr 2019 00:09:52 +0200
Subject: [PATCH 59/75] Paginate invoice list view by 10 items on each page

---
 hosting/views.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/hosting/views.py b/hosting/views.py
index 0afdc909..c031a623 100644
--- a/hosting/views.py
+++ b/hosting/views.py
@@ -1162,6 +1162,7 @@ class InvoiceListView(LoginRequiredMixin, ListView):
     login_url = reverse_lazy('hosting:login')
     context_object_name = "invoices"
     model = MonthlyHostingBill
+    paginate_by = 10
     ordering = '-created'
 
     def get_context_data(self, **kwargs):

From 8c04acaff8431e2140054f3864d7f10d583803ce Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Sat, 6 Apr 2019 14:07:26 +0200
Subject: [PATCH 60/75] Check if user has stripecustomer attr

---
 hosting/views.py | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/hosting/views.py b/hosting/views.py
index c031a623..484cbe32 100644
--- a/hosting/views.py
+++ b/hosting/views.py
@@ -85,9 +85,11 @@ class DashboardView(LoginRequiredMixin, View):
         context = self.get_context_data()
         context['has_invoices'] = False
         try:
-            bills = MonthlyHostingBill.objects.filter(
-                customer=self.request.user.stripecustomer
-            )
+            bills = []
+            if hasattr(self.request.user, 'stripecustomer'):
+                bills = MonthlyHostingBill.objects.filter(
+                    customer=self.request.user.stripecustomer
+                )
             if len(bills) > 0:
                 context['has_invoices'] = True
         except MonthlyHostingBill.DoesNotExist as dne:

From 6a7b5459cebc5cd4d065b7b10b6b1981da3eddcf Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Fri, 12 Apr 2019 08:14:52 +0200
Subject: [PATCH 61/75] Allow admin to list invoices

---
 hosting/views.py | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/hosting/views.py b/hosting/views.py
index 484cbe32..15476fd4 100644
--- a/hosting/views.py
+++ b/hosting/views.py
@@ -1169,9 +1169,15 @@ class InvoiceListView(LoginRequiredMixin, ListView):
 
     def get_context_data(self, **kwargs):
         context = super(InvoiceListView, self).get_context_data(**kwargs)
-        mabs = MonthlyHostingBill.objects.filter(
-            customer__user=self.request.user
-        )
+        if ('user_email' in self.request.GET
+            and self.request.user.email == settings.ADMIN_EMAIL):
+            mabs = MonthlyHostingBill.objects.filter(
+                customer__user=self.request.GET['user_email']
+            )
+        else:
+            mabs = MonthlyHostingBill.objects.filter(
+                customer__user=self.request.user
+            )
         ips_dict = {}
         for mab in mabs:
             try:

From c232f4c1da66b8839fd7cb1a3ccb4ce39f6090b7 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Fri, 12 Apr 2019 08:41:05 +0200
Subject: [PATCH 62/75] Fix error in obtaining customuser for mab

---
 hosting/views.py | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/hosting/views.py b/hosting/views.py
index 15476fd4..6f41eaa8 100644
--- a/hosting/views.py
+++ b/hosting/views.py
@@ -1171,9 +1171,16 @@ class InvoiceListView(LoginRequiredMixin, ListView):
         context = super(InvoiceListView, self).get_context_data(**kwargs)
         if ('user_email' in self.request.GET
             and self.request.user.email == settings.ADMIN_EMAIL):
-            mabs = MonthlyHostingBill.objects.filter(
-                customer__user=self.request.GET['user_email']
+            user_email = self.request.GET['user_email']
+            logger.debug(
+                "user_email = {}".format(user_email)
             )
+            try:
+                cu = CustomUser.objects.get(user_email)
+            except CustomUser.DoesNotExist as dne:
+                logger.debug("User does not exist")
+                cu = self.request.user
+            mabs = MonthlyHostingBill.objects.filter(customer__user=cu)
         else:
             mabs = MonthlyHostingBill.objects.filter(
                 customer__user=self.request.user

From fefcdb56479d1e20747d1c1aba9bdb131e7a7d00 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Fri, 12 Apr 2019 09:10:19 +0200
Subject: [PATCH 63/75] Pass correct key email

---
 hosting/views.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/hosting/views.py b/hosting/views.py
index 6f41eaa8..f6114ffd 100644
--- a/hosting/views.py
+++ b/hosting/views.py
@@ -1176,7 +1176,7 @@ class InvoiceListView(LoginRequiredMixin, ListView):
                 "user_email = {}".format(user_email)
             )
             try:
-                cu = CustomUser.objects.get(user_email)
+                cu = CustomUser.objects.get(email=user_email)
             except CustomUser.DoesNotExist as dne:
                 logger.debug("User does not exist")
                 cu = self.request.user

From 9ee21b9bc370ea7129a8013a90d50223f3e4419d Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Sat, 13 Apr 2019 12:37:37 +0200
Subject: [PATCH 64/75] Allow admin to see invoices

---
 hosting/views.py | 18 ++++++++++++++++--
 1 file changed, 16 insertions(+), 2 deletions(-)

diff --git a/hosting/views.py b/hosting/views.py
index f6114ffd..5e80a39a 100644
--- a/hosting/views.py
+++ b/hosting/views.py
@@ -1163,7 +1163,6 @@ class InvoiceListView(LoginRequiredMixin, ListView):
     template_name = "hosting/invoices.html"
     login_url = reverse_lazy('hosting:login')
     context_object_name = "invoices"
-    model = MonthlyHostingBill
     paginate_by = 10
     ordering = '-created'
 
@@ -1200,7 +1199,22 @@ class InvoiceListView(LoginRequiredMixin, ListView):
 
     def get_queryset(self):
         user = self.request.user
-        self.queryset = MonthlyHostingBill.objects.filter(customer__user=user)
+        if ('user_email' in self.request.GET
+            and self.request.user.email == settings.ADMIN_EMAIL):
+            user_email = self.request.GET['user_email']
+            logger.debug(
+                "user_email = {}".format(user_email)
+            )
+            try:
+                cu = CustomUser.objects.get(email=user_email)
+            except CustomUser.DoesNotExist as dne:
+                logger.debug("User does not exist")
+                cu = self.request.user
+            self.queryset = MonthlyHostingBill.objects.filter(customer__user=cu)
+        else:
+            self.queryset = MonthlyHostingBill.objects.filter(
+                customer__user=self.request.user
+            )
         return super(InvoiceListView, self).get_queryset()
 
     @method_decorator(decorators)

From 061ef7d0368b76a4b8829eb9ef4c26f917e37e6d Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Sat, 13 Apr 2019 12:54:57 +0200
Subject: [PATCH 65/75] Fetch invoices whose date is greater than given date
 only

---
 hosting/management/commands/fetch_stripe_bills.py | 2 +-
 utils/stripe_utils.py                             | 6 +++---
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/hosting/management/commands/fetch_stripe_bills.py b/hosting/management/commands/fetch_stripe_bills.py
index 2a37ed61..e321179d 100644
--- a/hosting/management/commands/fetch_stripe_bills.py
+++ b/hosting/management/commands/fetch_stripe_bills.py
@@ -35,7 +35,7 @@ class Command(BaseCommand):
 
                     all_invoices_response = stripe_utils.get_all_invoices(
                         user.stripecustomer.stripe_id,
-                        created=created_gt
+                        created_gt=created_gt
                     )
                     if all_invoices_response['error'] is not None:
                         self.stdout.write(self.style.ERROR(all_invoices_response['error']))
diff --git a/utils/stripe_utils.py b/utils/stripe_utils.py
index b43470fa..577eab80 100644
--- a/utils/stripe_utils.py
+++ b/utils/stripe_utils.py
@@ -123,19 +123,19 @@ class StripeUtils(object):
         return card_details
 
     @handleStripeError
-    def get_all_invoices(self, customer_id, created):
+    def get_all_invoices(self, customer_id, created_gt):
         return_list = []
         has_more_invoices = True
         starting_after = False
         while has_more_invoices:
             if starting_after:
                 invoices = stripe.Invoice.list(
-                    limit=10, customer=customer_id, created=created,
+                    limit=10, customer=customer_id, created={'gt': created_gt},
                     starting_after=starting_after
                 )
             else:
                 invoices = stripe.Invoice.list(
-                    limit=10, customer=customer_id, created=created
+                    limit=10, customer=customer_id, created={'gt': created_gt}
                 )
             has_more_invoices = invoices.has_more
             for invoice in invoices.data:

From 8816793803b797aee64a47a5a284289648914175 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Sat, 13 Apr 2019 13:42:04 +0200
Subject: [PATCH 66/75] Add Line item

---
 .../migrations/0052_hostingbilllineitem.py    | 37 +++++++++++++++++
 hosting/models.py                             | 41 +++++++++++++++++++
 utils/stripe_utils.py                         |  1 +
 3 files changed, 79 insertions(+)
 create mode 100644 hosting/migrations/0052_hostingbilllineitem.py

diff --git a/hosting/migrations/0052_hostingbilllineitem.py b/hosting/migrations/0052_hostingbilllineitem.py
new file mode 100644
index 00000000..6f1a3db4
--- /dev/null
+++ b/hosting/migrations/0052_hostingbilllineitem.py
@@ -0,0 +1,37 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.4 on 2019-04-13 11:38
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+import utils.mixins
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('hosting', '0051_auto_20190403_0703'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='HostingBillLineItem',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('amount', models.PositiveSmallIntegerField()),
+                ('description', models.CharField(max_length=255)),
+                ('discountable', models.BooleanField()),
+                ('metadata', models.CharField(max_length=128)),
+                ('period_start', models.DateTimeField()),
+                ('period_end', models.DateTimeField()),
+                ('proration', models.BooleanField()),
+                ('quantity', models.PositiveIntegerField()),
+                ('unit_amount', models.PositiveIntegerField()),
+                ('monthly_hosting_bill', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='hosting.MonthlyHostingBill')),
+            ],
+            options={
+                'permissions': (('view_hostingbilllineitem', 'View Monthly Hosting Bill Line Item'),),
+            },
+            bases=(utils.mixins.AssignPermissionsMixin, models.Model),
+        ),
+    ]
diff --git a/hosting/models.py b/hosting/models.py
index d58e2fce..34b16689 100644
--- a/hosting/models.py
+++ b/hosting/models.py
@@ -1,3 +1,4 @@
+import json
 import logging
 import os
 import pytz
@@ -235,6 +236,9 @@ class HostingBill(AssignPermissionsMixin, models.Model):
 
 
 class MonthlyHostingBill(AssignPermissionsMixin, models.Model):
+    """
+    Corresponds to Invoice object of Stripe
+    """
     customer = models.ForeignKey(StripeCustomer)
     order = models.ForeignKey(HostingOrder)
     created = models.DateTimeField(help_text="When the invoice was created")
@@ -326,6 +330,21 @@ class MonthlyHostingBill(AssignPermissionsMixin, models.Model):
             subscription_ids_csv=args['subscription_ids_csv'],
         )
 
+        if 'line_items' in args['line_items']:
+            line_items = args['line_items']
+            for item in line_items:
+                line_item_instance = HostingBillLineItem.objects.create(
+                    monthly_hosting_bill=instance,
+                    amount=item.amount,
+                    description=item.description,
+                    discountable=item.discountable,
+                    metadata=json.dumps(item.metadata),
+                    period_start=datetime.utcfromtimestamp(item.period.start).replace(tzinfo=pytz.utc),                                            period_end=datetime.utcfromtimestamp(item.period.end).replace(tzinfo=pytz.utc),
+                    proration=item.proration,
+                    quantity=item.quantity,
+                    unit_amount=item.unit_amount
+                )
+                line_item_instance.assign_permission(instance.customer.user)
         instance.assign_permissions(instance.customer.user)
         return instance
 
@@ -366,6 +385,28 @@ class MonthlyHostingBill(AssignPermissionsMixin, models.Model):
         return return_value
 
 
+class HostingBillLineItem(AssignPermissionsMixin, models.Model):
+    """
+    Corresponds to InvoiceItem object of Stripe
+    """
+    monthly_hosting_bill = models.ForeignKey(MonthlyHostingBill)
+    amount = models.PositiveSmallIntegerField()
+    description = models.CharField(max_length=255)
+    discountable = models.BooleanField()
+    metadata = models.CharField(max_length=128)
+    period_start = models.DateTimeField()
+    period_end = models.DateTimeField()
+    proration = models.BooleanField()
+    quantity = models.PositiveIntegerField()
+    unit_amount = models.PositiveIntegerField()
+    permissions = ('view_hostingbilllineitem',)
+
+    class Meta:
+        permissions = (
+            ('view_hostingbilllineitem', 'View Monthly Hosting Bill Line Item'),
+        )
+
+
 class VMDetail(models.Model):
     user = models.ForeignKey(CustomUser)
     vm_id = models.IntegerField(default=0)
diff --git a/utils/stripe_utils.py b/utils/stripe_utils.py
index 577eab80..4b4a157e 100644
--- a/utils/stripe_utils.py
+++ b/utils/stripe_utils.py
@@ -159,6 +159,7 @@ class StripeUtils(object):
                     'subscription_ids_csv': ','.join(
                         [line.id if line.type == 'subscription' else '' for line in invoice.lines.data]
                     ),
+                    'line_items': invoice.lines.data
                 }
                 starting_after = invoice.id
                 return_list.append(invoice_details)

From 94c78733974bb447c675dc47200d7267328f0252 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Sat, 13 Apr 2019 13:50:09 +0200
Subject: [PATCH 67/75] Fix getting line_items

---
 hosting/models.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/hosting/models.py b/hosting/models.py
index 34b16689..4476615a 100644
--- a/hosting/models.py
+++ b/hosting/models.py
@@ -330,7 +330,7 @@ class MonthlyHostingBill(AssignPermissionsMixin, models.Model):
             subscription_ids_csv=args['subscription_ids_csv'],
         )
 
-        if 'line_items' in args['line_items']:
+        if 'line_items' in args:
             line_items = args['line_items']
             for item in line_items:
                 line_item_instance = HostingBillLineItem.objects.create(

From 4a01036ab538d49184c68ed5405313034607b51d Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Sat, 13 Apr 2019 14:34:17 +0200
Subject: [PATCH 68/75] Attempt correction to gt dict to be passed for created
 greater than

---
 hosting/management/commands/fetch_stripe_bills.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/hosting/management/commands/fetch_stripe_bills.py b/hosting/management/commands/fetch_stripe_bills.py
index e321179d..2baaa921 100644
--- a/hosting/management/commands/fetch_stripe_bills.py
+++ b/hosting/management/commands/fetch_stripe_bills.py
@@ -31,7 +31,7 @@ class Command(BaseCommand):
                         # fetch only invoices which is created after
                         # mhb.created, because we already have invoices till
                         # this date
-                        created_gt = {'gt': int(mhb.created.timestamp())}
+                        created_gt = {'created': {'gt': int(mhb.created.timestamp())}}
 
                     all_invoices_response = stripe_utils.get_all_invoices(
                         user.stripecustomer.stripe_id,

From ed6059feaa3f2f7a94f501ccefeff16030877edb Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Sat, 13 Apr 2019 14:34:32 +0200
Subject: [PATCH 69/75] Set unit amount to 0 if not available in Stripe
 response

---
 hosting/models.py | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/hosting/models.py b/hosting/models.py
index 4476615a..ea776389 100644
--- a/hosting/models.py
+++ b/hosting/models.py
@@ -342,7 +342,12 @@ class MonthlyHostingBill(AssignPermissionsMixin, models.Model):
                     period_start=datetime.utcfromtimestamp(item.period.start).replace(tzinfo=pytz.utc),                                            period_end=datetime.utcfromtimestamp(item.period.end).replace(tzinfo=pytz.utc),
                     proration=item.proration,
                     quantity=item.quantity,
-                    unit_amount=item.unit_amount
+                    # Strange that line item does not have unit_amount but api
+                    # states that it is present
+                    # https://stripe.com/docs/api/invoiceitems/object#invoiceitem_object-unit_amount
+                    # So, for the time being I set the unit_amount to 0 if not
+                    # found in the line item
+                    unit_amount=item.unit_amount if hasattr(item, "unit_amount") else 0
                 )
                 line_item_instance.assign_permission(instance.customer.user)
         instance.assign_permissions(instance.customer.user)

From a4427dd3afef8c894b16dc67f9a509833443363d Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Sat, 13 Apr 2019 14:40:20 +0200
Subject: [PATCH 70/75] Set empty string for description if not available

---
 hosting/models.py | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/hosting/models.py b/hosting/models.py
index ea776389..aeed606f 100644
--- a/hosting/models.py
+++ b/hosting/models.py
@@ -336,7 +336,9 @@ class MonthlyHostingBill(AssignPermissionsMixin, models.Model):
                 line_item_instance = HostingBillLineItem.objects.create(
                     monthly_hosting_bill=instance,
                     amount=item.amount,
-                    description=item.description,
+                    # description seems to be set to null in the Stripe
+                    # response for an invoice
+                    description="" if item.description is None else item.description,
                     discountable=item.discountable,
                     metadata=json.dumps(item.metadata),
                     period_start=datetime.utcfromtimestamp(item.period.start).replace(tzinfo=pytz.utc),                                            period_end=datetime.utcfromtimestamp(item.period.end).replace(tzinfo=pytz.utc),

From 869f74e4e68f537499aff2e83638598a3e4ddc11 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Sat, 13 Apr 2019 14:43:39 +0200
Subject: [PATCH 71/75] Fix typo

---
 hosting/models.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/hosting/models.py b/hosting/models.py
index aeed606f..e20a7725 100644
--- a/hosting/models.py
+++ b/hosting/models.py
@@ -351,7 +351,7 @@ class MonthlyHostingBill(AssignPermissionsMixin, models.Model):
                     # found in the line item
                     unit_amount=item.unit_amount if hasattr(item, "unit_amount") else 0
                 )
-                line_item_instance.assign_permission(instance.customer.user)
+                line_item_instance.assign_permissions(instance.customer.user)
         instance.assign_permissions(instance.customer.user)
         return instance
 

From 77669c962cc5b1a8f72176e9900d652812bd07be Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Sat, 13 Apr 2019 15:00:19 +0200
Subject: [PATCH 72/75] Fix passing correct created value

---
 hosting/management/commands/fetch_stripe_bills.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/hosting/management/commands/fetch_stripe_bills.py b/hosting/management/commands/fetch_stripe_bills.py
index 2baaa921..09cb3295 100644
--- a/hosting/management/commands/fetch_stripe_bills.py
+++ b/hosting/management/commands/fetch_stripe_bills.py
@@ -31,7 +31,7 @@ class Command(BaseCommand):
                         # fetch only invoices which is created after
                         # mhb.created, because we already have invoices till
                         # this date
-                        created_gt = {'created': {'gt': int(mhb.created.timestamp())}}
+                        created_gt = int(mhb.created.timestamp())
 
                     all_invoices_response = stripe_utils.get_all_invoices(
                         user.stripecustomer.stripe_id,

From a3a2016cb49e7f8f67001a82c70894e364f438c9 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Sat, 13 Apr 2019 15:24:37 +0200
Subject: [PATCH 73/75] Attempt period from line_items

---
 hosting/templates/hosting/invoices.html |  4 +++-
 hosting/views.py                        | 19 +++++++++++--------
 2 files changed, 14 insertions(+), 9 deletions(-)

diff --git a/hosting/templates/hosting/invoices.html b/hosting/templates/hosting/invoices.html
index eaf5d22d..8eb3d02c 100644
--- a/hosting/templates/hosting/invoices.html
+++ b/hosting/templates/hosting/invoices.html
@@ -30,7 +30,9 @@
                 <tr>
                     <td class="xs-td-inline" data-header="{% trans 'VM ID' %}">{{ invoice.order.vm_id }}</td>
                     <td class="xs-td-inline" data-header="{% trans 'IP Address' %}">{{ ips|get_value_from_dict:invoice.invoice_number|join:"<br/>" }}</td>
-                    <td class="xs-td-inline" data-header="{% trans 'Period' %}">{{ invoice.period_start | date:'Y-m-d' }} &mdash; {{ invoice.period_end | date:'Y-m-d' }}</td>
+                    {% with line_items|get_value_from_dict:invoice.invoice_number as line_items_to_show %}
+                    <td class="xs-td-inline" data-header="{% trans 'Period' %}">{{ line_items_to_show.period_start | date:'Y-m-d' }} &mdash; {{ line_items_to_show.period_end | date:'Y-m-d' }}</td>
+                    {% endwith %}
                     <td class="xs-td-inline" data-header="{% trans 'Amount' %}">{{ invoice.total_in_chf|floatformat:2|intcomma }}</td>
                     <td class="text-right last-td">
                         <a class="btn btn-order-detail" href="{% url 'hosting:invoices' invoice.invoice_number %}">{% trans 'See Invoice' %}</a>
diff --git a/hosting/views.py b/hosting/views.py
index 5e80a39a..45230d1c 100644
--- a/hosting/views.py
+++ b/hosting/views.py
@@ -61,7 +61,7 @@ from .forms import (
 from .mixins import ProcessVMSelectionMixin, HostingContextMixin
 from .models import (
     HostingOrder, HostingBill, HostingPlan, UserHostingKey, VMDetail,
-    GenericProduct, MonthlyHostingBill
+    GenericProduct, MonthlyHostingBill, HostingBillLineItem
 )
 
 logger = logging.getLogger(__name__)
@@ -1179,21 +1179,24 @@ class InvoiceListView(LoginRequiredMixin, ListView):
             except CustomUser.DoesNotExist as dne:
                 logger.debug("User does not exist")
                 cu = self.request.user
-            mabs = MonthlyHostingBill.objects.filter(customer__user=cu)
+            mhbs = MonthlyHostingBill.objects.filter(customer__user=cu)
         else:
-            mabs = MonthlyHostingBill.objects.filter(
+            mhbs = MonthlyHostingBill.objects.filter(
                 customer__user=self.request.user
             )
         ips_dict = {}
-        for mab in mabs:
+        line_items_dict = {}
+        for mhb in mhbs:
             try:
-                vm_detail = VMDetail.objects.get(vm_id=mab.order.vm_id)
-                ips_dict[mab.invoice_number] = [vm_detail.ipv6, vm_detail.ipv4]
+                vm_detail = VMDetail.objects.get(vm_id=mhb.order.vm_id)
+                ips_dict[mhb.invoice_number] = [vm_detail.ipv6, vm_detail.ipv4]
+                line_items_dict[mhb.invoice_number] = HostingBillLineItem.objects.filter(monthly_hosting_bill=mhb)
             except VMDetail.DoesNotExist as dne:
-                ips_dict[mab.invoice_number] = ['--']
+                ips_dict[mhb.invoice_number] = ['--']
                 logger.debug("VMDetail for {} doesn't exist".format(
-                    mab.order.vm_id
+                    mhb.order.vm_id
                 ))
+        context['line_items'] = line_items_dict
         context['ips'] = ips_dict
         return context
 

From c5f72792d23a4f60a72db8e19c8fdc100066a4b9 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Sat, 13 Apr 2019 15:35:36 +0200
Subject: [PATCH 74/75] Show period of the first line item as the period of the
 invoice

---
 hosting/templates/hosting/invoices.html | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/hosting/templates/hosting/invoices.html b/hosting/templates/hosting/invoices.html
index 8eb3d02c..f2486111 100644
--- a/hosting/templates/hosting/invoices.html
+++ b/hosting/templates/hosting/invoices.html
@@ -31,7 +31,7 @@
                     <td class="xs-td-inline" data-header="{% trans 'VM ID' %}">{{ invoice.order.vm_id }}</td>
                     <td class="xs-td-inline" data-header="{% trans 'IP Address' %}">{{ ips|get_value_from_dict:invoice.invoice_number|join:"<br/>" }}</td>
                     {% with line_items|get_value_from_dict:invoice.invoice_number as line_items_to_show %}
-                    <td class="xs-td-inline" data-header="{% trans 'Period' %}">{{ line_items_to_show.period_start | date:'Y-m-d' }} &mdash; {{ line_items_to_show.period_end | date:'Y-m-d' }}</td>
+                    <td class="xs-td-inline" data-header="{% trans 'Period' %}">{{ line_items_to_show.0.period_start | date:'Y-m-d' }} &mdash; {{ line_items_to_show.0.period_end | date:'Y-m-d' }}</td>
                     {% endwith %}
                     <td class="xs-td-inline" data-header="{% trans 'Amount' %}">{{ invoice.total_in_chf|floatformat:2|intcomma }}</td>
                     <td class="text-right last-td">

From c3842a5ed50c74125de1df52dc1f6883471c4d54 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Sat, 13 Apr 2019 15:43:27 +0200
Subject: [PATCH 75/75] Get periods from line items in invoice detail also

---
 hosting/models.py | 22 ++++++++++++++++++++++
 hosting/views.py  |  6 +++---
 2 files changed, 25 insertions(+), 3 deletions(-)

diff --git a/hosting/models.py b/hosting/models.py
index e20a7725..550cf27f 100644
--- a/hosting/models.py
+++ b/hosting/models.py
@@ -391,6 +391,28 @@ class MonthlyHostingBill(AssignPermissionsMixin, models.Model):
                 logger.debug("VM_IDS=".format(','.join(vm_ids)))
         return return_value
 
+    def get_period_start(self):
+        """
+        Return the period start of the invoice for the line items
+        :return:
+        """
+        items = HostingBillLineItem.objects.filter(monthly_hosting_bill=self)
+        if len(items) > 0:
+            return items[0].period_start
+        else:
+            return self.period_start
+
+    def get_period_end(self):
+        """
+        Return the period end of the invoice for the line items
+        :return:
+        """
+        items = HostingBillLineItem.objects.filter(monthly_hosting_bill=self)
+        if len(items) > 0:
+            return items[0].period_end
+        else:
+            return self.period_end
+
 
 class HostingBillLineItem(AssignPermissionsMixin, models.Model):
     """
diff --git a/hosting/views.py b/hosting/views.py
index 45230d1c..fe13ff21 100644
--- a/hosting/views.py
+++ b/hosting/views.py
@@ -1230,7 +1230,7 @@ class InvoiceDetailView(LoginRequiredMixin, DetailView):
     context_object_name = "invoice"
     login_url = reverse_lazy('hosting:login')
     permission_required = ['view_monthlyhostingbill']
-    model = MonthlyHostingBill
+    # model = MonthlyHostingBill
 
     def get_object(self, queryset=None):
         invoice_id = self.kwargs.get('invoice_id')
@@ -1316,8 +1316,8 @@ class InvoiceDetailView(LoginRequiredMixin, DetailView):
                     return context
 
             # add context params from monthly hosting bill
-            context['period_start'] = obj.period_start
-            context['period_end'] = obj.period_end
+            context['period_start'] = obj.get_period_start()
+            context['period_end'] = obj.get_period_end()
             context['paid_at'] = obj.paid_at
             context['total_in_chf'] = obj.total_in_chf()
             context['invoice_number'] = obj.invoice_number