Task #9530 bills that include vat per month in html or pdf format

This commit is contained in:
amalelshihaby 2021-08-13 10:06:13 +02:00
parent 1c3d3efb3a
commit 1aeb757e22
17 changed files with 298 additions and 39 deletions

View file

@ -1325,7 +1325,7 @@ body, html {
}
.transaction-title {
background-color: #f1f5f6;
background-color: #d8e4e6;
border-top: 1px solid #e9eff0;
border-bottom: 1px solid #e9eff0;
}
@ -1795,7 +1795,7 @@ body, html {
position: relative;
}
.accordion:not(.accordion-alternate) .card-header a {
background-color: #e41d25;
background-color: #2b343c;
color: #fff;
}
.accordion:not(.accordion-alternate) .card-header a.collapsed {
@ -1826,8 +1826,10 @@ body, html {
}
.accordion .card-body {
line-height: 26px;
padding: 1rem 0 1rem 2.25rem;
border-left: 4px solid #2b343c;
background-color: #f9f9f9;
}
.accordion.arrow-right .card-header a {
padding-left: 1.25rem;
}

View file

@ -17,7 +17,7 @@ function fetch_pricing() {
if(data['total'] > balance) {
$('#has-enough-balance').hide();
$('#cards-section').show();
window.cardNumberElement = loadStripe(stripe);
window.cardNumberElement = loadStripe(window.stripe);
} else {
$('#cards-section').hide();
$('#has-enough-balance').show();
@ -44,7 +44,7 @@ function incrementValue(e) {
};
$(document).ready(function () {
var stripe = Stripe(window.stripeKey);
window.stripe = Stripe(window.stripeKey);
if ($('#pricing_name') != undefined) {
fetch_pricing();
@ -56,7 +56,7 @@ $(document).ready(function () {
var hasCreditcard = window.hasCreditcard || false;
if (hasCreditcard) {
window.cardNumberElement = loadStripe(stripe);
window.cardNumberElement = loadStripe(window.stripe);
}
function submitBillingForm(pmId) {
@ -76,7 +76,7 @@ $(document).ready(function () {
$('#id_payment_method').val(paymentMethod.id);
submitBillingForm(paymentMethod.id);
}
stripe.createPaymentMethod({
window.stripe.createPaymentMethod({
type: 'card',
card: window.cardNumberElement,
})

View file

@ -0,0 +1,196 @@
{% extends "matrixhosting/base.html" %}
{% load static i18n compress mathfilters %}
{% block title %} Bills {% endblock %}
{% block content %}
<div class="container">
<div class="bg-white shadow-md rounded p-4 my-4">
<div class="row">
<div class="col-md-3">
<ul class="nav nav-tabs flex-column" id="myTabVertical" role="tablist">
<li class="nav-item"> <a class="nav-link" id="first-tab" href="{% url 'matrixhosting:billing' %}">{% trans "Payment Moves"%}</a> </li>
<li class="nav-item"> <a class="nav-link active" id="second-tab" href="{% url 'matrixhosting:bills' %}">{% trans "Bills"%}</a> </li>
<li class="nav-item"> <a class="nav-link" id="fourth-tab" href="{% url 'matrixhosting:cards' %}">{% trans "Payment Methods"%}</a> </li>
</ul>
</div>
<div class="col-md-9">
<div class="tab-content" id="myTabContentVertical">
<div class="tab-pane fade show active" id="bills" role="tabpanel" aria-labelledby="bills">
<div class="">
<!-- Filter
============================================= -->
<div class="row">
<div class="col mb-2">
<form id="filterBills" method="post">
<div class="form-row">
<!-- Date Range
========================= -->
<div class="col-sm-6 col-md-6 form-group">
<h3 class="text-5 font-weight-400 d-flex align-items-center px-4 mb-4">{% trans "Bills"%}</h3>
<!-- <input id="dateRange" type="text" class="form-control" placeholder="Date Range">
<span class="icon-inside"><i class="fas fa-calendar-alt"></i></span> -->
</div>
<div class="col col-sm-6 text-right text-2">
<div class="featured-box float-right style-3">
<div class="featured-box-icon text-9 text-light"> <i class="fas fa-wallet"></i> </div>
<h3 class="text-6 font-weight-400 text-dark">{{balance}} CHF</h3>
<p class="text-muted text-3 opacity-8">{% trans "Available Balance"%}</p>
</div>
</div>
</div>
</form>
</div>
</div>
<!-- Filter End -->
<!-- All Transactions
============================================= -->
<div class="px-2 py-4 mb-4">
<!-- Title
=============================== -->
<div class="transaction-title mb-1 py-2">
<div class="row">
<div class="col-3 col-sm-3 text-center"><span class="">{% trans "Bill No."%}</span></div>
<div class="col-2 col-sm-2 text-center"><span class="">{% trans "Creation Date"%}</span></div>
<div class="col-2 col-sm-2 text-center">{% trans "Amount"%}</div>
<div class="col-2 col-sm-2 text-center">{% trans "Due Date"%}</div>
<div class="col-1 col-sm-1 text-center">{% trans "Closed"%}</div>
<div class="col-2 col-sm-2 text-center">{% trans "Download"%}</div>
</div>
</div>
<!-- Title End -->
<!-- Transaction List
=============================== -->
<div class="bills-list">
<div class="accordion" id="accordionDefault">
{% for bill in object_list %}
<div class="card">
<div class="card-header" id="heading{{bill.id}}">
<a href="#" data-toggle="collapse" data-target="#collapse{{bill.id}}" aria-expanded="false" aria-controls="collapse{{bill.id}}" class="collapsed">
<div class="row align-items-center flex-row">
<div class="col-3 col-sm-3 text-center"> <span class="text-2 font-weight-400">#{{bill.id}}</span></div>
<div class="col-2 col-sm-2 text-center"> <span class="text-2 font-weight-300">{{bill.creation_date|date:"Y-m-d"}}</span></div>
<div class="col-2 col-sm-2 text-center"><span class="text-2 font-weight-300">{{bill.sum}}</span><span class="text-1 text-uppercase"> {{bill.currency}}</span></div>
<div class="col-2 col-sm-2 text-center"> <span class="text-2 font-weight-300">{{bill.due_date|date:"Y-m-d"}}</span> </div>
<div class="col-1 col-sm-1 text-center text-3">
{% if bill.is_closed %}
<span class="text-success" data-toggle="tooltip" data-original-title="Closed or Paid"><i class="fas fa-check-circle"></i></span>
{%else%}
<span class="text-danger" data-toggle="tooltip" data-original-title="Pending"><i class="text-danger fas fa-times-circle"></i></span>
{%endif%}
</div>
<div class="col-2 col-sm-2 text-center text-3">
<form method="get" action="{% url 'matrix:invoice_download' bill_id=bill.id %}">
<button class="download-bill border border-primary" type="submit"><span class="text-primary" data-toggle="tooltip" data-original-title="Download"><i class="text-primary fas fa-file-download"></i></span></button>
</form>
</div>
</div>
</a>
</div>
<div id="collapse{{bill.id}}" class="collapse" aria-labelledby="{{bill.id}}" data-parent="#accordionDefault">
<div class="mt-n1 card-body">
<h6>Bill Lines:</h6>
<div class="table-responsive">
<table class="bg-white table table-bordered text-1">
<thead>
<tr>
<th>Order</th>
<th>Description</th>
<th>Start Date</th>
<th>End Date</th>
<th>Subtotal</th>
<th>Total</th>
</tr>
</thead>
<tbody>
{% for record in bill.bill_records.all %}
<tr>
<td>#{{record.order.id}}</td>
<td>{{record.description}}</td>
<td>{{record.starting_date|date:"Y-m-d"}}</td>
<td>{{record.ending_date|date:"Y-m-d"}}</td>
<td>{{record.subtotal}}</td>
<td>{{record.sum}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="row">
<div class="col-sm-6"></div>
<div class="col-sm-6">
<div class="row text-1 text-right">
<div class="col-md-6 col-sm-6 col-xs-6">
<p><strong>{% trans "Subtotal" %}</strong></p>
</div>
<div class="col-md-6 col-sm-6 col-xs-6">
<p><strong class="pull-right">{{bill.subtotal}}</strong></p>
</div>
</div>
<div class="row text-right">
<div class="col-md-6 col-sm-6 col-xs-6">
<p><span>{{bill.billing_address.get_country_display}} VAT {{ bill.vat_rate|mul:100 }}%</span></p>
</div>
<div class="col-md-6 col-sm-6 col-xs-6">
<p><span class="pull-right" >{{bill.vat_amount}}</span></p>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-6"></div>
<div class="col-sm-6">
<div class="row text-1 text-right">
<div class="col-md-6 col-sm-6 col-xs-6">
<p><strong>{% trans "Total" %}</strong></p>
</div>
<div class="col-md-6 col-sm-6 col-xs-6">
<p><strong class="pull-right">{{bill.sum|floatformat:2}} CHF</strong></p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{%endfor%}
</div>
</div>
<!-- Transaction List End -->
<!-- Pagination will handled later
============================================= -->
<ul class="pagination justify-content-center mt-4 mb-0" style="display: none;">
<li class="page-item disabled"> <a class="page-link" href="#" tabindex="-1"><i class="fas fa-angle-left"></i></a> </li>
<li class="page-item"><a class="page-link" href="#">1</a></li>
<li class="page-item active"> <a class="page-link" href="#">2 <span class="sr-only">(current)</span></a> </li>
<li class="page-item"><a class="page-link" href="#">3</a></li>
<li class="page-item d-flex align-content-center flex-wrap text-muted text-5 mx-1">......</li>
<li class="page-item"><a class="page-link" href="#">15</a></li>
<li class="page-item"> <a class="page-link" href="#"><i class="fas fa-angle-right"></i></a> </li>
</ul>
<!-- Paginations end -->
</div>
<!-- All Transactions End -->
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block js_extra %}
<script>
$('.download-bill').click(function (e) {
e.preventDefault();
e.stopPropagation();
$(e.target).closest("form").submit();
});
</script>
{% endblock js_extra %}

View file

@ -2,16 +2,17 @@
{% load static i18n compress %}
{% block title %} Payments {% endblock %}
{% block title %} Payment Methods {% endblock %}
{% block content %}
<div class="container">
<div class="bg-white shadow-md rounded p-4 m-4">
<div class="bg-white shadow-md rounded p-4 my-4">
<div class="row">
<div class="col-md-3">
<ul class="nav nav-tabs flex-column" id="myTabVertical" role="tablist">
<li class="nav-item"> <a class="nav-link" id="first-tab" href="{% url 'matrixhosting:billing' %}">Payment Moves</a> </li>
<li class="nav-item"> <a class="nav-link active" id="fourth-tab" href="{% url 'matrixhosting:cards' %}">Payment Methods</a> </li>
<li class="nav-item"> <a class="nav-link" id="first-tab" href="{% url 'matrixhosting:billing' %}">{% trans "Payment Moves"%}</a> </li>
<li class="nav-item"> <a class="nav-link" id="second-tab" href="{% url 'matrixhosting:bills' %}">{% trans "Bills"%}</a> </li>
<li class="nav-item"> <a class="nav-link active" id="fourth-tab" href="{% url 'matrixhosting:cards' %}">{% trans "Payment Methods"%}</a> </li>
</ul>
</div>
<div class="col-md-9">
@ -23,8 +24,8 @@
<div class="col-sm-12 col-lg-12">
<div class="featured-box style-3 float-right mb-4">
<div class="featured-box-icon text-17 text-light"> <i class="fas fa-wallet"></i> </div>
<h3 class="text-9 font-weight-400">{{balance}} CHF</h3>
<a href="" data-target="#make-deposit" data-toggle="modal">Make a deposit</a>
<h3 class="text-7 font-weight-400">{{balance}} CHF</h3>
<a href="" data-target="#make-deposit" data-toggle="modal">{% trans "Make a deposit"%}</a>
</div>
</div>
</div>
@ -34,7 +35,6 @@
<div class="row">
<div class="col-12">
<h3 class="text-4 font-weight-400 mb-4">{% trans "Credit or Debit Cards"%} <span class="text-muted text-3">({% trans "for payments"%})</span></h3>
<hr class="mt-0 mb-2 mx-n8">
<div class="row">
{% for card in object_list %}
<div class="col-12 col-sm-6 col-lg-4 mt-2">
@ -44,7 +44,7 @@
thru<br>
</span> <span class="text-4 opacity-9">{{card.expiry_date|date:"m"}}/{{card.expiry_date|date:"y"}}</span> {% if card.active %}<span class="badge badge-warning text-0 font-weight-500 rounded-pill px-2 ml-auto">{% trans "Primary"%}</span>{%endif%} </p>
<p class="d-flex align-items-center m-0"> <span class="text-uppercase font-weight-500">{{card.card_name}}</span> <img class="ml-auto" src="{% static 'matrixhosting/images/' %}{{card.brand}}.png" alt="visa" title=""> </p>
<div class="account-card-overlay rounded" data-card="{{card.card_id}}"> <a href="#" data-href="{% url 'payments:card_activate'%}" class="activate-btn text-light btn-link mx-2"><span class="mr-1"><i class="fas fa-edit"></i></span>{% trans "Set As Primary"%}</a> <a href="#" data-href="{% url 'stripecreditcard-detail' card.id %}" class="delete-card text-light btn-link mx-2"><span class="mr-1"><i class="fas fa-minus-circle"></i></span>{% trans "Delete"%}</a> </div>
<div class="account-card-overlay rounded" data-card="{{card.id}}"> <a href="#" data-href="{% url 'payments:card_activate'%}" class="activate-btn text-light btn-link mx-2"><span class="mr-1"><i class="fas fa-edit"></i></span>{% trans "Set As Primary"%}</a> <a href="#" data-href="{% url 'stripecreditcard-detail' card.id %}" class="delete-card text-light btn-link mx-2"><span class="mr-1"><i class="fas fa-minus-circle"></i></span>{% trans "Delete"%}</a> </div>
</div>
</div>
{% endfor %}

View file

@ -17,6 +17,7 @@
<!-- Primary Navigation
============================== -->
{% url 'matrix:index' as index_url %}
{% url 'matrix:orders' as orders_url %}
{% url 'matrix:billing' as payments_url %}
<nav class="primary-menu navbar navbar-expand-lg">
<div id="header-nav" class="collapse navbar-collapse">
@ -26,8 +27,8 @@
<li><a href="">Pricing</a></li>
<li><a href="">Contact Us</a></li>
{% else %}
<li><a href=>Dashboard</a></li>
<li class="{% if request.path == payments_url %}active{%endif%}"><a href="{{payments_url}}">{%trans "Payments" %}</a></li>
<li><a class="{% if request.path == orders_url %}active{%endif%}" href="{{orders_url}}">Orders</a></li>
<li class="{% if request.path == payments_url %}active{%endif%}"><a href="{{payments_url}}">{%trans "Billing" %}</a></li>
<li><a href="">Help</a></li>
{% endif %}
</ul>
@ -66,7 +67,6 @@
<li class="text-center text-3 py-2">Hi, {{request.user.username}}</li>
<li class="dropdown-divider mx-n3"></li>
<li><a class="dropdown-item" href=""><i class="fas fa-user"></i>{%trans "My Profile" %}</a></li>
<li><a class="dropdown-item" href=""><i class="fas fa-tachometer-alt"></i>{%trans "Dashboard" %}</a></li>
<li><a class="dropdown-item" href="{% url 'matrix:orders' %}"><i class="fas fa-shopping-cart"></i>{%trans "Orders" %}</a></li>
<li><a class="dropdown-item" href="{% url 'matrix:billing' %}"><i class="fas fa-file-invoice"></i>{%trans "Billing" %}</a></li>
<li><a class="dropdown-item" href="{% url 'matrix:instances' %}"><i class="fas fa-cloud"></i>{%trans "Instances" %}</a></li>

View file

@ -52,7 +52,7 @@
<div class="d4" style="margin-top:15px;">
<div class="bold">
<div class="b1">
<span>{%trans "Invoice Number:" %}</span>
<span>{%trans "Bill Number:" %}</span>
</div>
<div class="b2">
<span>#{{bill.id}}</span>

View file

@ -200,7 +200,7 @@
<p>{% trans "VAT" %}<span class="float-right" id="vat"> {{request.session.pricing.vat_amount}} CHF</span></p>
<p class="text-4 font-weight-500">{% trans "Total To Pay"%}
<small>
({% if matrix_vm_pricing.vat_inclusive %}{%trans "including VAT" %}{% else %}{%trans "excluding VAT" %}{% endif %})
{% if matrix_vm_pricing.vat_inclusive %}({%trans "including VAT" %}){% endif %}
</small>
<span id="total" class="float-right">{{request.session.pricing.total}} CHF</span>
</p>

View file

@ -6,12 +6,13 @@
{% block content %}
<div class="container">
<div class="bg-white shadow-md rounded p-4 m-4">
<div class="bg-white shadow-md rounded p-4 my-4">
<div class="row">
<div class="col-md-3">
<ul class="nav nav-tabs flex-column" id="myTabVertical" role="tablist">
<li class="nav-item"> <a class="nav-link active" id="first-tab" href="{% url 'matrixhosting:billing' %}">Payment Moves</a> </li>
<li class="nav-item"> <a class="nav-link" id="fourth-tab" href="{% url 'matrixhosting:cards' %}">Payment Methods</a> </li>
<li class="nav-item"> <a class="nav-link active" id="first-tab" href="{% url 'matrixhosting:billing' %}">{% trans "Payment Moves"%}</a> </li>
<li class="nav-item"> <a class="nav-link" id="second-tab" href="{% url 'matrixhosting:bills' %}">{% trans "Bills"%}</a> </li>
<li class="nav-item"> <a class="nav-link" id="fourth-tab" href="{% url 'matrixhosting:cards' %}">{% trans "Payment Methods"%}</a> </li>
</ul>
</div>
<div class="col-md-9">

View file

@ -10,9 +10,10 @@ urlpatterns = [
path('order/new/', OrderPaymentView.as_view(), name='payment'),
path('order/confirm/', OrderConfirmationView.as_view(), name='order_confirmation'),
path('order/success/', OrderSuccessView.as_view(), name='order_success'),
path('order/invoice/download', InvoiceDownloadView.as_view(), name='invoice_download'),
path('order/invoice/<int:bill_id>/download/', InvoiceDownloadView.as_view(), name='invoice_download'),
path('billing/', PaymentsView.as_view(), name='billing'),
path('billing/cards', CardsView.as_view(), name='cards'),
path('billing/bills', BillsView.as_view(), name='bills'),
path('instances/', InstancesView.as_view(), name='instances'),
path('orders/', OrdersView.as_view(), name='orders'),
path('', IndexView.as_view(), name='index'),

View file

@ -260,7 +260,6 @@ class OrderSuccessView(DetailView):
class InvoiceDownloadView(View):
template = 'matrixhosting/invoice.html'
filename = 'invoice.pdf'
@method_decorator(login_required)
def dispatch(self, *args, **kwargs):
@ -268,21 +267,22 @@ class InvoiceDownloadView(View):
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'))
return context
def get(self, request, bill_id):
cmd_options = settings.REPORT_FORMAT
context = self.get_context_data()
bill = Bill.objects.get(owner=self.request.user, id=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 = settings.REPORT_FORMAT
return PDFTemplateResponse(request=request,
template=self.template,
filename = self.filename,
filename = f"bill-{bill_id}.pdf",
cmd_options= cmd_options,
footer_template= 'matrixhosting/includes/invoice_footer.html',
context= self.get_context_data())
context= context)
class OrdersView(ListView):
@ -360,4 +360,24 @@ class CardsView(ListView):
uncloud_stripe.sync_cards_for_user(self.request.user)
return StripeCreditCard.objects.filter(owner=self.request.user).order_by('-active')
class BillsView(ListView):
template_name = "matrixhosting/bills.html"
model = Bill
@method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super().dispatch(*args, **kwargs)
def get_context_data(self, **kwargs):
context = super(BillsView, self).get_context_data(**kwargs)
context = super().get_context_data(**kwargs)
context.update({
'balance': get_balance_for_user(self.request.user),
})
return context
def get_queryset(self):
return Bill.objects.filter(owner=self.request.user).order_by('-creation_date')

View file

@ -3,6 +3,7 @@ Django==3.2.4
djangorestframework
django-auth-ldap
fontawesome-free
django-mathfilters
psycopg2
ldap3

View file

@ -16,4 +16,4 @@ GITLAB_PROJECT_ID=
GITLAB_OAUTH_TOKEN=
GITLAB_AUTHOR_EMAIL=
GITLAB_AUTHOR_NAME=
WKHTMLTOPDF_CMD=/usr/bin/wkhtmltopdf
WKHTMLTOPDF_CMD=/usr/local/bin/wkhtmltopdf

View file

@ -62,6 +62,7 @@ INSTALLED_APPS = [
'django.contrib.sites',
'django.contrib.staticfiles',
'django_extensions',
'mathfilters',
'compressor',
'wkhtmltopdf',
'rest_framework',

View file

@ -0,0 +1,18 @@
# Generated by Django 3.2.4 on 2021-08-12 16:00
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('uncloud_pay', '0026_remove_order_description'),
]
operations = [
migrations.AlterField(
model_name='stripecreditcard',
name='card_name',
field=models.CharField(default='', max_length=128),
),
]

View file

@ -0,0 +1,18 @@
# Generated by Django 3.2.4 on 2021-08-12 16:06
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('uncloud_pay', '0027_alter_stripecreditcard_card_name'),
]
operations = [
migrations.AddField(
model_name='bill',
name='currency',
field=models.CharField(choices=[('CHF', 'Swiss Franc')], default='CHF', max_length=32),
),
]

View file

@ -997,6 +997,8 @@ class Bill(models.Model):
on_delete=models.CASCADE,
editable=True,
null=False)
currency = models.CharField(max_length=32, choices=Currency.choices, default=Currency.CHF)
# FIXME: editable=True -> is in the admin, but also editable in DRF
# Maybe filter fields in the serializer?
@ -1030,6 +1032,10 @@ class Bill(models.Model):
bill_records = BillRecord.objects.filter(bill=self)
return sum([ br.subtotal for br in bill_records ])
@property
def vat_amount(self):
return round(self.vat_rate * self.subtotal, 2)
@property
def vat_rate(self):
return VATRate.get_vat_rate(self.billing_address, when=self.ending_date)
@ -1086,8 +1092,6 @@ class Bill(models.Model):
Create the next bill for a specific order of a user
"""
bill = cls.get_or_create_bill(order.billing_address, ending_date=ending_date)
print("??????????????????????????????")
print(bill.id)
order.create_bill_record(bill)
return bill
@ -1206,9 +1210,6 @@ class BillRecord(models.Model):
return bill_line
def save(self, *args, **kwargs):
print(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>")
print(self.ending_date)
print(self.starting_date)
if self.ending_date < self.starting_date:
raise ValidationError("End date cannot be before starting date")

View file

@ -58,7 +58,7 @@ class CardActivateView(View):
def post(self, request, **args):
card_id = request.POST.get('card_id')
if card_id:
matched_card = StripeCreditCard.objects.filter(owner=self.request.user, card_id=card_id).first()
matched_card = StripeCreditCard.objects.filter(owner=self.request.user, id=card_id).first()
matched_card.activate()
return JsonResponse({'success': 1})
else: