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> + {% 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> + {{ 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> Bahnhofstrasse 1, 8783 + Linthal, Switzerland<br> + www.datacenterlight.ch | info@datacenterlight.ch | <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' }} — {{ 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' }} — {{ invoice.period_end | date: 'Y-m-d' }}</td> + <td class="xs-td-inline" data-header="{% trans 'Period' %}">{{ invoice.period_start | date:'Y-m-d' }} — {{ 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' }} — {{ 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' }} — {{ 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' }} — {{ 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' }} — {{ 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' }} — {{ 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' }} — {{ 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