diff --git a/.gitignore b/.gitignore
index ec901a3..5c039d9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,3 +26,4 @@ dist/
*.iso
*.sqlite3
.DS_Store
+static/CACHE/
diff --git a/matrixhosting/static/matrixhosting/css/invoice.css b/matrixhosting/static/matrixhosting/css/invoice.css
new file mode 100644
index 0000000..60ee335
--- /dev/null
+++ b/matrixhosting/static/matrixhosting/css/invoice.css
@@ -0,0 +1,113 @@
+body {
+ font-family: Avenir;
+ background: white;
+ font-weight: 500;
+ line-height: 1.1em;
+ font-size: 16px;
+ margin: auto;
+}
+p {
+ display: block;
+ -webkit-margin-before: 14px;
+ -webkit-margin-after: 14px;
+ -webkit-margin-start: 0px;
+ -webkit-margin-end: 0px;
+}
+.bold {
+ font-weight: bold;
+}
+.d1 {
+ line-height:1.1em;
+ width: 60%;
+ float: left;
+}
+.d2 {
+ line-height:1.5em;
+ padding-top: 15px;
+ width: 40%;
+ float: left;
+}
+.d4 {
+ line-height:1.5em;
+ width:40%;
+ float: left;
+}
+.b1 {
+ width: 45%;
+ float: left;
+}
+.b2 {
+ width: 55%;
+ float: left;
+ text-align: right;
+ left: 0;
+}
+.d5 {
+ width: 100%;
+}
+.d6 {
+ width: 68%;
+ float: left;
+ font-size: 13px;
+}
+.d7 {
+ width: 32%;
+ float: left;
+}
+.wf {
+ width: 100%;
+}
+hr {
+ border: 0;
+ clear:both;
+ display: inline-block;
+ width: 100%;
+ background-color:gray;
+ height: 1px;
+ }
+ .tl {
+ text-align: left;
+ margin-left: 5px;
+ }
+
+ .tr {
+ text-align: right;
+ margin-right: 5px;
+ float: right;
+ }
+ .tc {
+ text-align: center;
+ }
+ .pc p {
+ display: block;
+ -webkit-margin-before: 3px;
+ -webkit-margin-after: 5px;
+ -webkit-margin-start: 0px;
+ -webkit-margin-end: 0px;
+}
+ .th {
+ border-top: 1px solid gray;
+ border-bottom: 1px solid gray;
+
+ }
+ .ts {
+ font-size: 14px;
+ }
+ .icon {
+ width: 16px;
+ height: 14px;
+ vertical-align: middle;
+ margin-right: 2px;
+ }
+ .footer {
+ margin-top: 70px;
+ font-size: 14px;
+ }
+
+ .footer p {
+ display: block;
+ -webkit-margin-before: 5px;
+ -webkit-margin-after: 5px;
+ -webkit-margin-start: 0px;
+ -webkit-margin-end: 0px;
+}
\ No newline at end of file
diff --git a/matrixhosting/static/matrixhosting/images/call.png b/matrixhosting/static/matrixhosting/images/call.png
new file mode 100644
index 0000000..e774362
Binary files /dev/null and b/matrixhosting/static/matrixhosting/images/call.png differ
diff --git a/matrixhosting/static/matrixhosting/images/company-large.jpg b/matrixhosting/static/matrixhosting/images/company-large.jpg
new file mode 100644
index 0000000..32d48cd
Binary files /dev/null and b/matrixhosting/static/matrixhosting/images/company-large.jpg differ
diff --git a/matrixhosting/static/matrixhosting/images/company-small.jpg b/matrixhosting/static/matrixhosting/images/company-small.jpg
new file mode 100644
index 0000000..a7b575f
Binary files /dev/null and b/matrixhosting/static/matrixhosting/images/company-small.jpg differ
diff --git a/matrixhosting/static/matrixhosting/images/home.png b/matrixhosting/static/matrixhosting/images/home.png
new file mode 100644
index 0000000..24428e7
Binary files /dev/null and b/matrixhosting/static/matrixhosting/images/home.png differ
diff --git a/matrixhosting/static/matrixhosting/images/msg.png b/matrixhosting/static/matrixhosting/images/msg.png
new file mode 100644
index 0000000..3b7b0c7
Binary files /dev/null and b/matrixhosting/static/matrixhosting/images/msg.png differ
diff --git a/matrixhosting/static/matrixhosting/images/twitter.png b/matrixhosting/static/matrixhosting/images/twitter.png
new file mode 100644
index 0000000..4db6da0
Binary files /dev/null and b/matrixhosting/static/matrixhosting/images/twitter.png differ
diff --git a/matrixhosting/static/matrixhosting/js/payment.js b/matrixhosting/static/matrixhosting/js/payment.js
index 14d806e..6fdf6fd 100644
--- a/matrixhosting/static/matrixhosting/js/payment.js
+++ b/matrixhosting/static/matrixhosting/js/payment.js
@@ -8,7 +8,50 @@ function setBrandIcon(brand) {
brandIconElement.classList.add(pfClass);
}
+function fetch_pricing() {
+ var url = '/pricing/' + $('input[name="pricing_name"]').val() + '/calculate/';
+ var cores = $('#cores').val();
+ var memory = $('#memory').val();
+ var storage = $('#storage').val();
+ $.ajax({
+ type: 'GET',
+ url: url,
+ data: { cores: cores, memory: memory, storage: storage},
+ dataType: 'json',
+ success: function (data) {
+ if (data && data['total']) {
+ $('#total').text(data['total']);
+ }
+ }
+ });
+};
+
+function incrementValue(e) {
+ var valueElement = $(e.target).parent().parent().find('input');
+ var step = $(valueElement).attr('step');
+ var min = parseInt($(valueElement).attr('min'));
+ var max = parseInt($(valueElement).attr('max'));
+ var new_value = 0;
+ if (e.data.inc == 1) {
+ new_value = Math.min(parseInt($(valueElement).val()) + parseInt(step) * e.data.inc, max);
+ } else {
+ new_value = Math.max(parseInt($(valueElement).val()) + parseInt(step) * e.data.inc, min);
+ }
+ $(valueElement).val(new_value);
+ fetch_pricing();
+ return false;
+};
+
+
$(document).ready(function () {
+ if ($('#pricing_name') != undefined) {
+ fetch_pricing();
+ }
+
+ $('.fa-plus-circle.right').bind('click', {inc: 1}, incrementValue);
+
+ $('.fa-minus-circle.left').bind('click', {inc: -1}, incrementValue);
+
var hasCreditcard = window.hasCreditcard || false;
if (hasCreditcard && window.stripeKey) {
var stripe = Stripe(window.stripeKey);
@@ -154,7 +197,7 @@ $(document).ready(function () {
});
$('#checkout-btn').click(function () {
- if($('input[name="payment_card"]:checked').size() == 1) {
+ if($('input[name="payment_card"]:checked').length == 1) {
var id = $('input[name="payment_card"]:checked').val();
if (id != 'new') {
$('#id_card').val(id);
diff --git a/matrixhosting/static/matrixhosting/webfonts/Avenir-Book.ttf b/matrixhosting/static/matrixhosting/webfonts/Avenir-Book.ttf
new file mode 100644
index 0000000..84ae914
Binary files /dev/null and b/matrixhosting/static/matrixhosting/webfonts/Avenir-Book.ttf differ
diff --git a/matrixhosting/templates/matrixhosting/base.html b/matrixhosting/templates/matrixhosting/base.html
index fa4adcf..dcda82d 100644
--- a/matrixhosting/templates/matrixhosting/base.html
+++ b/matrixhosting/templates/matrixhosting/base.html
@@ -1,4 +1,4 @@
-{% load static i18n %} {% get_current_language as LANGUAGE_CODE %}
+{% load static compress i18n %} {% get_current_language as LANGUAGE_CODE %}
@@ -22,11 +22,13 @@
type="text/css"
/>
+ {% compress css %}
+ {% endcompress %}
{% block css_extra %} {% endblock css_extra %}
@@ -55,6 +57,8 @@
{% block js_extra %} {% endblock js_extra %}
-
+ {% compress js %}
+
+ {% endcompress %}
diff --git a/matrixhosting/templates/matrixhosting/invoice.html b/matrixhosting/templates/matrixhosting/invoice.html
index 827fae7..dbe8b96 100644
--- a/matrixhosting/templates/matrixhosting/invoice.html
+++ b/matrixhosting/templates/matrixhosting/invoice.html
@@ -1,49 +1,171 @@
{% load static i18n %}
-{% get_current_language as LANGUAGE_CODE %}
-
+
-
-
-
-
-
-
- Hosting Invoice
-
-
-
-
-
-
-
+
+
+
+ {%trans "Invoice: " %} {{bill.id}}
+
+
-
-
-
-
-
-
-
-
-
-
Invoice
-
-
-
-
-
-
-
-
-
-
+
+
+
+
\ No newline at end of file
diff --git a/matrixhosting/templates/matrixhosting/order_confirmation.html b/matrixhosting/templates/matrixhosting/order_confirmation.html
index e8f9799..b7899e2 100644
--- a/matrixhosting/templates/matrixhosting/order_confirmation.html
+++ b/matrixhosting/templates/matrixhosting/order_confirmation.html
@@ -1,6 +1,6 @@
{% extends "matrixhosting/base.html" %}
-{% load static i18n %}
+{% load static compress i18n %}
{% block title %} Request Details {% endblock %}
@@ -130,7 +130,7 @@
{% trans "VAT for" %} {{pricing.vat_country}} ({{pricing.vat_percent}}%)
-
CHF
+
{{pricing.vat_amount}} CHF
@@ -203,5 +203,7 @@ aria-hidden="true" data-backdrop="static" data-keyboard="false">
+ {% compress js %}
+ {% endcompress %}
{% endblock js_extra %}
\ No newline at end of file
diff --git a/matrixhosting/templates/matrixhosting/order_details.html b/matrixhosting/templates/matrixhosting/order_details.html
index 799f89c..3654341 100644
--- a/matrixhosting/templates/matrixhosting/order_details.html
+++ b/matrixhosting/templates/matrixhosting/order_details.html
@@ -1,6 +1,6 @@
{% extends "matrixhosting/base.html" %}
-{% load static i18n %}
+{% load static compress i18n %}
{% block title %} Request Details {% endblock %}
@@ -196,7 +196,7 @@
{% endif %}
-
{{request.session.pricing.total|floatformat}}CHF
+
{{request.session.pricing.total|floatformat}}CHF
@@ -269,7 +269,9 @@
(function () {
window.stripeKey = "{{stripe_key}}";
window.current_lan = "{{LANGUAGE_CODE}}";
- window.hasCreditcard = "{{show_cards}}";
+ {% if show_cards %}
+ window.hasCreditcard = true;
+ {% endif %}
})();
{%endif%}
@@ -278,5 +280,7 @@
-
+ {% compress js %}
+
+ {% endcompress %}
{% endblock js_extra %}
\ No newline at end of file
diff --git a/matrixhosting/templates/matrixhosting/order_success.html b/matrixhosting/templates/matrixhosting/order_success.html
index 2492cac..d5ab677 100644
--- a/matrixhosting/templates/matrixhosting/order_success.html
+++ b/matrixhosting/templates/matrixhosting/order_success.html
@@ -50,7 +50,4 @@
{% endblock %}
{% block js_extra %}
-
-
-
{% endblock js_extra %}
\ No newline at end of file
diff --git a/matrixhosting/urls.py b/matrixhosting/urls.py
index 6954e11..f98ba4d 100644
--- a/matrixhosting/urls.py
+++ b/matrixhosting/urls.py
@@ -1,7 +1,7 @@
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
-
+from wkhtmltopdf.views import PDFTemplateView
from .views import *
app_name = 'matrixhosting'
diff --git a/matrixhosting/utils.py b/matrixhosting/utils.py
index 61fb46b..e69de29 100644
--- a/matrixhosting/utils.py
+++ b/matrixhosting/utils.py
@@ -1,19 +0,0 @@
-import os
-from io import BytesIO
-from django.http import HttpResponse
-from django.template.loader import get_template
-from django.conf import settings
-
-from xhtml2pdf import pisa
-
-def render_to_pdf(template_src, context_dict={}):
- template = get_template(template_src)
- html = template.render(context_dict)
- result = BytesIO()
- # pdf = pisa.pisaDocument(BytesIO(html.encode("ISO-8859-1")), result)
- links = lambda uri, rel: os.path.join(settings.MEDIA_ROOT, uri.replace(settings.MEDIA_URL, ''))
- pdf = pisa.pisaDocument(BytesIO(html.encode("ISO-8859-1")),dest=result)
-
- if not pdf.err:
- return HttpResponse(result.getvalue(), content_type='application/pdf')
- return None
\ No newline at end of file
diff --git a/matrixhosting/views.py b/matrixhosting/views.py
index b25e6e3..7cd0c2a 100644
--- a/matrixhosting/views.py
+++ b/matrixhosting/views.py
@@ -17,6 +17,7 @@ from django.conf import settings
from django.http import (
HttpResponseRedirect, JsonResponse, HttpResponse
)
+from wkhtmltopdf.views import PDFTemplateResponse
from rest_framework import viewsets, permissions
from uncloud_pay.models import PricingPlan
@@ -27,7 +28,7 @@ from uncloud_pay.selectors import get_billing_address_for_user, has_enough_balan
import uncloud_pay.stripe as uncloud_stripe
from .models import VMInstance
from .serializers import *
-from .utils import render_to_pdf
+from .utils import *
logger = logging.getLogger(__name__)
@@ -195,6 +196,7 @@ class OrderDetailsView(DetailView):
request.session.get('order'))
if order:
bill = Bill.create_next_bill_for_user_address(billing_address)
+ self.request.session['bill_id'] = bill.id
payment= Payment.withdraw(owner=request.user, amount=total, notes=f"BILL #{bill.id}")
if payment:
#Close the bill as the payment has been added
@@ -244,26 +246,40 @@ class OrderSuccessView(DetailView):
'order': self.request.session.get('order'),
'balance': get_balance_for_user(self.request.user)
}
- # if ('order' not in request.session):
- # return HttpResponseRedirect(reverse('matrix:index'))
+ if ('order' not in request.session):
+ return HttpResponseRedirect(reverse('matrix:index'))
return render(request, self.template_name, context)
class InvoiceDownloadView(View):
- def get(self, request, *args, **kwargs):
- data = {
- 'today': datetime.date.today(),
- 'amount': 39.99,
- 'customer_name': 'Cooper Mann',
- 'order_id': 1233434,
- }
- pdf = render_to_pdf('matrixhosting/invoice.html', data)
- if pdf:
- response = HttpResponse(pdf, content_type='application/pdf')
- content = "inline; filename=invoice.pdf"
- content = "attachment; filename=invoice.pdf"
- response['Content-Disposition'] = content
- return response
- return HttpResponse("Not found")
+ template = 'matrixhosting/invoice.html'
+ filename = 'invoice.pdf'
+
+ @method_decorator(login_required)
+ def dispatch(self, *args, **kwargs):
+ return super().dispatch(*args, **kwargs)
+
+ def get_context_data(self, **kwargs):
+ context = {'base_url': f'{self.request.scheme}://{self.request.get_host()}'}
+ bill = Bill.objects.get(id=self.request.session.get('bill_id'))
+ if bill:
+ context['bill'] = bill
+ context['vat_rate'] = str(round(bill.vat_rate * 100, 2)) + '%'
+ context['tax_amount'] = round(bill.vat_rate * bill.subtotal, 2)
+ return context
+
+ def get(self, request):
+ cmd_options = {
+ 'page_height': 240,
+ 'page_width':175,
+ 'orientation': 'Portrait',
+ 'header_spacing': 65,
+ 'header_line': False
+ }
+ return PDFTemplateResponse(request=request,
+ template=self.template,
+ filename = self.filename,
+ cmd_options= cmd_options,
+ context= self.get_context_data())
class Dashboard(ListView):
diff --git a/requirements.txt b/requirements.txt
index 575a52a..7517d42 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -7,8 +7,9 @@ fontawesome-free
psycopg2
ldap3
django-allauth
+django-compressor
xmltodict
-xhtml2pdf
+django-wkhtmltopdf
parsedatetime
# Follow are for creating graph models
pyparsing
diff --git a/uncloud/settings.py b/uncloud/settings.py
index 86e0942..c5567c0 100644
--- a/uncloud/settings.py
+++ b/uncloud/settings.py
@@ -62,6 +62,8 @@ INSTALLED_APPS = [
'django.contrib.sites',
'django.contrib.staticfiles',
'django_extensions',
+ 'compressor',
+ 'wkhtmltopdf',
'rest_framework',
'django_q',
'notifications',
@@ -90,6 +92,7 @@ MIDDLEWARE = [
]
ROOT_URLCONF = 'uncloud.urls'
+WKHTMLTOPDF_CMD = "/usr/local/bin/wkhtmltopdf"
TEMPLATES = [
{
@@ -188,11 +191,13 @@ USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.0/howto/static-files/
STATIC_URL = '/static/'
-STATICFILES_DIRS = [ os.path.join(BASE_DIR, "static") ]
+STATIC_ROOT = os.path.join(BASE_DIR, "static")
STATICFILES_FINDERS = [
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
+ 'compressor.finders.CompressorFinder',
]
+COMPRESS_ENABLED = True
#VM Deployment TEMPLATE
GITLAB_SERVER = env('GITLAB_SERVER')
diff --git a/uncloud_pay/migrations/0025_auto_20210803_2118.py b/uncloud_pay/migrations/0025_auto_20210803_2118.py
new file mode 100644
index 0000000..5be92a5
--- /dev/null
+++ b/uncloud_pay/migrations/0025_auto_20210803_2118.py
@@ -0,0 +1,24 @@
+# Generated by Django 3.2.4 on 2021-08-03 21:18
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('uncloud_pay', '0024_auto_20210730_1441'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='billrecord',
+ name='bill',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='bill_records', to='uncloud_pay.bill'),
+ ),
+ migrations.AlterField(
+ model_name='payment',
+ name='type',
+ field=models.CharField(choices=[('withdraw', 'Withdraw Money'), ('deposit', 'Deposit Money')], default='deposit', max_length=256),
+ ),
+ ]
diff --git a/uncloud_pay/migrations/0026_remove_order_description.py b/uncloud_pay/migrations/0026_remove_order_description.py
new file mode 100644
index 0000000..c81cdfe
--- /dev/null
+++ b/uncloud_pay/migrations/0026_remove_order_description.py
@@ -0,0 +1,17 @@
+# Generated by Django 3.2.4 on 2021-08-03 21:40
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('uncloud_pay', '0025_auto_20210803_2118'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='order',
+ name='description',
+ ),
+ ]
diff --git a/uncloud_pay/models.py b/uncloud_pay/models.py
index e45db0d..d5ccedb 100644
--- a/uncloud_pay/models.py
+++ b/uncloud_pay/models.py
@@ -649,8 +649,6 @@ class Order(models.Model):
customer = models.ForeignKey(StripeCustomer, on_delete=models.CASCADE, null=True)
- description = models.TextField()
-
product = models.ForeignKey(Product, blank=False, null=False, on_delete=models.CASCADE)
config = models.JSONField()
@@ -806,6 +804,19 @@ class Order(models.Model):
@property
def is_one_time(self):
return not self.is_recurring
+
+ @property
+ def description(self):
+ desc = self.product.description + "( "
+ config = json.loads(self.config)
+ if config and config["cores"]:
+ desc = f"{desc} {config['cores']} Cores,"
+ if config and config["memory"]:
+ desc = f"{desc} {config['memory']} RAM,"
+ if config and config["storage"]:
+ desc = f"{desc} {config['storage']} GB SSD,"
+ desc += " )"
+ return desc
def replace_with(self, new_order):
new_order.replaces = self
@@ -1011,6 +1022,11 @@ class Bill(models.Model):
bill_records = BillRecord.objects.filter(bill=self)
return sum([ br.sum for br in bill_records ])
+ @property
+ def subtotal(self):
+ bill_records = BillRecord.objects.filter(bill=self)
+ return sum([ br.subtotal for br in bill_records ])
+
@property
def vat_rate(self):
return VATRate.get_vat_rate(self.billing_address, when=self.ending_date)
@@ -1114,7 +1130,7 @@ class BillRecord(models.Model):
Entry of a bill, dynamically generated from an order.
"""
- bill = models.ForeignKey(Bill, on_delete=models.CASCADE)
+ bill = models.ForeignKey(Bill, on_delete=models.CASCADE, related_name='bill_records')
order = models.ForeignKey(Order, on_delete=models.CASCADE)
creation_date = models.DateTimeField(auto_now_add=True)
@@ -1142,12 +1158,30 @@ class BillRecord(models.Model):
else:
return self.order.one_time_price
+ @property
+ def description(self):
+ if self.order:
+ return self.order.description
+ return ''
+
@property
def price(self):
if self.is_recurring_record:
return self.order.recurring_price
else:
return self.order.one_time_price
+
+ @property
+ def subtotal(self):
+ billing_address_ins = self.order.billing_address
+ vat_rate = VATRate.get_vat_rate(billing_address_ins)
+ vat_validation_status = "verified" if billing_address_ins.vat_number_validated_on and billing_address_ins.vat_number_verified else False
+ config = json.loads(self.order.config)
+ pricing = uncloud_pay.utils.get_order_total_with_vat(
+ config["cores"], config["memory"], config["storage"], self.order.pricing_plan.name,
+ vat_rate=vat_rate * 100, vat_validation_status = vat_validation_status
+ )
+ return pricing['subtotal_after_discount']
def __str__(self):
if self.is_recurring_record:
diff --git a/uncloud_pay/utils.py b/uncloud_pay/utils.py
index cf5d09c..16c2ce7 100644
--- a/uncloud_pay/utils.py
+++ b/uncloud_pay/utils.py
@@ -124,10 +124,11 @@ def apply_vat_discount(subtotal, pricing_plan, vat_rate=False, vat_validation_st
'amount_with_vat': round(float(discount_amount_with_vat), 2)
}
subtotal_after_discount = subtotal - discount["amount"]
+ vat_amount = round(vat_percent * 0.01 * subtotal_after_discount, 2)
price_after_discount_with_vat = round((subtotal - discount['amount']) * (1 + vat_percent * 0.01), 2)
return (subtotal, round(float(subtotal_after_discount), 2), price_after_discount_with_vat,
- round(float(vat), 2), vat_percent, discount)
+ round(float(vat), 2), vat_percent, vat_amount, discount)
def get_order_total_with_vat(cores, memory, storage,
@@ -149,13 +150,14 @@ def get_order_total_with_vat(cores, memory, storage,
(decimal.Decimal(memory) * pricing.ram_unit_price) +
(decimal.Decimal(storage) * (pricing.storage_unit_price))
)
- subtotal, subtotal_after_discount, price_after_discount_with_vat, vat, vat_percent, discount = \
+ subtotal, subtotal_after_discount, price_after_discount_with_vat, vat, vat_percent, vat_amount, discount = \
apply_vat_discount(subtotal, pricing, vat_rate, vat_validation_status)
return {
"name": pricing.name,
"subtotal": subtotal,
"discount": discount,
"vat": vat, "vat_percent": vat_percent,
+ 'vat_amount': vat_amount,
"vat_validation_status": vat_validation_status,
"subtotal_after_discount": subtotal_after_discount,
"total": price_after_discount_with_vat