Skip to content
Snippets Groups Projects
Commit 795ed859 authored by Nik | Klampfradler's avatar Nik | Klampfradler
Browse files

Merge branch '1-add-ui-for-invoice-management' into 'master'

Resolve "Add UI for invoice management"

Closes #1

See merge request !4
parents e67e9486 e28c6fa9
No related branches found
No related tags found
1 merge request!4Resolve "Add UI for invoice management"
Pipeline #58881 failed
Showing
with 450 additions and 10 deletions
from material import Layout, Row
from aleksis.core.mixins import ExtensibleForm
from .models.base import Client
from .models.invoice import InvoiceGroup
class EditClientForm(ExtensibleForm):
"""Form to create or edit clients."""
layout = Layout("name")
class Meta:
model = Client
exclude = []
class EditInvoiceGroupForm(ExtensibleForm):
layout = Layout(
Row("name", "template_name")
)
class Meta:
model = InvoiceGroup
exclude = ["client"]
from django.utils.translation import gettext_lazy as _
MENUS = {
"NAV_MENU_CORE": [
{
"name": _("Payments"),
"url": "#",
"root": True,
"icon": "price_check",
"validators": [
"menu_generator.validators.is_authenticated",
"aleksis.core.util.core_helpers.has_person",
],
"submenu": [
{
"name": _("Clients"),
"url": "clients",
"icon": "account_balance",
"validators": [
(
"aleksis.core.util.predicates.permission_validator",
"tezor.can_view_clients",
)
],
},
],
}
]
}
...@@ -11,3 +11,6 @@ class Client(ExtensibleModel): ...@@ -11,3 +11,6 @@ class Client(ExtensibleModel):
constraints = [ constraints = [
models.UniqueConstraint(fields=["name", "site"], name="uniq_client_per_site") models.UniqueConstraint(fields=["name", "site"], name="uniq_client_per_site")
] ]
def __str__(self) -> str:
return self.name
...@@ -21,6 +21,10 @@ class InvoiceGroup(ExtensibleModel): ...@@ -21,6 +21,10 @@ class InvoiceGroup(ExtensibleModel):
template_name = models.CharField(verbose_name=_("Template to render invoices with as PDF"), blank=True, max_length=255) template_name = models.CharField(verbose_name=_("Template to render invoices with as PDF"), blank=True, max_length=255)
def __str__(self) -> str:
return self.name
class Meta: class Meta:
constraints = [ constraints = [
models.UniqueConstraint(fields=["client", "name"], name="group_uniq_per_client") models.UniqueConstraint(fields=["client", "name"], name="group_uniq_per_client")
......
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
import django_tables2 as tables import django_tables2 as tables
from django_tables2.utils import A
class PurchasedItemsTable(tables.Table): class PurchasedItemsTable(tables.Table):
...@@ -32,3 +33,73 @@ class TotalsTable(tables.Table): ...@@ -32,3 +33,73 @@ class TotalsTable(tables.Table):
class Meta: class Meta:
show_header = False show_header = False
orderable = False orderable = False
class ClientsTable(tables.Table):
class Meta:
attrs = {"class": "responsive-table highlight"}
name = tables.Column()
view = tables.LinkColumn(
"client_by_pk",
args=[A("id")],
verbose_name=_("View"),
text=_("View"),
)
edit = tables.LinkColumn(
"edit_client_by_pk",
args=[A("id")],
verbose_name=_("Edit"),
text=_("Edit"),
)
delete = tables.LinkColumn(
"delete_client_by_pk",
args=[A("id")],
verbose_name=_("Delete"),
text=_("Delete"),
)
class InvoiceGroupsTable(tables.Table):
name = tables.Column()
template_name = tables.Column()
view = tables.LinkColumn(
"invoice_group_by_pk",
args=[A("id")],
verbose_name=_("View"),
text=_("View"),
)
edit = tables.LinkColumn(
"edit_invoice_group_by_pk",
args=[A("id")],
verbose_name=_("Edit"),
text=_("Edit"),
)
delete = tables.LinkColumn(
"delete_invoice_group_by_pk",
args=[A("id")],
verbose_name=_("Delete"),
text=_("Delete"),
)
class InvoicesTable(tables.Table):
transaction_id = tables.Column()
status = tables.Column()
created = tables.DateColumn()
billing_first_name = tables.Column()
billing_last_name = tables.Column()
total = tables.Column()
view = tables.LinkColumn(
"invoice_by_pk",
args=[A("id")],
verbose_name=_("View"),
text=_("View"),
)
print = tables.LinkColumn(
"get_invoice_by_pk",
args=[A("id")],
verbose_name=_("Print"),
text=_("Print"),
)
{% extends "core/base.html" %}
{% load material_form i18n %}
{% block page_title %}{% blocktrans %}Create client{% endblocktrans %}{% endblock %}
{% block browser_title %}{% blocktrans %}Create client{% endblocktrans %}{% endblock %}
{% block extra_head %}
{{ form.media.css }}
{% endblock %}
{% block content %}
<form method="post">
{% csrf_token %}
{% form form=form %}{% endform %}
{% include "core/partials/save_button.html" %}
</form>
{{ form.media.js }}
{% endblock %}
{% extends "core/base.html" %}
{% load material_form i18n %}
{% block page_title %}{% blocktrans %}Edit client{% endblocktrans %}{% endblock %}
{% block browser_title %}{% blocktrans %}Edit client{% endblocktrans %}{% endblock %}
{% block extra_head %}
{{ form.media.css }}
{% endblock %}
{% block content %}
<form method="post">
{% csrf_token %}
{% form form=form %}{% endform %}
{% include "core/partials/save_button.html" %}
</form>
{{ form.media.js }}
{% endblock %}
{% extends "core/base.html" %}
{% load material_form i18n %}
{% load render_table from django_tables2 %}
{% block page_title %}{% blocktrans %}Client{% endblocktrans %} {{ client }}{% endblock %}
{% block browser_title %}{% blocktrans %}Client{% endblocktrans %} {{ client }}{% endblock %}
{% block content %}
<a class="btn colour-primary waves-effect waves-light" href="{% url 'create_invoice_group' client.id %}">{% trans "Add invoice group" %}</a>
{% render_table invoice_groups_table %}
{% endblock %}
{% extends "core/base.html" %}
{% load material_form i18n %}
{% load render_table from django_tables2 %}
{% block page_title %}{% blocktrans %}Clients{% endblocktrans %}{% endblock %}
{% block browser_title %}{% blocktrans %}Clients{% endblocktrans %}{% endblock %}
{% block content %}
<a class="btn colour-primary waves-effect waves-light" href="{% url 'create_client' %}">{% trans "Create client" %}</a>
{% render_table table %}
{% endblock %}
{% extends 'core/base.html' %}
{% load i18n %}
{% block content %}
<p class="flow-text">
{% blocktrans %}Tezor (account and payment system){% endblocktrans %}
</p>
{% endblock %}
{% extends "core/base.html" %}
{% load material_form i18n %}
{% load render_table from django_tables2 %}
{% block browser_title %}{{ object.transaction_id }}{% endblock %}
{% block content %}
<h1>{% trans "Invoice" %} {{ object.transaction_id }} — {{ object.created.date }}</h1>
<a class="btn colour-primary waves-effect waves-light" href="{% url 'invoice_group_by_pk' object.group.pk %}">{% trans "Back" %}</a>
{% render_table object.purchased_items_table %}
{% render_table object.totals_table %}
{% endblock %}
{% extends "core/base.html" %}
{% load material_form i18n %}
{% block page_title %}{% blocktrans %}Create invoice group{% endblocktrans %}{% endblock %}
{% block browser_title %}{% blocktrans %}Create invoice group{% endblocktrans %}{% endblock %}
{% block extra_head %}
{{ form.media.css }}
{% endblock %}
{% block content %}
<form method="post">
{% csrf_token %}
{% form form=form %}{% endform %}
{% include "core/partials/save_button.html" %}
</form>
{{ form.media.js }}
{% endblock %}
{% extends "core/base.html" %}
{% load material_form i18n %}
{% block page_title %}{% blocktrans %}Edit invoice group{% endblocktrans %}{% endblock %}
{% block browser_title %}{% blocktrans %}Edit invoice group{% endblocktrans %}{% endblock %}
{% block extra_head %}
{{ form.media.css }}
{% endblock %}
{% block content %}
<form method="post">
{% csrf_token %}
{% form form=form %}{% endform %}
{% include "core/partials/save_button.html" %}
</form>
{{ form.media.js }}
{% endblock %}
{% extends "core/base.html" %}
{% load material_form i18n %}
{% load render_table from django_tables2 %}
{% block page_title %}{{ object }}{% endblock %}
{% block browser_title %}{{ object }}{% endblock %}
{% block content %}
<a class="btn colour-primary waves-effect waves-light" href="{% url 'client_by_pk' object.client.pk %}">{% trans "Back" %}</a>
{% render_table invoices_table %}
{% endblock %}
...@@ -4,5 +4,55 @@ from . import views ...@@ -4,5 +4,55 @@ from . import views
urlpatterns = [ urlpatterns = [
path("payments/", include("payments.urls")), path("payments/", include("payments.urls")),
path("invoice/<int:pk>/print", views.GetInvoicePDF.as_view(), name="get_invoice_by_pk") path("invoice/<int:pk>/print", views.GetInvoicePDF.as_view(), name="get_invoice_by_pk"),
path(
"clients/list",
views.ClientListView.as_view(),
name="clients",
),
path(
"clients/create",
views.ClientCreateView.as_view(),
name="create_client",
),
path(
"clients/<int:pk>/edit",
views.ClientEditView.as_view(),
name="edit_client_by_pk",
),
path(
"clients/<int:pk>/delete",
views.ClientDeleteView.as_view(),
name="delete_client_by_pk",
),
path(
"clients/<int:pk>/",
views.ClientDetailView.as_view(),
name="client_by_pk",
),
path(
"client/<int:pk>/invoice_groups/create",
views.InvoiceGroupCreateView.as_view(),
name="create_invoice_group",
),
path(
"invoice_groups/<int:pk>/edit",
views.InvoiceGroupEditView.as_view(),
name="edit_invoice_group_by_pk",
),
path(
"invoice_groups/<int:pk>/",
views.InvoiceGroupDetailView.as_view(),
name="invoice_group_by_pk",
),
path(
"invoice_groups/<int:pk>/delete",
views.InvoiceGroupDeleteView.as_view(),
name="delete_invoice_group_by_pk",
),
path(
"invoice/<int:pk>/",
views.InvoiceDetailView.as_view(),
name="invoice_by_pk",
),
] ]
from django.views.generic import View from django.views.generic import View
from django.shortcuts import render from django.shortcuts import render
from django.views.decorators.cache import never_cache
from django.utils.decorators import method_decorator
from django.views.generic import FormView, TemplateView
from django.utils.translation import ugettext as _
from django.urls import reverse, reverse_lazy
from django.views.generic.detail import DetailView
from rules.contrib.views import PermissionRequiredMixin from rules.contrib.views import PermissionRequiredMixin
from django_tables2.views import SingleTableView, RequestConfig
from aleksis.core.views import RenderPDFView from aleksis.core.views import RenderPDFView
from aleksis.core.mixins import AdvancedCreateView, AdvancedDeleteView, AdvancedEditView
from .models.invoice import Invoice from .tables import ClientsTable, InvoiceGroupsTable, InvoicesTable
from .forms import EditClientForm, EditInvoiceGroupForm
from .models.base import Client
from .models.invoice import Invoice, InvoiceGroup
class GetInvoicePDF(PermissionRequiredMixin, RenderPDFView): class GetInvoicePDF(PermissionRequiredMixin, RenderPDFView):
...@@ -20,3 +31,125 @@ class GetInvoicePDF(PermissionRequiredMixin, RenderPDFView): ...@@ -20,3 +31,125 @@ class GetInvoicePDF(PermissionRequiredMixin, RenderPDFView):
print(invoice.group.__dict__) print(invoice.group.__dict__)
return context return context
class ClientListView(PermissionRequiredMixin, SingleTableView):
"""Table of all clients."""
model = Client
table_class = ClientsTable
permission_required = "tezor.view_clients"
template_name = "tezor/client/list.html"
@method_decorator(never_cache, name="dispatch")
class ClientCreateView(PermissionRequiredMixin, AdvancedCreateView):
"""Create view for clients."""
model = Client
form_class = EditClientForm
permission_required = "tezor.add_clients"
template_name = "tezor/client/create.html"
success_url = reverse_lazy("clients")
success_message = _("The client has been created.")
@method_decorator(never_cache, name="dispatch")
class ClientEditView(PermissionRequiredMixin, AdvancedEditView):
"""Edit view for clients."""
model = Client
form_class = EditClientForm
permission_required = "tezor.edit_clients"
template_name = "tezor/client/edit.html"
success_url = reverse_lazy("clients")
success_message = _("The client has been saved.")
class ClientDeleteView(PermissionRequiredMixin, AdvancedDeleteView):
"""Delete view for client."""
model = Client
permission_required = "tezor.delete_client"
template_name = "core/pages/delete.html"
success_url = reverse_lazy("clients")
success_message = _("The client has been deleted.")
class ClientDetailView(PermissionRequiredMixin, DetailView):
model = Client
permission_required = "tezor.view_client"
template_name = "tezor/client/full.html"
def get_context_data(self, object):
context = super().get_context_data()
invoice_groups = object.invoice_groups.all()
invoice_groups_table = InvoiceGroupsTable(invoice_groups)
RequestConfig(self.request).configure(invoice_groups_table)
context["invoice_groups_table"] = invoice_groups_table
return context
class InvoiceGroupDetailView(PermissionRequiredMixin, DetailView):
model = InvoiceGroup
permission_required = "tezor.view_invoice_group"
template_name = "tezor/invoice_group/full.html"
def get_context_data(self, object):
context = super().get_context_data()
invoices = object.invoices.all()
invoices_table = InvoicesTable(invoices)
RequestConfig(self.request).configure(invoices_table)
context["invoices_table"] = invoices_table
return context
@method_decorator(never_cache, name="dispatch")
class InvoiceGroupCreateView(PermissionRequiredMixin, AdvancedCreateView):
"""Create view for invoice_groups."""
model = InvoiceGroup
form_class = EditInvoiceGroupForm
permission_required = "tezor.add_invoice_groups"
template_name = "tezor/invoice_group/create.html"
success_url = reverse_lazy("clients")
success_message = _("The invoice_group has been created.")
def form_valid(self, form):
client = Client.objects.get(id=self.kwargs["pk"])
InvoiceGroup.objects.create(client=client, name=form.cleaned_data["name"], template_name=form.cleaned_data["template_name"])
return super().form_valid(form)
@method_decorator(never_cache, name="dispatch")
class InvoiceGroupEditView(PermissionRequiredMixin, AdvancedEditView):
"""Edit view for invoice_groups."""
model = InvoiceGroup
form_class = EditInvoiceGroupForm
permission_required = "tezor.edit_invoice_groups"
template_name = "tezor/invoice_group/edit.html"
success_url = reverse_lazy("invoice_groups")
success_message = _("The invoice_group has been saved.")
class InvoiceGroupDeleteView(PermissionRequiredMixin, AdvancedDeleteView):
"""Delete view for invoice_group."""
model = InvoiceGroup
permission_required = "tezor.delete_invoice_group"
template_name = "core/pages/delete.html"
success_url = reverse_lazy("invoice_groups")
success_message = _("The invoice_group has been deleted.")
class InvoiceDetailView(PermissionRequiredMixin, DetailView):
model = Invoice
permission_required = "tezor.view_invoice"
template_name = "tezor/invoice/full.html"
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment