- Fix project list show unknown

- Fix download usage cose
- Add reset transaction button on disabled billing
This commit is contained in:
Setyo Nugroho 2023-12-10 22:03:04 +07:00
parent 1e1ee561d6
commit 6c2bec211f
16 changed files with 317 additions and 96 deletions

View file

@ -28,7 +28,8 @@
<div class="panel-body">
<p>Please make sure all price is already configured before enable billing</p>
<a href="{% url 'horizon:admin:billing_setting:enable_billing' %}" class="btn btn-primary {% if missing_price.has_missing or missing_setting.has_missing %} disabled {% endif %}">Enable</a>
<a href="{% url 'horizon:admin:billing_setting:reset_billing' %}" class="btn btn-danger">Reset Billing Data</a>
<a href="{% url 'horizon:admin:billing_setting:reset_billing' %}" class="btn btn-danger">Reset All Billing Data</a>
<a href="{% url 'horizon:admin:billing_setting:reset_transaction' %}" class="btn btn-danger">Reset Transaction Data</a>
</div>
</div>
{% endif %}

View file

@ -19,6 +19,7 @@ urlpatterns = [
url(r'^enable_billing$', views.EnableBillingView.as_view(), name='enable_billing'),
url(r'^disable_billing$', views.DisableBillingView.as_view(), name='disable_billing'),
url(r'^reset_billing$', views.ResetBillingView.as_view(), name='reset_billing'),
url(r'^reset_transaction$', views.ResetTransactionView.as_view(), name='reset_transaction'),
url(r'^update_setting/$',
views.UpdateSettingView.as_view(), name='update_setting'),
]

View file

@ -111,3 +111,15 @@ class ResetBillingView(views.APIView):
exceptions.handle(self.request,
_("Unable to reset billing"))
return shortcuts.redirect("horizon:admin:billing_setting:index")
class ResetTransactionView(views.APIView):
invoice_uc = InvoiceUseCase()
def get(self, request, *args, **kwargs):
try:
self.invoice_uc.reset_transaction(request)
messages.success(request, _("Data successfully Reset."))
except Exception:
exceptions.handle(self.request,
_("Unable to reset transaction"))
return shortcuts.redirect("horizon:admin:billing_setting:index")

View file

@ -41,7 +41,7 @@ class IndexView(tables.DataTableView):
if hasattr(self, "table"):
context[self.context_object_name] = self.table
context['select_list'], _ = api.keystone.tenant_list(self.request, user=self.request.user.id)
context['select_list'], _ = api.keystone.tenant_list(self.request)
context['current_tenant_id'] = self.request.GET.get('tenant_id', None)
return context
@ -67,6 +67,7 @@ class ReadAllView(views.APIView):
try:
notifications = self.notification_uc.get_list(self.request,
filter_selection=self.kwargs['selection'])
for n in notifications:
if not n["is_read"]:
self.notification_uc.set_read(request, n['id'])

View file

@ -34,7 +34,7 @@ class IndexView(tables.DataTableView):
def get_data(self):
try:
data = []
project_list, has_more = api.keystone.tenant_list(self.request, user=self.request.user.id)
project_list, has_more = api.keystone.tenant_list(self.request)
for d in self.balance_uc.list(self.request):
project = list(filter(lambda x: x.id == d['project']['tenant_id'], project_list))
project_name = 'Unknown Project'

View file

@ -0,0 +1,139 @@
<html>
<head>
<title>Invoice Download</title>
{% include '_stylesheets.html' %}
</head>
<body>
<style>
.invoice {
padding: 30px;
}
.invoice h2 {
margin-top: 0px;
line-height: 0.8em;
}
.invoice .small {
font-weight: 300;
}
.invoice hr {
margin-top: 10px;
border-color: #ddd;
}
.invoice .table tr.line {
border-bottom: 1px solid #ccc;
}
.invoice .table td {
border: none;
}
.invoice .identity {
margin-top: 10px;
font-size: 1.1em;
font-weight: 300;
}
.invoice .identity strong {
font-weight: 600;
}
.grid {
position: relative;
width: 100%;
background: #fff;
color: #666666;
border-radius: 2px;
margin-bottom: 25px;
box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.1);
}
</style>
<div id="invoice">
<div class="container">
<div class="row">
<!-- BEGIN INVOICE -->
<div class="col-xs-12">
<div class="grid invoice">
<div class="grid-body">
<div class="invoice-title">
<div class="row">
<div class="col-xs-12">{{ setting.company_logo }}</div>
</div>
<br />
<div class="row">
<div class="col-xs-12">
<h2>
invoice<br />
<span class="small">order #{{ invoice.id }}</span>
</h2>
</div>
</div>
</div>
<hr />
<div class="row">
<div class="col-xs-6">
<address style="max-width: 30%;">
{{ setting.company_name }} <br />
{{ setting.company_address }}
</address>
</div>
<div class="col-xs-6 text-right">
<address>
<strong>Invoice Month:</strong><br />
{{ invoice.start_date|date:'M Y' }}
<br />
<br />
<strong>Invoice State:</strong><br />
{{ invoice.state_text }}
<br />
<br />
<strong>Subtotal</strong><br />
{{ invoice.subtotal_money }}
<br />
<br />
{% if invoice.state != 1 %}
<strong>Tax</strong><br />
{{ invoice.tax_money }}
<br />
<br />
<strong>Total</strong><br />
{{ invoice.total_money }}
<br />
<br />
{% endif %}
</address>
</div>
</div>
<div class="row">
<div class="col-md-12">
<h3>ORDER SUMMARY</h3>
<br />
<br />
<div id="usage_cost">
<div id="instance-cost">{{ instance_cost_table.render }}</div>
<div id="volume-cost">{{ volume_cost_table.render }}</div>
<div id="floating-ip-cost">{{ floating_ip_cost_table.render }}</div>
<div id="router-cost">{{ router_cost_table.render }}</div>
<div id="snapshot-cost">{{ snapshot_cost_table.render }}</div>
<div id="image-cost">{{ image_cost_table.render }}</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- END INVOICE -->
</div>
</div>
</div>
{% include 'horizon/_scripts.html' %}
<script type="text/javascript">
window.print()
</script>
</body>
</html>

View file

@ -14,7 +14,7 @@
<div class="col-md-6">
<span class="pull-right" data-html2canvas-ignore="true">
{% include 'admin/projects_invoice/manage_invoice_modal.html' with invoice=invoice project_balance_amount=project_balance_amount %}
<button onclick="javascript:downloadPdf();" class="btn btn-default">Download PDF</button>
<a href="{% url "horizon:admin:projects_invoice:usage_cost_print" project_id invoice.id %}" target="_blank" class="btn btn-default">Download PDF</a>
</span>
</div>
</div>
@ -79,36 +79,3 @@
{% endif %}
{% endblock %}
{% block js %}
{{ block.super }}
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"
integrity="sha512-GsLlZN/3F2ErC5ifS5QtgpiJtWd43JWSuIgh7mbzZ8zBps+dvLusV+eNQATqgA/HdeKFVgA5v3S/cIrLF7QnIg=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script type="text/javascript">
function downloadPdf() {
let opt = {
filename: 'usage_cost.pdf',
margin: [16, 16],
enableLinks: true,
image: {type: 'jpeg', quality: 0.98},
pagebreak: { mode: 'avoid-all', },
jsPDF: {unit: 'mm', format: 'a4', orientation: 'portrait'},
}
// clone element to modify
const ucElement = document.getElementById('usage_cost');
const ucElementClone = ucElement.cloneNode(true);
// modify element
const navTabs = ucElementClone.querySelector('.nav-tabs');
navTabs.style.display = 'none';
const tabContent = ucElementClone.querySelectorAll('.tab-pane');
tabContent.forEach(content => {
content.classList.remove("tab-pane");
});
html2pdf().set(opt).from(ucElementClone).save();
}
</script>
{% endblock %}

View file

@ -8,16 +8,3 @@
{% include 'admin/projects_invoice/base_invoice.html' %}
{% endblock %}
{% block js %}
{{ block.super }}
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"
integrity="sha512-GsLlZN/3F2ErC5ifS5QtgpiJtWd43JWSuIgh7mbzZ8zBps+dvLusV+eNQATqgA/HdeKFVgA5v3S/cIrLF7QnIg=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script type="text/javascript">
function downloadPdf() {
window.print();
}
</script>
{% endblock %}

View file

@ -19,6 +19,7 @@ urlpatterns = [
url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^invoice/detail/(?P<project_id>[^/]+)/(?P<id>[^/]+)/$', views.InvoiceView.as_view(), name='invoice_detail'),
url(r'^invoice/usage/(?P<project_id>[^/]+)/(?P<id>[^/]+)/$', views.UsageCostTabView.as_view(), name='usage_cost'),
url(r'^invoice/usage/print/(?P<project_id>[^/]+)/(?P<id>[^/]+)/$', views.UsageCostView.as_view(), name='usage_cost_print'),
url(r'^invoice/finish/(?P<id>[^/]+)/$', views.FinishInvoice.as_view(), name='finish_invoice'),
url(r'^invoice/rollback_to_unpaid/(?P<id>[^/]+)/$', views.RollbackToUnpaidInvoice.as_view(),
name='rollback_to_unpaid'),

View file

@ -46,7 +46,7 @@ class IndexView(tables.DataTableView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['project_list'], _ = api.keystone.tenant_list(self.request, user=self.request.user.id)
context['project_list'], _ = api.keystone.tenant_list(self.request)
context['current_project_id'] = self.request.GET.get('project_id', self.request.user.project_id)
context['current_project_name'] = self.request.GET.get('project_name', self.request.user.project_id)
return context
@ -129,6 +129,7 @@ class UsageCostTabView(tabs.TabbedTableView):
context = super().get_context_data(**kwargs)
balance = self.balance_uc.retrieve_by_project(self.request, self.kwargs['project_id'])
context['invoice'] = self.request.invoice
context['project_id'] = self.kwargs['project_id']
context['project_balance_amount'] = Money(amount=balance['amount'],
currency=balance['amount_currency']) if balance else 0
return context
@ -138,10 +139,12 @@ class UsageCostView(tables.MultiTableView):
table_classes = (
InstanceCostTable, VolumeCostTable, FloatingIpCostTable, RouterCostTable, SnapshotCostTable, ImageCostTable)
page_title = _("Usage Cost")
template_name = "admin/projects_invoice/cost_tables.html"
template_name = "admin/projects_invoice/cost_download.html"
invoice_uc = InvoiceUseCase()
balance_uc = BalanceUseCase()
setting_uc = SettingUseCase()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@ -154,6 +157,7 @@ class UsageCostView(tables.MultiTableView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
balance = self.balance_uc.retrieve_by_project(self.request, self.kwargs['project_id'])
context['setting'] = self.setting_uc.get_settings(self.request)
context['invoice'] = self.request.invoice
context['project_balance_amount'] = Money(amount=balance['amount'],
currency=balance['amount_currency']) if balance else 0

View file

@ -110,6 +110,9 @@ class InvoiceUseCase:
def reset_billing(self, request):
yuyu_client.post(request, f"invoice/reset_billing/", {})
def reset_transaction(self, request):
yuyu_client.post(request, f"invoice/reset_transaction_data/", {})
def finish_invoice(self, request, id, skip_balance):
response = yuyu_client.get(request, f"invoice/{id}/finish/?skip_balance={skip_balance}")
data = response.json()

View file

@ -3,7 +3,7 @@ from openstack_dashboard.dashboards.yuyu.core import yuyu_client
class NotificationCenterUseCase:
def get_list(self, request, filter_selection=None):
if filter_selection is None:
if filter_selection is None or filter_selection == 'None':
return yuyu_client.get(request, f"notification/").json()
return yuyu_client.get(request, f"notification/?tenant_id={filter_selection}").json()

View file

@ -0,0 +1,139 @@
<html>
<head>
<title>Invoice Download</title>
{% include '_stylesheets.html' %}
</head>
<body>
<style>
.invoice {
padding: 30px;
}
.invoice h2 {
margin-top: 0px;
line-height: 0.8em;
}
.invoice .small {
font-weight: 300;
}
.invoice hr {
margin-top: 10px;
border-color: #ddd;
}
.invoice .table tr.line {
border-bottom: 1px solid #ccc;
}
.invoice .table td {
border: none;
}
.invoice .identity {
margin-top: 10px;
font-size: 1.1em;
font-weight: 300;
}
.invoice .identity strong {
font-weight: 600;
}
.grid {
position: relative;
width: 100%;
background: #fff;
color: #666666;
border-radius: 2px;
margin-bottom: 25px;
box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.1);
}
</style>
<div id="invoice">
<div class="container">
<div class="row">
<!-- BEGIN INVOICE -->
<div class="col-xs-12">
<div class="grid invoice">
<div class="grid-body">
<div class="invoice-title">
<div class="row">
<div class="col-xs-12">{{ setting.company_logo }}</div>
</div>
<br />
<div class="row">
<div class="col-xs-12">
<h2>
invoice<br />
<span class="small">order #{{ invoice.id }}</span>
</h2>
</div>
</div>
</div>
<hr />
<div class="row">
<div class="col-xs-6">
<address style="max-width: 30%;">
{{ setting.company_name }} <br />
{{ setting.company_address }}
</address>
</div>
<div class="col-xs-6 text-right">
<address>
<strong>Invoice Month:</strong><br />
{{ invoice.start_date|date:'M Y' }}
<br />
<br />
<strong>Invoice State:</strong><br />
{{ invoice.state_text }}
<br />
<br />
<strong>Subtotal</strong><br />
{{ invoice.subtotal_money }}
<br />
<br />
{% if invoice.state != 1 %}
<strong>Tax</strong><br />
{{ invoice.tax_money }}
<br />
<br />
<strong>Total</strong><br />
{{ invoice.total_money }}
<br />
<br />
{% endif %}
</address>
</div>
</div>
<div class="row">
<div class="col-md-12">
<h3>ORDER SUMMARY</h3>
<br />
<br />
<div id="usage_cost">
<div id="instance-cost">{{ instance_cost_table.render }}</div>
<div id="volume-cost">{{ volume_cost_table.render }}</div>
<div id="floating-ip-cost">{{ floating_ip_cost_table.render }}</div>
<div id="router-cost">{{ router_cost_table.render }}</div>
<div id="snapshot-cost">{{ snapshot_cost_table.render }}</div>
<div id="image-cost">{{ image_cost_table.render }}</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- END INVOICE -->
</div>
</div>
</div>
{% include 'horizon/_scripts.html' %}
<script type="text/javascript">
window.print()
</script>
</body>
</html>

View file

@ -18,7 +18,7 @@
</select>
</div>
<div class="col-md-6">
<button onclick="javascript:downloadPdf();" class="btn btn-default pull-right" data-html2canvas-ignore="true">Download PDF</button>
<a href="{% url "horizon:project:usage_cost:download" %}?invoice_id={{ invoice.id }}" target="_blank" class="btn btn-default pull-right">Download PDF</a>
</div>
</div>
@ -81,42 +81,3 @@
<h1>You don't have any usage yet</h1> <br/>
{% endif %}
{% endblock %}
{% block js %}
{{ block.super }}
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"
integrity="sha512-GsLlZN/3F2ErC5ifS5QtgpiJtWd43JWSuIgh7mbzZ8zBps+dvLusV+eNQATqgA/HdeKFVgA5v3S/cIrLF7QnIg=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script type="text/javascript">
function onInvoiceChange(val) {
var search = "?invoice_id=" + val;
window.location.href = window.location.protocol + "//" + window.location.host + window.location.pathname + search;
}
function downloadPdf() {
let opt = {
filename: 'usage_cost.pdf',
margin: [16, 16],
enableLinks: true,
image: {type: 'jpeg', quality: 0.98},
pagebreak: { mode: 'avoid-all', },
jsPDF: {unit: 'mm', format: 'a4', orientation: 'portrait'},
}
// clone element to modify
const ucElement = document.getElementById('usage_cost');
const ucElementClone = ucElement.cloneNode(true);
// modify element
const navTabs = ucElementClone.querySelector('.nav-tabs');
navTabs.style.display = 'none';
const tabContent = ucElementClone.querySelectorAll('.tab-pane');
tabContent.forEach(content => {
content.classList.remove("tab-pane");
});
html2pdf().set(opt).from(ucElementClone).save();
}
</script>
{% endblock %}

View file

@ -17,4 +17,5 @@ from openstack_dashboard.dashboards.yuyu.project.usage_cost import views
urlpatterns = [
url(r'^$', views.IndexViewTab.as_view(), name='index'),
url(r'^download/$', views.DownloadView.as_view(), name='download'),
]

View file

@ -17,6 +17,7 @@ from djmoney.money import Money
from horizon import exceptions, tables, tabs
from openstack_dashboard import api
from openstack_dashboard.dashboards.yuyu.cases.invoice_use_case import InvoiceUseCase
from openstack_dashboard.dashboards.yuyu.cases.setting_use_case import SettingUseCase
from openstack_dashboard.dashboards.yuyu.core.usage_cost.tables import InstanceCostTable, VolumeCostTable, \
FloatingIpCostTable, RouterCostTable, SnapshotCostTable, ImageCostTable
from openstack_dashboard.dashboards.yuyu.core.usage_cost.tabs import UsageCostTabs
@ -51,13 +52,14 @@ class IndexViewTab(tabs.TabbedTableView):
return context
class IndexView(tables.MultiTableView):
class DownloadView(tables.MultiTableView):
table_classes = (
InstanceCostTable, VolumeCostTable, FloatingIpCostTable, RouterCostTable, SnapshotCostTable, ImageCostTable)
page_title = _("Usage Cost")
template_name = "project/usage_cost/cost_tables.html"
template_name = "project/usage_cost/cost_download.html"
invoice_uc = InvoiceUseCase()
setting_uc = SettingUseCase()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@ -71,12 +73,14 @@ class IndexView(tables.MultiTableView):
invoice_id = request.invoice_list[0]['id']
request.invoice = self.invoice_uc.get_invoice(self.request, invoice_id) if request.has_invoice else {}
return super(IndexView, self).get(request, *args, **kwargs)
return super(DownloadView, self).get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['invoice_list'] = self.request.invoice_list
context['invoice'] = self.request.invoice
context['setting'] = self.setting_uc.get_settings(self.request)
return context
def _get_flavor_name(self, flavor_id):