forked from uncloud/uncloud
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
|
*.iso
|
||||||
*.sqlite3
|
*.sqlite3
|
||||||
.DS_Store
|
.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);
|
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 () {
|
$(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;
|
var hasCreditcard = window.hasCreditcard || false;
|
||||||
if (hasCreditcard && window.stripeKey) {
|
if (hasCreditcard && window.stripeKey) {
|
||||||
var stripe = Stripe(window.stripeKey);
|
var stripe = Stripe(window.stripeKey);
|
||||||
|
@ -154,7 +197,7 @@ $(document).ready(function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#checkout-btn').click(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();
|
var id = $('input[name="payment_card"]:checked').val();
|
||||||
if (id != 'new') {
|
if (id != 'new') {
|
||||||
$('#id_card').val(id);
|
$('#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>
|
<!DOCTYPE html>
|
||||||
<html lang="{{LANGUAGE_CODE}}">
|
<html lang="{{LANGUAGE_CODE}}">
|
||||||
<head>
|
<head>
|
||||||
|
@ -22,11 +22,13 @@
|
||||||
type="text/css"
|
type="text/css"
|
||||||
/>
|
/>
|
||||||
<!-- Custom CSS -->
|
<!-- Custom CSS -->
|
||||||
|
{% compress css %}
|
||||||
<link
|
<link
|
||||||
href="{% static 'matrixhosting/css/theme.css' %}"
|
href="{% static 'matrixhosting/css/theme.css' %}"
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
type="text/css"
|
type="text/css"
|
||||||
/>
|
/>
|
||||||
|
{% endcompress %}
|
||||||
{% block css_extra %} {% endblock css_extra %}
|
{% block css_extra %} {% endblock css_extra %}
|
||||||
|
|
||||||
<!-- External Fonts -->
|
<!-- External Fonts -->
|
||||||
|
@ -55,6 +57,8 @@
|
||||||
<script src="{% static 'matrixhosting/js/bootstrap.bundle.min.js' %}"></script>
|
<script src="{% static 'matrixhosting/js/bootstrap.bundle.min.js' %}"></script>
|
||||||
<!-- Custom JS -->
|
<!-- Custom JS -->
|
||||||
{% block js_extra %} {% endblock js_extra %}
|
{% 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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,49 +1,171 @@
|
||||||
{% load static i18n %}
|
{% 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">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="UTF-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
<meta name="viewport"
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||||
<meta name="description" content="Matrix Hosting by ungleich" />
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
<meta name="author" content="ungleich glarus ag" />
|
<title>{%trans "Invoice: " %} {{bill.id}}</title>
|
||||||
<title>
|
<style>
|
||||||
Hosting Invoice
|
@font-face {
|
||||||
</title>
|
font-family: 'Avenir';
|
||||||
|
src: url("{{ base_url }}{% static 'matrixhosting/webfonts/Avenir-Book.ttf' %}");
|
||||||
<!-- Web Fonts
|
}
|
||||||
======================= -->
|
</style>
|
||||||
<link rel='stylesheet' href='https://fonts.googleapis.com/css?family=Rubik:300,300i,400,400i,500,500i,700,700i,900,900i' type='text/css'>
|
<link href="{{ base_url }}{% static 'matrixhosting/css/invoice.css' %}" rel="stylesheet">
|
||||||
|
|
||||||
<!-- Stylesheet
|
|
||||||
======================= -->
|
|
||||||
<link href="{% static 'matrixhosting/css/bootstrap.min.css' %}" rel="stylesheet" />
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<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>
|
<div class="invoice-container">
|
||||||
<!-- Footer -->
|
|
||||||
<footer class="text-center mt-4">
|
<div class="header" style="line-height:1.1em;">
|
||||||
</footer>
|
<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>
|
||||||
|
</div>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
|
@ -1,6 +1,6 @@
|
||||||
{% extends "matrixhosting/base.html" %}
|
{% extends "matrixhosting/base.html" %}
|
||||||
|
|
||||||
{% load static i18n %}
|
{% load static compress i18n %}
|
||||||
|
|
||||||
{% block title %} Request Details {% endblock %}
|
{% block title %} Request Details {% endblock %}
|
||||||
|
|
||||||
|
@ -130,7 +130,7 @@
|
||||||
<p><span>{% trans "VAT for" %} {{pricing.vat_country}} ({{pricing.vat_percent}}%)</span></p>
|
<p><span>{% trans "VAT for" %} {{pricing.vat_country}} ({{pricing.vat_percent}}%)</span></p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 col-sm-6 col-xs-6">
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -203,5 +203,7 @@ aria-hidden="true" data-backdrop="static" data-keyboard="false">
|
||||||
<!-- jQuery -->
|
<!-- jQuery -->
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.3/jquery.validate.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.3/jquery.validate.min.js"></script>
|
||||||
<!-- Custom JS -->
|
<!-- Custom JS -->
|
||||||
|
{% compress js %}
|
||||||
<script type="text/javascript" src="{% static 'matrixhosting/js/order.js' %}"></script>
|
<script type="text/javascript" src="{% static 'matrixhosting/js/order.js' %}"></script>
|
||||||
|
{% endcompress %}
|
||||||
{% endblock js_extra %}
|
{% endblock js_extra %}
|
|
@ -1,6 +1,6 @@
|
||||||
{% extends "matrixhosting/base.html" %}
|
{% extends "matrixhosting/base.html" %}
|
||||||
|
|
||||||
{% load static i18n %}
|
{% load static compress i18n %}
|
||||||
|
|
||||||
{% block title %} Request Details {% endblock %}
|
{% block title %} Request Details {% endblock %}
|
||||||
|
|
||||||
|
@ -196,7 +196,7 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col col-lg-6">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
<hr class="mt-2 mx-n3">
|
<hr class="mt-2 mx-n3">
|
||||||
|
@ -269,7 +269,9 @@
|
||||||
(function () {
|
(function () {
|
||||||
window.stripeKey = "{{stripe_key}}";
|
window.stripeKey = "{{stripe_key}}";
|
||||||
window.current_lan = "{{LANGUAGE_CODE}}";
|
window.current_lan = "{{LANGUAGE_CODE}}";
|
||||||
window.hasCreditcard = "{{show_cards}}";
|
{% if show_cards %}
|
||||||
|
window.hasCreditcard = true;
|
||||||
|
{% endif %}
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
{%endif%}
|
{%endif%}
|
||||||
|
@ -278,5 +280,7 @@
|
||||||
<script src="https://js.stripe.com/v3/"></script>
|
<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>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.3/jquery.validate.min.js"></script>
|
||||||
<!-- Custom JS -->
|
<!-- 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 %}
|
{% endblock js_extra %}
|
|
@ -50,7 +50,4 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block js_extra %}
|
{% 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 %}
|
{% endblock js_extra %}
|
|
@ -1,7 +1,7 @@
|
||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
|
from wkhtmltopdf.views import PDFTemplateView
|
||||||
from .views import *
|
from .views import *
|
||||||
|
|
||||||
app_name = 'matrixhosting'
|
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 (
|
from django.http import (
|
||||||
HttpResponseRedirect, JsonResponse, HttpResponse
|
HttpResponseRedirect, JsonResponse, HttpResponse
|
||||||
)
|
)
|
||||||
|
from wkhtmltopdf.views import PDFTemplateResponse
|
||||||
from rest_framework import viewsets, permissions
|
from rest_framework import viewsets, permissions
|
||||||
|
|
||||||
from uncloud_pay.models import PricingPlan
|
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
|
import uncloud_pay.stripe as uncloud_stripe
|
||||||
from .models import VMInstance
|
from .models import VMInstance
|
||||||
from .serializers import *
|
from .serializers import *
|
||||||
from .utils import render_to_pdf
|
from .utils import *
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -195,6 +196,7 @@ class OrderDetailsView(DetailView):
|
||||||
request.session.get('order'))
|
request.session.get('order'))
|
||||||
if order:
|
if order:
|
||||||
bill = Bill.create_next_bill_for_user_address(billing_address)
|
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}")
|
payment= Payment.withdraw(owner=request.user, amount=total, notes=f"BILL #{bill.id}")
|
||||||
if payment:
|
if payment:
|
||||||
#Close the bill as the payment has been added
|
#Close the bill as the payment has been added
|
||||||
|
@ -244,26 +246,40 @@ class OrderSuccessView(DetailView):
|
||||||
'order': self.request.session.get('order'),
|
'order': self.request.session.get('order'),
|
||||||
'balance': get_balance_for_user(self.request.user)
|
'balance': get_balance_for_user(self.request.user)
|
||||||
}
|
}
|
||||||
# if ('order' not in request.session):
|
if ('order' not in request.session):
|
||||||
# return HttpResponseRedirect(reverse('matrix:index'))
|
return HttpResponseRedirect(reverse('matrix:index'))
|
||||||
return render(request, self.template_name, context)
|
return render(request, self.template_name, context)
|
||||||
|
|
||||||
class InvoiceDownloadView(View):
|
class InvoiceDownloadView(View):
|
||||||
def get(self, request, *args, **kwargs):
|
template = 'matrixhosting/invoice.html'
|
||||||
data = {
|
filename = 'invoice.pdf'
|
||||||
'today': datetime.date.today(),
|
|
||||||
'amount': 39.99,
|
@method_decorator(login_required)
|
||||||
'customer_name': 'Cooper Mann',
|
def dispatch(self, *args, **kwargs):
|
||||||
'order_id': 1233434,
|
return super().dispatch(*args, **kwargs)
|
||||||
}
|
|
||||||
pdf = render_to_pdf('matrixhosting/invoice.html', data)
|
def get_context_data(self, **kwargs):
|
||||||
if pdf:
|
context = {'base_url': f'{self.request.scheme}://{self.request.get_host()}'}
|
||||||
response = HttpResponse(pdf, content_type='application/pdf')
|
bill = Bill.objects.get(id=self.request.session.get('bill_id'))
|
||||||
content = "inline; filename=invoice.pdf"
|
if bill:
|
||||||
content = "attachment; filename=invoice.pdf"
|
context['bill'] = bill
|
||||||
response['Content-Disposition'] = content
|
context['vat_rate'] = str(round(bill.vat_rate * 100, 2)) + '%'
|
||||||
return response
|
context['tax_amount'] = round(bill.vat_rate * bill.subtotal, 2)
|
||||||
return HttpResponse("Not found")
|
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):
|
class Dashboard(ListView):
|
||||||
|
|
|
@ -7,8 +7,9 @@ fontawesome-free
|
||||||
psycopg2
|
psycopg2
|
||||||
ldap3
|
ldap3
|
||||||
django-allauth
|
django-allauth
|
||||||
|
django-compressor
|
||||||
xmltodict
|
xmltodict
|
||||||
xhtml2pdf
|
django-wkhtmltopdf
|
||||||
parsedatetime
|
parsedatetime
|
||||||
# Follow are for creating graph models
|
# Follow are for creating graph models
|
||||||
pyparsing
|
pyparsing
|
||||||
|
|
|
@ -62,6 +62,8 @@ INSTALLED_APPS = [
|
||||||
'django.contrib.sites',
|
'django.contrib.sites',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
'django_extensions',
|
'django_extensions',
|
||||||
|
'compressor',
|
||||||
|
'wkhtmltopdf',
|
||||||
'rest_framework',
|
'rest_framework',
|
||||||
'django_q',
|
'django_q',
|
||||||
'notifications',
|
'notifications',
|
||||||
|
@ -90,6 +92,7 @@ MIDDLEWARE = [
|
||||||
]
|
]
|
||||||
|
|
||||||
ROOT_URLCONF = 'uncloud.urls'
|
ROOT_URLCONF = 'uncloud.urls'
|
||||||
|
WKHTMLTOPDF_CMD = "/usr/local/bin/wkhtmltopdf"
|
||||||
|
|
||||||
TEMPLATES = [
|
TEMPLATES = [
|
||||||
{
|
{
|
||||||
|
@ -188,11 +191,13 @@ USE_TZ = True
|
||||||
# Static files (CSS, JavaScript, Images)
|
# Static files (CSS, JavaScript, Images)
|
||||||
# https://docs.djangoproject.com/en/3.0/howto/static-files/
|
# https://docs.djangoproject.com/en/3.0/howto/static-files/
|
||||||
STATIC_URL = '/static/'
|
STATIC_URL = '/static/'
|
||||||
STATICFILES_DIRS = [ os.path.join(BASE_DIR, "static") ]
|
STATIC_ROOT = os.path.join(BASE_DIR, "static")
|
||||||
STATICFILES_FINDERS = [
|
STATICFILES_FINDERS = [
|
||||||
'django.contrib.staticfiles.finders.FileSystemFinder',
|
'django.contrib.staticfiles.finders.FileSystemFinder',
|
||||||
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
|
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
|
||||||
|
'compressor.finders.CompressorFinder',
|
||||||
]
|
]
|
||||||
|
COMPRESS_ENABLED = True
|
||||||
|
|
||||||
#VM Deployment TEMPLATE
|
#VM Deployment TEMPLATE
|
||||||
GITLAB_SERVER = env('GITLAB_SERVER')
|
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)
|
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)
|
product = models.ForeignKey(Product, blank=False, null=False, on_delete=models.CASCADE)
|
||||||
config = models.JSONField()
|
config = models.JSONField()
|
||||||
|
|
||||||
|
@ -806,6 +804,19 @@ class Order(models.Model):
|
||||||
@property
|
@property
|
||||||
def is_one_time(self):
|
def is_one_time(self):
|
||||||
return not self.is_recurring
|
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):
|
def replace_with(self, new_order):
|
||||||
new_order.replaces = self
|
new_order.replaces = self
|
||||||
|
@ -1011,6 +1022,11 @@ class Bill(models.Model):
|
||||||
bill_records = BillRecord.objects.filter(bill=self)
|
bill_records = BillRecord.objects.filter(bill=self)
|
||||||
return sum([ br.sum for br in bill_records ])
|
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
|
@property
|
||||||
def vat_rate(self):
|
def vat_rate(self):
|
||||||
return VATRate.get_vat_rate(self.billing_address, when=self.ending_date)
|
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.
|
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)
|
order = models.ForeignKey(Order, on_delete=models.CASCADE)
|
||||||
|
|
||||||
creation_date = models.DateTimeField(auto_now_add=True)
|
creation_date = models.DateTimeField(auto_now_add=True)
|
||||||
|
@ -1142,12 +1158,30 @@ class BillRecord(models.Model):
|
||||||
else:
|
else:
|
||||||
return self.order.one_time_price
|
return self.order.one_time_price
|
||||||
|
|
||||||
|
@property
|
||||||
|
def description(self):
|
||||||
|
if self.order:
|
||||||
|
return self.order.description
|
||||||
|
return ''
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def price(self):
|
def price(self):
|
||||||
if self.is_recurring_record:
|
if self.is_recurring_record:
|
||||||
return self.order.recurring_price
|
return self.order.recurring_price
|
||||||
else:
|
else:
|
||||||
return self.order.one_time_price
|
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):
|
def __str__(self):
|
||||||
if self.is_recurring_record:
|
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)
|
'amount_with_vat': round(float(discount_amount_with_vat), 2)
|
||||||
}
|
}
|
||||||
subtotal_after_discount = subtotal - discount["amount"]
|
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)
|
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,
|
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,
|
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(memory) * pricing.ram_unit_price) +
|
||||||
(decimal.Decimal(storage) * (pricing.storage_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)
|
apply_vat_discount(subtotal, pricing, vat_rate, vat_validation_status)
|
||||||
return {
|
return {
|
||||||
"name": pricing.name,
|
"name": pricing.name,
|
||||||
"subtotal": subtotal,
|
"subtotal": subtotal,
|
||||||
"discount": discount,
|
"discount": discount,
|
||||||
"vat": vat, "vat_percent": vat_percent,
|
"vat": vat, "vat_percent": vat_percent,
|
||||||
|
'vat_amount': vat_amount,
|
||||||
"vat_validation_status": vat_validation_status,
|
"vat_validation_status": vat_validation_status,
|
||||||
"subtotal_after_discount": subtotal_after_discount,
|
"subtotal_after_discount": subtotal_after_discount,
|
||||||
"total": price_after_discount_with_vat
|
"total": price_after_discount_with_vat
|
||||||
|
|
Loading…
Reference in a new issue