Add the invoice template

This commit is contained in:
amalelshihaby 2021-08-04 12:03:55 +02:00
parent 030a0cd501
commit 5bb0c4cdda
24 changed files with 462 additions and 96 deletions

1
.gitignore vendored
View File

@ -26,3 +26,4 @@ dist/
*.iso *.iso
*.sqlite3 *.sqlite3
.DS_Store .DS_Store
static/CACHE/

View 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;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@ -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);

View File

@ -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>

View File

@ -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>

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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'

View File

@ -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

View File

@ -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):

View File

@ -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

View File

@ -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')

View 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),
),
]

View 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',
),
]

View File

@ -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:

View File

@ -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