Add the invoice template
This commit is contained in:
parent
030a0cd501
commit
5bb0c4cdda
24 changed files with 462 additions and 96 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -26,3 +26,4 @@ dist/
|
|||
*.iso
|
||||
*.sqlite3
|
||||
.DS_Store
|
||||
static/CACHE/
|
||||
|
|
113
matrixhosting/static/matrixhosting/css/invoice.css
Normal file
113
matrixhosting/static/matrixhosting/css/invoice.css
Normal file
|
@ -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;
|
||||
}
|
BIN
matrixhosting/static/matrixhosting/images/call.png
Normal file
BIN
matrixhosting/static/matrixhosting/images/call.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.4 KiB |
BIN
matrixhosting/static/matrixhosting/images/company-large.jpg
Normal file
BIN
matrixhosting/static/matrixhosting/images/company-large.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 39 KiB |
BIN
matrixhosting/static/matrixhosting/images/company-small.jpg
Normal file
BIN
matrixhosting/static/matrixhosting/images/company-small.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
BIN
matrixhosting/static/matrixhosting/images/home.png
Normal file
BIN
matrixhosting/static/matrixhosting/images/home.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.6 KiB |
BIN
matrixhosting/static/matrixhosting/images/msg.png
Normal file
BIN
matrixhosting/static/matrixhosting/images/msg.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.5 KiB |
BIN
matrixhosting/static/matrixhosting/images/twitter.png
Normal file
BIN
matrixhosting/static/matrixhosting/images/twitter.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.7 KiB |
|
@ -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);
|
||||
|
|
BIN
matrixhosting/static/matrixhosting/webfonts/Avenir-Book.ttf
Normal file
BIN
matrixhosting/static/matrixhosting/webfonts/Avenir-Book.ttf
Normal file
Binary file not shown.
|
@ -1,4 +1,4 @@
|
|||
{% load static i18n %} {% get_current_language as LANGUAGE_CODE %}
|
||||
{% load static compress i18n %} {% get_current_language as LANGUAGE_CODE %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{LANGUAGE_CODE}}">
|
||||
<head>
|
||||
|
@ -22,11 +22,13 @@
|
|||
type="text/css"
|
||||
/>
|
||||
<!-- Custom CSS -->
|
||||
{% compress css %}
|
||||
<link
|
||||
href="{% static 'matrixhosting/css/theme.css' %}"
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
/>
|
||||
{% endcompress %}
|
||||
{% block css_extra %} {% endblock css_extra %}
|
||||
|
||||
<!-- External Fonts -->
|
||||
|
@ -55,6 +57,8 @@
|
|||
<script src="{% static 'matrixhosting/js/bootstrap.bundle.min.js' %}"></script>
|
||||
<!-- Custom JS -->
|
||||
{% block js_extra %} {% endblock js_extra %}
|
||||
<script src="{% static 'matrixhosting/js/theme.js' %}"></script>
|
||||
{% compress js %}
|
||||
<script src="{% static 'matrixhosting/js/theme.js' %}"></script>
|
||||
{% endcompress %}
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,49 +1,171 @@
|
|||
{% load static i18n %}
|
||||
{% get_current_language as LANGUAGE_CODE %}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="description" content="Matrix Hosting by ungleich" />
|
||||
<meta name="author" content="ungleich glarus ag" />
|
||||
<title>
|
||||
Hosting Invoice
|
||||
</title>
|
||||
|
||||
<!-- Web Fonts
|
||||
======================= -->
|
||||
<link rel='stylesheet' href='https://fonts.googleapis.com/css?family=Rubik:300,300i,400,400i,500,500i,700,700i,900,900i' type='text/css'>
|
||||
|
||||
<!-- Stylesheet
|
||||
======================= -->
|
||||
<link href="{% static 'matrixhosting/css/bootstrap.min.css' %}" rel="stylesheet" />
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport"
|
||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>{%trans "Invoice: " %} {{bill.id}}</title>
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: 'Avenir';
|
||||
src: url("{{ base_url }}{% static 'matrixhosting/webfonts/Avenir-Book.ttf' %}");
|
||||
}
|
||||
</style>
|
||||
<link href="{{ base_url }}{% static 'matrixhosting/css/invoice.css' %}" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Container -->
|
||||
<div class="container-fluid invoice-container">
|
||||
<!-- Header -->
|
||||
<header>
|
||||
<div class="row align-items-center">
|
||||
<div class="col-sm-7 text-center text-sm-start mb-3 mb-sm-0">
|
||||
<img id="logo" src="{% static 'matrixhosting/images/logo.png' %}" title="matrixhosting" alt="matrixhosting" />
|
||||
</div>
|
||||
<div class="col-sm-5 text-center text-sm-end">
|
||||
<h4 class="text-7 mb-0">Invoice</h4>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
</header>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main>
|
||||
|
||||
</main>
|
||||
<!-- Footer -->
|
||||
<footer class="text-center mt-4">
|
||||
</footer>
|
||||
<div class="invoice-container">
|
||||
|
||||
<div class="header" style="line-height:1.1em;">
|
||||
<div class="d1">
|
||||
<div class="logo">
|
||||
<img class="" src="{{ base_url }}{% static 'matrixhosting/images/company-small.jpg' %}" width="50%"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d2" name="company_address">
|
||||
<span class="bold">ungleich glarus ag</span>
|
||||
<address>
|
||||
Bahnhofstrasse 1<br>
|
||||
8783 Linthal<br>
|
||||
Switzerland
|
||||
</address>
|
||||
</div>
|
||||
<div class="first-page">
|
||||
|
||||
<div class="d1" style="margin-top:15px;">
|
||||
<b>
|
||||
<span style="font-size: 16px">{{bill.billing_address.full_name}}</span>
|
||||
</b>
|
||||
<br/>
|
||||
|
||||
<span>{{bill.billing_address.owner.email}}</span>
|
||||
<address>
|
||||
{{bill.billing_address.street}}<br>
|
||||
{{bill.billing_address.city}}<br>
|
||||
{{bill.billing_address.get_country_display}}
|
||||
</address>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d4" style="margin-top:15px;">
|
||||
<div>
|
||||
<div class="b1">
|
||||
<span>{%trans "Date of invoice:" %}</span>
|
||||
</div>
|
||||
|
||||
<div class="b2">
|
||||
<span>{{bill.starting_date|date}}</span>
|
||||
<br/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="b1">
|
||||
<span>{%trans "Invoice Number:" %}</span>
|
||||
</div>
|
||||
<div class="b2">
|
||||
<span>#{{bill.id}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="b1">
|
||||
<span>{%trans "Due Date:" %}</span>
|
||||
|
||||
</div>
|
||||
<div class="b2">
|
||||
<span>{{bill.due_date}}</span>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="clear: both;">
|
||||
</div>
|
||||
<div class="d5" style="margin-top:20px; margin-bottom:10px important;">
|
||||
<br/>
|
||||
<span style="font-size: 2em !important; font-weight: bolder !important;">{%trans "INVOICE" %}</span>
|
||||
<span style="font-size: 2em !important; font-weight: bolder !important;padding-left:5px"> {{bill.starting_date|date:"m-Y"}}</span>
|
||||
</div>
|
||||
<div style="clear: both;">
|
||||
<table class="wf" style="margin-top:20px; margin-bottom:10px important;border-top: 1px solid gray;">
|
||||
<thead >
|
||||
<tr class="" style="padding-top: 10px; padding-bottom:10px;background-color: #f5f5f5;">
|
||||
<th class="tl bold" style="padding: 5px 0px 5px 0px !important;">{%trans "Product" %}</th>
|
||||
<th class="tc bold" style="padding: 5px 0px 5px 0px !important;">{%trans "Quantity" %}</th>
|
||||
<th class="bold" style="text-align: right; padding: 5px 0px 5px 0px !important;">{%trans "Amount in " %} <span>CHF</span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for record in bill.bill_records.all %}
|
||||
<tr class="ts" style="line-height:1.8em !important;">
|
||||
<td class="tl">
|
||||
<span>{{record.description}}</span>
|
||||
</td>
|
||||
<td class="tc">
|
||||
<span>{{record.quantity}}</span>
|
||||
</td>
|
||||
<td style="text-align: right;">
|
||||
<span>{{record.subtotal}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
<div class="page">
|
||||
<div class="wf th">
|
||||
<p class="ts">
|
||||
<span class="tl">SubTotal</span>
|
||||
<span class="tr">{{bill.subtotal}}</span>
|
||||
</span>
|
||||
</p>
|
||||
<p class="ts">
|
||||
<span class="tl">{{vat_rate}}</span>
|
||||
<span class="tr">{{tax_amount}}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="wf pc" style="background-color: #f5f5f5;padding-top:5px;padding-bottom:5px;">
|
||||
<p class="bold" style="padding-top:5px;">
|
||||
<span class="tl" style="font-size: 16px">{%trans "CHF" %}</span>
|
||||
<span class="tr" style="font-size: 16px">{{bill.sum}}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer wf custom_footer">
|
||||
<br/>
|
||||
<div class="d6">
|
||||
<p>
|
||||
<img class="icon" src="{{ base_url }}{% static 'matrixhosting/images/call.png' %}"/>
|
||||
<span>+4(144) 534-6622</span>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<img class="icon" src="{{ base_url }}{% static 'matrixhosting/images/msg.png' %}"/>
|
||||
<span>buchhaltung-ag@ungleich.ch</span>
|
||||
</p>
|
||||
<p>
|
||||
<img class="icon" src="{{ base_url }}{% static 'matrixhosting/images/home.png' %}"/>
|
||||
<span>https://www.ungleich.ch</span>
|
||||
</p>
|
||||
<p>
|
||||
<img class="icon" src="{{ base_url }}{% static 'matrixhosting/images/twitter.png' %}"/>
|
||||
@ungleich
|
||||
</p>
|
||||
</div>
|
||||
<div class="d7">
|
||||
<div>
|
||||
<p>Glarner Kantonalbank</p>
|
||||
<p>
|
||||
<span class="bold">IBAN: CH 4300 7730 0055 5931 177</span>
|
||||
</p>
|
||||
<p>
|
||||
<span class="bold">BIC: GLKBCH22</span>
|
||||
</p>
|
||||
</div>
|
||||
<p style="font-size: 13px; white-space: nowrap !important">Mwst-Nummer: CHE-156.970.649 MWST</span></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</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 @@
|
|||
<p><span>{% trans "VAT for" %} {{pricing.vat_country}} ({{pricing.vat_percent}}%)</span></p>
|
||||
</div>
|
||||
<div class="col-md-6 col-sm-6 col-xs-6">
|
||||
<p><span class="pull-right" > CHF</span></p>
|
||||
<p><span class="pull-right" > {{pricing.vat_amount}} CHF</span></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -203,5 +203,7 @@ aria-hidden="true" data-backdrop="static" data-keyboard="false">
|
|||
<!-- jQuery -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.3/jquery.validate.min.js"></script>
|
||||
<!-- Custom JS -->
|
||||
{% compress js %}
|
||||
<script type="text/javascript" src="{% static 'matrixhosting/js/order.js' %}"></script>
|
||||
{% endcompress %}
|
||||
{% endblock js_extra %}
|
|
@ -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 %}
|
||||
</div>
|
||||
<div class="col col-lg-6">
|
||||
<div class="float-right"><span class="text-4">{{request.session.pricing.total|floatformat}}</span><span class="text-2 p-1">CHF</span></div>
|
||||
<div class="float-right"><span id="total" class="text-4">{{request.session.pricing.total|floatformat}}</span><span class="text-2 p-1">CHF</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="mt-2 mx-n3">
|
||||
|
@ -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 %}
|
||||
})();
|
||||
</script>
|
||||
{%endif%}
|
||||
|
@ -278,5 +280,7 @@
|
|||
<script src="https://js.stripe.com/v3/"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.3/jquery.validate.min.js"></script>
|
||||
<!-- Custom JS -->
|
||||
<script type="text/javascript" src="{% static 'matrixhosting/js/payment.js' %}"></script>
|
||||
{% compress js %}
|
||||
<script type="text/javascript" src="{% static 'matrixhosting/js/payment.js' %}"></script>
|
||||
{% endcompress %}
|
||||
{% endblock js_extra %}
|
|
@ -50,7 +50,4 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block js_extra %}
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.3/jquery.validate.min.js"></script>
|
||||
<!-- Custom JS -->
|
||||
<script type="text/javascript" src="{% static 'matrixhosting/js/order.js' %}"></script>
|
||||
{% endblock js_extra %}
|
|
@ -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'
|
||||
|
|
|
@ -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
|
|
@ -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):
|
||||
|
|
|
@ -7,8 +7,9 @@ fontawesome-free
|
|||
psycopg2
|
||||
ldap3
|
||||
django-allauth
|
||||
django-compressor
|
||||
xmltodict
|
||||
xhtml2pdf
|
||||
django-wkhtmltopdf
|
||||
parsedatetime
|
||||
# Follow are for creating graph models
|
||||
pyparsing
|
||||
|
|
|
@ -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')
|
||||
|
|
24
uncloud_pay/migrations/0025_auto_20210803_2118.py
Normal file
24
uncloud_pay/migrations/0025_auto_20210803_2118.py
Normal file
|
@ -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),
|
||||
),
|
||||
]
|
17
uncloud_pay/migrations/0026_remove_order_description.py
Normal file
17
uncloud_pay/migrations/0026_remove_order_description.py
Normal file
|
@ -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',
|
||||
),
|
||||
]
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue