invoicing: manage invoice cancellation reasons (#89732)
gitea/lingo/pipeline/head This commit looks good
Details
gitea/lingo/pipeline/head This commit looks good
Details
This commit is contained in:
parent
167a09d37a
commit
e50edafd02
|
@ -33,6 +33,7 @@ from lingo.invoicing.models import (
|
|||
DraftInvoiceLine,
|
||||
DraftJournalLine,
|
||||
Invoice,
|
||||
InvoiceCancellationReason,
|
||||
InvoiceLine,
|
||||
InvoiceLinePayment,
|
||||
JournalLine,
|
||||
|
@ -1280,6 +1281,22 @@ class PayerMappingForm(forms.ModelForm):
|
|||
return self.instance
|
||||
|
||||
|
||||
class InvoiceCancellationReasonForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = InvoiceCancellationReason
|
||||
fields = ['label', 'slug', 'disabled']
|
||||
|
||||
def clean_slug(self):
|
||||
slug = self.cleaned_data['slug']
|
||||
|
||||
if InvoiceCancellationReason.objects.filter(slug=slug).exclude(pk=self.instance.pk).exists():
|
||||
raise forms.ValidationError(
|
||||
_('Another invoice cancellation reason exists with the same identifier.')
|
||||
)
|
||||
|
||||
return slug
|
||||
|
||||
|
||||
class PaymentCancellationReasonForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = PaymentCancellationReason
|
||||
|
|
|
@ -17,11 +17,28 @@
|
|||
<div class="section">
|
||||
<div class="pk-tabs">
|
||||
<div class="pk-tabs--tab-list" role="tablist">
|
||||
<button aria-controls="panel-payment" aria-selected="true" id="tab-payment" role="tab" tabindex="0">{% trans "Payments" %}</button>
|
||||
<button aria-controls="panel-invoice" aria-selected="true" id="tab-invoice" role="tab" tabindex="0">{% trans "Invoices" %}</button>
|
||||
<button aria-controls="panel-payment" aria-selected="false" id="tab-payment" role="tab" tabindex="-1">{% trans "Payments" %}</button>
|
||||
</div>
|
||||
<div class="pk-tabs--container">
|
||||
|
||||
<div aria-labelledby="tab-payment" id="panel-payment" role="tabpanel" tabindex="0">
|
||||
<div aria-labelledby="tab-invoice" id="panel-invoice" role="tabpanel" tabindex="0">
|
||||
<ul class="objects-list single-links">
|
||||
{% for reason in invoice_reason_list %}
|
||||
<li>
|
||||
<a href="{% url 'lingo-manager-invoicing-invoice-cancellation-reason-edit' reason.pk %}">{{ reason }}{% if reason.disabled %}<span class="extra-info"> ({% trans "disabled" %})</span>{% endif %}</a>
|
||||
{% if not reason.invoice_set.exists %}
|
||||
<a class="delete" rel="popup" href="{% url 'lingo-manager-invoicing-invoice-cancellation-reason-delete' reason.pk %}">{% trans "delete"%}</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<div class="panel--buttons">
|
||||
<a class="pk-button" href="{% url 'lingo-manager-invoicing-invoice-cancellation-reason-add' %}" rel="popup">{% trans "New invoice cancellation reason" %}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div aria-labelledby="tab-payment" hidden id="panel-payment" role="tabpanel" tabindex="0">
|
||||
<ul class="objects-list single-links">
|
||||
{% for reason in payment_reason_list %}
|
||||
<li>
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
{% extends "lingo/invoicing/manager_cancellation_reason_list.html" %}
|
||||
{% load i18n gadjo %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
{% if object.pk %}
|
||||
<a href="{% url 'lingo-manager-invoicing-invoice-cancellation-reason-edit' object.pk %}">{% trans "Edit" %}</a>
|
||||
{% else %}
|
||||
<a href="{% url 'lingo-manager-invoicing-invoice-cancellation-reason-add' %}">{% trans "New invoice cancellation reason" %}</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block appbar %}
|
||||
{% if object.pk %}
|
||||
<h2>{% trans "Edit invoice cancellation reason" %} - {{ reason }}</h2>
|
||||
{% else %}
|
||||
<h2>{% trans "New invoice cancellation reason" %}</h2>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{{ form|with_template }}
|
||||
<div class="buttons">
|
||||
<button>{% trans "Submit" %}</button>
|
||||
<a class="cancel" href="{% url 'lingo-manager-invoicing-cancellation-reason-list' %}#open:invoice">{% trans 'Cancel' %}</a>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar %}
|
||||
{% endblock %}
|
|
@ -314,6 +314,21 @@ urlpatterns = [
|
|||
cancellation_reason_views.reason_list,
|
||||
name='lingo-manager-invoicing-cancellation-reason-list',
|
||||
),
|
||||
path(
|
||||
'cancellation-reason/invoice/add/',
|
||||
cancellation_reason_views.invoice_reason_add,
|
||||
name='lingo-manager-invoicing-invoice-cancellation-reason-add',
|
||||
),
|
||||
path(
|
||||
'cancellation-reason/invoice/<int:pk>/edit/',
|
||||
cancellation_reason_views.invoice_reason_edit,
|
||||
name='lingo-manager-invoicing-invoice-cancellation-reason-edit',
|
||||
),
|
||||
path(
|
||||
'cancellation-reason/invoice/<int:pk>/delete/',
|
||||
cancellation_reason_views.invoice_reason_delete,
|
||||
name='lingo-manager-invoicing-invoice-cancellation-reason-delete',
|
||||
),
|
||||
path(
|
||||
'cancellation-reason/payment/add/',
|
||||
cancellation_reason_views.payment_reason_add,
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
from django.urls import reverse
|
||||
from django.views.generic import CreateView, DeleteView, TemplateView, UpdateView
|
||||
|
||||
from lingo.invoicing.forms import PaymentCancellationReasonForm
|
||||
from lingo.invoicing.models import PaymentCancellationReason
|
||||
from lingo.invoicing.forms import InvoiceCancellationReasonForm, PaymentCancellationReasonForm
|
||||
from lingo.invoicing.models import InvoiceCancellationReason, PaymentCancellationReason
|
||||
|
||||
|
||||
class ReasonListView(TemplateView):
|
||||
|
@ -27,6 +27,7 @@ class ReasonListView(TemplateView):
|
|||
def get_context_data(self, **kwargs):
|
||||
kwargs.update(
|
||||
{
|
||||
'invoice_reason_list': InvoiceCancellationReason.objects.all().order_by('disabled', 'label'),
|
||||
'payment_reason_list': PaymentCancellationReason.objects.all().order_by('disabled', 'label'),
|
||||
}
|
||||
)
|
||||
|
@ -36,6 +37,44 @@ class ReasonListView(TemplateView):
|
|||
reason_list = ReasonListView.as_view()
|
||||
|
||||
|
||||
class InvoiceReasonAddView(CreateView):
|
||||
template_name = 'lingo/invoicing/manager_invoice_cancellation_reason_form.html'
|
||||
model = InvoiceCancellationReason
|
||||
fields = ['label']
|
||||
|
||||
def get_success_url(self):
|
||||
return '%s#open:invoice' % reverse('lingo-manager-invoicing-cancellation-reason-list')
|
||||
|
||||
|
||||
invoice_reason_add = InvoiceReasonAddView.as_view()
|
||||
|
||||
|
||||
class InvoiceReasonEditView(UpdateView):
|
||||
template_name = 'lingo/invoicing/manager_invoice_cancellation_reason_form.html'
|
||||
model = InvoiceCancellationReason
|
||||
form_class = InvoiceCancellationReasonForm
|
||||
|
||||
def get_success_url(self):
|
||||
return '%s#open:invoice' % reverse('lingo-manager-invoicing-cancellation-reason-list')
|
||||
|
||||
|
||||
invoice_reason_edit = InvoiceReasonEditView.as_view()
|
||||
|
||||
|
||||
class InvoiceReasonDeleteView(DeleteView):
|
||||
template_name = 'lingo/manager_confirm_delete.html'
|
||||
model = InvoiceCancellationReason
|
||||
|
||||
def get_queryset(self):
|
||||
return InvoiceCancellationReason.objects.filter(invoice__isnull=True)
|
||||
|
||||
def get_success_url(self):
|
||||
return '%s#open:invoice' % reverse('lingo-manager-invoicing-cancellation-reason-list')
|
||||
|
||||
|
||||
invoice_reason_delete = InvoiceReasonDeleteView.as_view()
|
||||
|
||||
|
||||
class PaymentReasonAddView(CreateView):
|
||||
template_name = 'lingo/invoicing/manager_payment_cancellation_reason_form.html'
|
||||
model = PaymentCancellationReason
|
||||
|
|
|
@ -1,11 +1,93 @@
|
|||
import datetime
|
||||
|
||||
import pytest
|
||||
|
||||
from lingo.invoicing.models import Payment, PaymentCancellationReason, PaymentType, Regie
|
||||
from lingo.invoicing.models import (
|
||||
Invoice,
|
||||
InvoiceCancellationReason,
|
||||
Payment,
|
||||
PaymentCancellationReason,
|
||||
PaymentType,
|
||||
Regie,
|
||||
)
|
||||
from tests.utils import login
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
def test_add_invoice_reason(app, admin_user):
|
||||
app = login(app)
|
||||
resp = app.get('/manage/invoicing/regies/')
|
||||
resp = resp.click('Cancellation reasons')
|
||||
resp = resp.click('New invoice cancellation reason')
|
||||
resp.form['label'] = 'Foo bar'
|
||||
assert 'slug' not in resp.context['form'].fields
|
||||
assert 'disabled' not in resp.context['form'].fields
|
||||
resp = resp.form.submit()
|
||||
invoice_reason = InvoiceCancellationReason.objects.latest('pk')
|
||||
assert resp.location.endswith('/manage/invoicing/cancellation-reasons/#open:invoice')
|
||||
assert invoice_reason.label == 'Foo bar'
|
||||
assert invoice_reason.slug == 'foo-bar'
|
||||
assert invoice_reason.disabled is False
|
||||
|
||||
resp = app.get('/manage/invoicing/cancellation-reason/invoice/add/')
|
||||
resp.form['label'] = 'Foo bar'
|
||||
resp = resp.form.submit()
|
||||
invoice_reason = InvoiceCancellationReason.objects.latest('pk')
|
||||
assert resp.location.endswith('/manage/invoicing/cancellation-reasons/#open:invoice')
|
||||
assert invoice_reason.label == 'Foo bar'
|
||||
assert invoice_reason.slug == 'foo-bar-1'
|
||||
assert invoice_reason.disabled is False
|
||||
|
||||
|
||||
def test_edit_invoice_reason(app, admin_user):
|
||||
invoice_reason = InvoiceCancellationReason.objects.create(label='Foo')
|
||||
invoice_reason2 = InvoiceCancellationReason.objects.create(label='Baz')
|
||||
|
||||
app = login(app)
|
||||
resp = app.get('/manage/invoicing/cancellation-reasons/')
|
||||
resp = resp.click(href='/manage/invoicing/cancellation-reason/invoice/%s/edit/' % (invoice_reason.pk))
|
||||
resp.form['label'] = 'Foo bar'
|
||||
resp.form['slug'] = invoice_reason2.slug
|
||||
resp.form['disabled'] = True
|
||||
resp = resp.form.submit()
|
||||
assert resp.context['form'].errors['slug'] == [
|
||||
'Another invoice cancellation reason exists with the same identifier.'
|
||||
]
|
||||
|
||||
resp.form['slug'] = 'foo-bar'
|
||||
resp = resp.form.submit()
|
||||
assert resp.location.endswith('/manage/invoicing/cancellation-reasons/#open:invoice')
|
||||
invoice_reason.refresh_from_db()
|
||||
assert invoice_reason.label == 'Foo bar'
|
||||
assert invoice_reason.slug == 'foo-bar'
|
||||
assert invoice_reason.disabled is True
|
||||
|
||||
|
||||
def test_delete_invoice_reason(app, admin_user):
|
||||
regie = Regie.objects.create(label='Foo')
|
||||
invoice_reason = InvoiceCancellationReason.objects.create(label='Foo')
|
||||
invoice = Invoice.objects.create(
|
||||
regie=regie,
|
||||
date_publication=datetime.date(2023, 4, 21),
|
||||
date_payment_deadline=datetime.date(2023, 4, 22),
|
||||
date_due=datetime.date(2023, 4, 23),
|
||||
cancellation_reason=invoice_reason,
|
||||
)
|
||||
|
||||
app = login(app)
|
||||
resp = app.get('/manage/invoicing/cancellation-reasons/')
|
||||
assert '/manage/invoicing/cancellation-reason/invoice/%s/delete/' % invoice_reason.pk not in resp
|
||||
app.get('/manage/invoicing/cancellation-reason/invoice/%s/delete/' % invoice_reason.pk, status=404)
|
||||
|
||||
invoice.delete()
|
||||
|
||||
resp = app.get('/manage/invoicing/cancellation-reasons/')
|
||||
resp = resp.click(href='/manage/invoicing/cancellation-reason/invoice/%s/delete/' % invoice_reason.pk)
|
||||
resp = resp.form.submit()
|
||||
assert resp.location.endswith('/manage/invoicing/cancellation-reasons/#open:invoice')
|
||||
|
||||
|
||||
def test_add_payment_reason(app, admin_user):
|
||||
app = login(app)
|
||||
resp = app.get('/manage/invoicing/regies/')
|
||||
|
@ -77,3 +159,4 @@ def test_delete_payment_reason(app, admin_user):
|
|||
resp = resp.form.submit()
|
||||
assert resp.location.endswith('/manage/invoicing/cancellation-reasons/#open:payment')
|
||||
assert PaymentCancellationReason.objects.exists() is False
|
||||
assert PaymentCancellationReason.objects.exists() is False
|
||||
|
|
Loading…
Reference in New Issue