invoicing: manage invoice cancellation reasons (#89732)
gitea/lingo/pipeline/head This commit looks good Details

This commit is contained in:
Lauréline Guérin 2024-04-19 15:04:59 +02:00
parent 167a09d37a
commit e50edafd02
No known key found for this signature in database
GPG Key ID: 1FAB9B9B4F93D473
6 changed files with 209 additions and 5 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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