Compare commits

...

3 Commits

Author SHA1 Message Date
Lauréline Guérin 77499b9dd6
invoicing: cancel invoice from UI (#88590)
gitea/lingo/pipeline/head There was a failure building this commit Details
2024-04-25 11:20:30 +02:00
Lauréline Guérin 6b4c0976ae
invoicing: add cancel callback on invoice (#88590) 2024-04-23 16:18:10 +02:00
Lauréline Guérin 85151eafc7
misc: split tests (#88590) 2024-04-23 16:03:31 +02:00
16 changed files with 4465 additions and 1918 deletions

View File

@ -766,6 +766,24 @@ class RegieInvoiceFilterSet(AgendaFieldsFilterSetMixin, django_filters.FilterSet
return queryset.filter(pk__in=lines)
class RegieInvoiceCancelForm(forms.ModelForm):
class Meta:
model = Invoice
fields = ['cancellation_reason', 'cancellation_description']
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request')
super().__init__(*args, **kwargs)
self.fields['cancellation_reason'].queryset = InvoiceCancellationReason.objects.filter(disabled=False)
def save(self):
super().save(commit=False)
self.instance.cancelled_at = now()
self.instance.cancelled_by = self.request.user
self.instance.save()
return self.instance
class RegiePaymentFilterSet(AgendaFieldsFilterSetMixin, django_filters.FilterSet):
number = django_filters.CharFilter(
label=_('Payment number'),

View File

@ -0,0 +1,19 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('invoicing', '0089_credit_cancellation'),
]
operations = [
migrations.RemoveField(
model_name='draftinvoice',
name='payment_callback_url',
),
migrations.AddField(
model_name='invoice',
name='cancel_callback_url',
field=models.URLField(blank=True),
),
]

View File

@ -922,8 +922,6 @@ class AbstractInvoice(AbstractInvoiceObject):
payer_demat = models.BooleanField(default=False)
payer_direct_debit = models.BooleanField(default=False)
payment_callback_url = models.URLField(blank=True)
class Meta:
abstract = True
@ -1092,6 +1090,9 @@ class Invoice(AbstractInvoice):
)
cancellation_description = models.TextField(_('Description'), blank=True)
payment_callback_url = models.URLField(blank=True)
cancel_callback_url = models.URLField(blank=True)
def set_number(self):
self.number = Counter.get_count(
regie=self.regie,

View File

@ -0,0 +1,25 @@
{% extends "lingo/invoicing/manager_invoice_list.html" %}
{% load i18n gadjo %}
{% block breadcrumb %}
{{ block.super }}
<a href="{% url 'lingo-manager-invoicing-regie-invoice-cancel' regie.pk object.pk %}">{% trans "Cancel invoice" %}</a>
{% endblock %}
{% block appbar %}
<h2>{% trans "Cancel invoice" %} - {{ object.formatted_number }}</h2>
{% 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-regie-invoice-list' regie.pk %}">{% trans 'Cancel' %}</a>
</div>
</form>
{% endblock %}
{% block sidebar %}
{% endblock %}

View File

@ -187,12 +187,20 @@
</tr>
{% endif %}
{% if invoice.remaining_amount %}
<tr class="line last-line" data-related-invoicing-element-id="{{ invoice.pk }}">
<tr class="line {% if invoice.paid_amount %}last-line{% endif %}" data-related-invoicing-element-id="{{ invoice.pk }}">
<td colspan="2"></td>
<td class="invoice-details" colspan="5">
<i>{% trans "Remaining amount:" %} {% blocktrans with amount=invoice.remaining_amount|floatformat:"2" %}{{ amount }}€{% endblocktrans %}</i>
</td>
</tr>
{% if not invoice.paid_amount %}
<tr class="line last-line" data-related-invoicing-element-id="{{ invoice.pk }}">
<td colspan="2"></td>
<td class="invoice-details" colspan="5">
<a href="{% url 'lingo-manager-invoicing-regie-invoice-cancel' regie_pk=regie.pk invoice_pk=invoice.pk %}" rel="popup">{% trans "Cancel invoice" %}</a>
</td>
</tr>
{% endif %}
{% else %}
<tr class="line last-line" data-related-invoicing-element-id="{{ invoice.pk }}">
<td colspan="2"></td>

View File

@ -193,6 +193,11 @@ urlpatterns = [
regie_views.regie_invoice_line_list,
name='lingo-manager-invoicing-regie-invoice-line-list',
),
path(
'regie/<int:regie_pk>/invoice/<int:invoice_pk>/cancel/',
regie_views.regie_invoice_cancel,
name='lingo-manager-invoicing-regie-invoice-cancel',
),
path(
'regie/<int:regie_pk>/payments/',
regie_views.regie_payment_list,

View File

@ -50,6 +50,7 @@ from lingo.invoicing.forms import (
RegieCreditFilterSet,
RegieDocketPaymentFilterSet,
RegieForm,
RegieInvoiceCancelForm,
RegieInvoiceFilterSet,
RegiePaymentCancelForm,
RegiePaymentFilterSet,
@ -603,6 +604,46 @@ class RegieInvoiceLineListView(ListView):
regie_invoice_line_list = RegieInvoiceLineListView.as_view()
class RegieInvoiceCancelView(UpdateView):
template_name = 'lingo/invoicing/manager_invoice_cancel_form.html'
pk_url_kwarg = 'invoice_pk'
model = Invoice
form_class = RegieInvoiceCancelForm
def dispatch(self, request, *args, **kwargs):
self.regie = get_object_or_404(Regie, pk=kwargs['regie_pk'])
return super().dispatch(request, *args, **kwargs)
def get_queryset(self):
return (
super()
.get_queryset()
.filter(
regie=self.regie,
cancelled_at__isnull=True,
)
.exclude(pk__in=InvoiceLine.objects.filter(invoicelinepayment__isnull=False).values('invoice'))
)
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['request'] = self.request
return kwargs
def get_context_data(self, **kwargs):
kwargs['regie'] = self.regie
return super().get_context_data(**kwargs)
def get_success_url(self):
return '%s?number=%s' % (
reverse('lingo-manager-invoicing-regie-invoice-list', args=[self.regie.pk]),
self.object.formatted_number,
)
regie_invoice_cancel = RegieInvoiceCancelView.as_view()
class RegiePaymentListView(ListView):
template_name = 'lingo/invoicing/manager_payment_list.html'
paginate_by = 100

View File

View File

@ -0,0 +1,103 @@
import datetime
import pytest
from lingo.invoicing.models import InjectedLine, PaymentType, Regie
pytestmark = pytest.mark.django_db
def test_regies_empty(app, user):
app.authorization = ('Basic', ('john.doe', 'password'))
resp = app.get('/api/regies/')
data = resp.json
assert data['data'] == []
def test_regies(app, user):
app.authorization = ('Basic', ('john.doe', 'password'))
Regie.objects.create(label='Bar')
Regie.objects.create(label='Foo')
resp = app.get('/api/regies/')
data = resp.json
assert data['data'] == [
{'id': 'bar', 'text': 'Bar', 'slug': 'bar'},
{'id': 'foo', 'text': 'Foo', 'slug': 'foo'},
]
def test_payment_types(app, user):
regie = Regie.objects.create(label='Bar')
PaymentType.create_defaults(regie)
PaymentType.objects.create(regie=regie, label='Foo')
PaymentType.objects.create(regie=regie, label='Disabled', disabled=True)
app.get('/api/regie/%s/payment-types/' % regie.slug, status=403)
app.authorization = ('Basic', ('john.doe', 'password'))
resp = app.get('/api/regie/%s/payment-types/' % regie.slug)
data = resp.json
assert data['data'] == [
{'id': 'cash', 'text': 'Cash', 'slug': 'cash'},
{'id': 'cesu', 'text': 'CESU', 'slug': 'cesu'},
{'id': 'check', 'text': 'Check', 'slug': 'check'},
{'id': 'creditcard', 'text': 'Credit card', 'slug': 'creditcard'},
{'id': 'directdebit', 'text': 'Direct debit', 'slug': 'directdebit'},
{'id': 'foo', 'text': 'Foo', 'slug': 'foo'},
{'id': 'holidaycheck', 'text': 'Holiday check', 'slug': 'holidaycheck'},
{'id': 'online', 'text': 'Online', 'slug': 'online'},
]
def test_add_injected_line(app, user):
app.post('/api/regie/foo/injected-lines/', status=403)
app.authorization = ('Basic', ('john.doe', 'password'))
app.post('/api/regie/foo/injected-lines/', status=404)
regie = Regie.objects.create(slug='foo')
resp = app.post('/api/regie/foo/injected-lines/', status=400)
assert resp.json['err']
assert resp.json['errors'] == {
'event_date': ['This field is required.'],
'slug': ['This field is required.'],
'label': ['This field is required.'],
'amount': ['This field is required.'],
'user_external_id': ['This field is required.'],
'payer_external_id': ['This field is required.'],
}
params = {
'event_date': '2023-01-17',
'slug': 'foobar',
'label': 'Foo Bar',
'amount': 2,
'user_external_id': 'user:1',
'payer_external_id': 'payer:1',
}
resp = app.post('/api/regie/foo/injected-lines/', params=params)
assert resp.json['err'] == 0
injected_line = InjectedLine.objects.get(pk=resp.json['id'])
assert injected_line.event_date == datetime.date(2023, 1, 17)
assert injected_line.slug == 'foobar'
assert injected_line.label == 'Foo Bar'
assert injected_line.amount == 2
assert injected_line.user_external_id == 'user:1'
assert injected_line.payer_external_id == 'payer:1'
assert injected_line.regie == regie
params.update(
{
'amount': -70,
}
)
resp = app.post('/api/regie/foo/injected-lines/', params=params)
assert resp.json['err'] == 0
injected_line = InjectedLine.objects.get(pk=resp.json['id'])
assert injected_line.event_date == datetime.date(2023, 1, 17)
assert injected_line.slug == 'foobar'
assert injected_line.label == 'Foo Bar'
assert injected_line.amount == -70
assert injected_line.user_external_id == 'user:1'
assert injected_line.payer_external_id == 'payer:1'
assert injected_line.regie == regie

View File

@ -0,0 +1,808 @@
import datetime
import uuid
from unittest import mock
import pytest
from django.utils.timezone import now
from lingo.invoicing.models import (
Campaign,
Credit,
CreditAssignment,
CreditLine,
Invoice,
PayerError,
Payment,
PaymentType,
Pool,
Regie,
)
pytestmark = pytest.mark.django_db
@mock.patch.object(Regie, 'get_payer_external_id_from_nameid', autospec=True)
def test_list_credits(mock_payer, app, user):
app.get('/api/regie/foo/credits/', status=403)
app.authorization = ('Basic', ('john.doe', 'password'))
app.get('/api/regie/foo/credits/', status=404)
regie = Regie.objects.create(label='Foo')
PaymentType.create_defaults(regie)
app.get('/api/regie/foo/credits/', status=404)
invoice = Invoice.objects.create(
label='My invoice',
date_publication=datetime.date(2022, 10, 1),
date_payment_deadline=datetime.date.today(),
date_due=datetime.date(2022, 10, 31),
regie=regie,
payer_external_id='payer:1',
)
payment = Payment.objects.create(
regie=regie,
amount=42,
payment_type=PaymentType.objects.get(regie=regie, slug='credit'),
payer_external_id='payer:1',
)
payment.set_number()
payment.save()
credit = Credit.objects.create(
label='Credit from 01/09/2022',
date_publication=datetime.date(2022, 10, 1),
regie=regie,
payer_external_id='payer:1',
payer_first_name='First1',
payer_last_name='Name1',
payer_address='41 rue des kangourous\n99999 Kangourou Ville',
)
credit.set_number()
credit.save()
CreditLine.objects.create(
event_date=datetime.date(2022, 9, 1),
credit=credit,
quantity=42,
unit_amount=1,
label='Label 11',
user_external_id='user:1',
user_first_name='User1',
user_last_name='Name1',
)
CreditAssignment.objects.create(
invoice=invoice,
payment=payment,
credit=credit,
amount=1,
)
credit.refresh_from_db()
mock_payer.return_value = 'payer:1'
resp = app.get('/api/regie/foo/credits/', params={'NameID': 'foobar'})
assert resp.json['err'] == 0
assert resp.json['data'] == [
{
'id': str(credit.uuid),
'display_id': 'A%02d-%s-0000001'
% (
regie.pk,
credit.created_at.strftime('%y-%m'),
),
'label': 'A%02d-%s-0000001 - Credit from 01/09/2022 (credit left: 41.00€)'
% (
regie.pk,
credit.created_at.strftime('%y-%m'),
),
'total_amount': 42,
'remaining_amount': 41,
'created': datetime.date.today().isoformat(),
'has_pdf': True,
}
]
assert mock_payer.call_args_list == [mock.call(regie, mock.ANY, 'foobar')]
# publication date is in the future
credit.date_publication = datetime.date.today() + datetime.timedelta(days=1)
credit.save()
resp = app.get('/api/regie/foo/credits/', params={'NameID': 'foobar'})
assert resp.json['err'] == 0
assert resp.json['data'] == []
# credit is cancelled
credit.date_publication = datetime.date(2022, 10, 1)
credit.cancelled_at = now()
credit.save()
resp = app.get('/api/regie/foo/credits/', params={'NameID': 'foobar'})
assert resp.json['err'] == 0
assert resp.json['data'] == []
# other regie
other_regie = Regie.objects.create(label='Other Foo')
credit.cancelled_at = None
credit.regie = other_regie
credit.save()
resp = app.get('/api/regie/foo/credits/', params={'NameID': 'foobar'})
assert resp.json['err'] == 0
assert resp.json['data'] == []
# campaign is not finalized
campaign = Campaign.objects.create(
regie=regie,
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
date_publication=datetime.date(2022, 10, 1),
date_payment_deadline=datetime.date(2022, 10, 31),
date_due=datetime.date(2022, 10, 31),
date_debit=datetime.date(2022, 11, 15),
finalized=False,
)
pool = Pool.objects.create(
campaign=campaign,
draft=False,
)
credit.regie = regie
credit.pool = pool
credit.save()
resp = app.get('/api/regie/foo/credits/', params={'NameID': 'foobar'})
assert resp.json['err'] == 0
assert resp.json['data'] == []
# campaign is finalized
campaign.finalized = True
campaign.save()
resp = app.get('/api/regie/foo/credits/', params={'NameID': 'foobar'})
assert resp.json['err'] == 0
assert len(resp.json['data']) == 1
# no matching payer id
mock_payer.return_value = 'payer:unknown'
resp = app.get('/api/regie/foo/credits/', params={'NameID': 'foobar'})
assert resp.json['err'] == 0
assert resp.json['data'] == []
# payer error
mock_payer.side_effect = PayerError
app.get('/api/regie/foo/credits/', params={'NameID': 'foobar'}, status=404)
# credit fully assigned
mock_payer.return_value = 'payer:1'
mock_payer.side_effect = None
CreditAssignment.objects.create(
invoice=invoice,
payment=payment,
credit=credit,
amount=41,
)
resp = app.get('/api/regie/foo/credits/', params={'NameID': 'foobar'})
assert resp.json['err'] == 0
assert resp.json['data'] == []
def test_list_credits_for_payer(app, user):
app.get('/api/regie/foo/credits/', status=403)
app.authorization = ('Basic', ('john.doe', 'password'))
app.get('/api/regie/foo/credits/', status=404)
regie = Regie.objects.create(label='Foo')
PaymentType.create_defaults(regie)
app.get('/api/regie/foo/credits/', status=404)
resp = app.get('/api/regie/foo/credits/', params={'payer_external_id': 'payer:1'})
assert resp.json['err'] == 0
assert resp.json['data'] == []
invoice = Invoice.objects.create(
label='My invoice',
date_publication=datetime.date(2022, 10, 1),
date_payment_deadline=datetime.date.today(),
date_due=datetime.date(2022, 10, 31),
regie=regie,
payer_external_id='payer:1',
)
payment = Payment.objects.create(
regie=regie,
amount=42,
payment_type=PaymentType.objects.get(regie=regie, slug='cash'),
payer_external_id='payer:1',
)
payment.set_number()
payment.save()
credit = Credit.objects.create(
label='Credit from 01/09/2022',
date_publication=datetime.date(2022, 10, 1),
regie=regie,
payer_external_id='payer:1',
payer_first_name='First1',
payer_last_name='Name1',
payer_address='41 rue des kangourous\n99999 Kangourou Ville',
)
credit.set_number()
credit.save()
CreditLine.objects.create(
event_date=datetime.date(2022, 9, 1),
credit=credit,
quantity=42,
unit_amount=1,
label='Label 11',
user_external_id='user:1',
user_first_name='User1',
user_last_name='Name1',
)
CreditAssignment.objects.create(
invoice=invoice,
payment=payment,
credit=credit,
amount=1,
)
credit.refresh_from_db()
resp = app.get('/api/regie/foo/credits/', params={'payer_external_id': 'payer:1'})
assert resp.json['err'] == 0
assert resp.json['data'] == [
{
'id': str(credit.uuid),
'display_id': 'A%02d-%s-0000001'
% (
regie.pk,
credit.created_at.strftime('%y-%m'),
),
'label': 'A%02d-%s-0000001 - Credit from 01/09/2022 (credit left: 41.00€)'
% (
regie.pk,
credit.created_at.strftime('%y-%m'),
),
'total_amount': 42,
'remaining_amount': 41,
'created': datetime.date.today().isoformat(),
'has_pdf': True,
}
]
# publication date is in the future
credit.date_publication = datetime.date.today() + datetime.timedelta(days=1)
credit.save()
resp = app.get('/api/regie/foo/credits/', params={'payer_external_id': 'payer:1'})
assert resp.json['err'] == 0
assert resp.json['data'] == []
# credit is cancelled
credit.date_publication = datetime.date(2022, 10, 1)
credit.cancelled_at = now()
credit.save()
resp = app.get('/api/regie/foo/credits/', params={'payer_external_id': 'payer:1'})
assert resp.json['err'] == 0
assert resp.json['data'] == []
# other regie
other_regie = Regie.objects.create(label='Other Foo')
credit.cancelled_at = None
credit.regie = other_regie
credit.save()
resp = app.get('/api/regie/foo/credits/', params={'payer_external_id': 'payer:1'})
assert resp.json['err'] == 0
assert resp.json['data'] == []
# campaign is not finalized
campaign = Campaign.objects.create(
regie=regie,
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
date_publication=datetime.date(2022, 10, 1),
date_payment_deadline=datetime.date(2022, 10, 31),
date_due=datetime.date(2022, 10, 31),
date_debit=datetime.date(2022, 11, 15),
finalized=False,
)
pool = Pool.objects.create(
campaign=campaign,
draft=False,
)
credit.regie = regie
credit.pool = pool
credit.save()
resp = app.get('/api/regie/foo/credits/', params={'payer_external_id': 'payer:1'})
assert resp.json['err'] == 0
assert resp.json['data'] == []
# campaign is finalized
campaign.finalized = True
campaign.save()
resp = app.get('/api/regie/foo/credits/', params={'payer_external_id': 'payer:1'})
assert resp.json['err'] == 0
assert len(resp.json['data']) == 1
# credit fully assigned
CreditAssignment.objects.create(
invoice=invoice,
payment=payment,
credit=credit,
amount=41,
)
resp = app.get('/api/regie/foo/credits/', params={'payer_external_id': 'payer:1'})
assert resp.json['err'] == 0
assert resp.json['data'] == []
@mock.patch.object(Regie, 'get_payer_external_id_from_nameid', autospec=True)
def test_list_history_credits(mock_payer, app, user):
app.get('/api/regie/foo/credits/history/', status=403)
app.authorization = ('Basic', ('john.doe', 'password'))
app.get('/api/regie/foo/credits/history/', status=404)
regie = Regie.objects.create(label='Foo')
PaymentType.create_defaults(regie)
app.get('/api/regie/foo/credits/history/', status=404)
invoice = Invoice.objects.create(
label='My invoice',
date_publication=datetime.date(2022, 10, 1),
date_payment_deadline=datetime.date.today(),
date_due=datetime.date(2022, 10, 31),
regie=regie,
payer_external_id='payer:1',
)
payment = Payment.objects.create(
regie=regie,
amount=42,
payment_type=PaymentType.objects.get(regie=regie, slug='credit'),
payer_external_id='payer:1',
)
payment.set_number()
payment.save()
credit = Credit.objects.create(
label='Credit from 01/09/2022',
date_publication=datetime.date(2022, 10, 1),
regie=regie,
payer_external_id='payer:1',
payer_first_name='First1',
payer_last_name='Name1',
payer_address='41 rue des kangourous\n99999 Kangourou Ville',
)
credit.set_number()
credit.save()
CreditLine.objects.create(
event_date=datetime.date(2022, 9, 1),
credit=credit,
quantity=42,
unit_amount=1,
label='Label 11',
user_external_id='user:1',
user_first_name='User1',
user_last_name='Name1',
)
CreditAssignment.objects.create(
invoice=invoice,
payment=payment,
credit=credit,
amount=1,
)
credit.refresh_from_db()
mock_payer.return_value = 'payer:1'
resp = app.get('/api/regie/foo/credits/history/', params={'NameID': 'foobar'})
assert resp.json['err'] == 0
assert resp.json['data'] == []
# credit fully assigned
mock_payer.reset_mock()
CreditAssignment.objects.create(
invoice=invoice,
payment=payment,
credit=credit,
amount=41,
)
credit.refresh_from_db()
resp = app.get('/api/regie/foo/credits/history/', params={'NameID': 'foobar'})
assert resp.json['err'] == 0
assert resp.json['data'] == [
{
'id': str(credit.uuid),
'display_id': 'A%02d-%s-0000001'
% (
regie.pk,
credit.created_at.strftime('%y-%m'),
),
'label': 'A%02d-%s-0000001 - Credit from 01/09/2022'
% (
regie.pk,
credit.created_at.strftime('%y-%m'),
),
'total_amount': 42,
'remaining_amount': 0,
'created': datetime.date.today().isoformat(),
'has_pdf': True,
}
]
assert mock_payer.call_args_list == [mock.call(regie, mock.ANY, 'foobar')]
# publication date is in the future
credit.date_publication = datetime.date.today() + datetime.timedelta(days=1)
credit.save()
resp = app.get('/api/regie/foo/credits/history/', params={'NameID': 'foobar'})
assert resp.json['err'] == 0
assert resp.json['data'] == []
# credit is cancelled
credit.date_publication = datetime.date(2022, 10, 1)
credit.cancelled_at = now()
credit.save()
resp = app.get('/api/regie/foo/credits/history/', params={'NameID': 'foobar'})
assert resp.json['err'] == 0
assert resp.json['data'] == []
# other regie
other_regie = Regie.objects.create(label='Other Foo')
credit.cancelled_at = None
credit.regie = other_regie
credit.save()
resp = app.get('/api/regie/foo/credits/history/', params={'NameID': 'foobar'})
assert resp.json['err'] == 0
assert resp.json['data'] == []
# campaign is not finalized
campaign = Campaign.objects.create(
regie=regie,
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
date_publication=datetime.date(2022, 10, 1),
date_payment_deadline=datetime.date(2022, 10, 31),
date_due=datetime.date(2022, 10, 31),
date_debit=datetime.date(2022, 11, 15),
finalized=False,
)
pool = Pool.objects.create(
campaign=campaign,
draft=False,
)
credit.regie = regie
credit.pool = pool
credit.save()
resp = app.get('/api/regie/foo/credits/history/', params={'NameID': 'foobar'})
assert resp.json['err'] == 0
assert resp.json['data'] == []
# campaign is finalized
campaign.finalized = True
campaign.save()
resp = app.get('/api/regie/foo/credits/history/', params={'NameID': 'foobar'})
assert resp.json['err'] == 0
assert len(resp.json['data']) == 1
# no matching payer id
mock_payer.return_value = 'payer:unknown'
resp = app.get('/api/regie/foo/credits/history/', params={'NameID': 'foobar'})
assert resp.json['err'] == 0
assert resp.json['data'] == []
# payer error
mock_payer.side_effect = PayerError
app.get('/api/regie/foo/credits/history/', params={'NameID': 'foobar'}, status=404)
def test_list_history_credits_for_payer(app, user):
app.get('/api/regie/foo/credits/history/', status=403)
app.authorization = ('Basic', ('john.doe', 'password'))
app.get('/api/regie/foo/credits/history/', status=404)
regie = Regie.objects.create(label='Foo')
PaymentType.create_defaults(regie)
app.get('/api/regie/foo/credits/history/', status=404)
resp = app.get('/api/regie/foo/credits/history/', params={'payer_external_id': 'payer:1'})
assert resp.json['err'] == 0
assert resp.json['data'] == []
invoice = Invoice.objects.create(
label='My invoice',
date_publication=datetime.date(2022, 10, 1),
date_payment_deadline=datetime.date.today(),
date_due=datetime.date(2022, 10, 31),
regie=regie,
payer_external_id='payer:1',
)
payment = Payment.objects.create(
regie=regie,
amount=42,
payment_type=PaymentType.objects.get(regie=regie, slug='cash'),
payer_external_id='payer:1',
)
payment.set_number()
payment.save()
credit = Credit.objects.create(
label='Credit from 01/09/2022',
date_publication=datetime.date(2022, 10, 1),
regie=regie,
payer_external_id='payer:1',
payer_first_name='First1',
payer_last_name='Name1',
payer_address='41 rue des kangourous\n99999 Kangourou Ville',
)
credit.set_number()
credit.save()
CreditLine.objects.create(
event_date=datetime.date(2022, 9, 1),
credit=credit,
quantity=42,
unit_amount=1,
label='Label 11',
user_external_id='user:1',
user_first_name='User1',
user_last_name='Name1',
)
CreditAssignment.objects.create(
invoice=invoice,
payment=payment,
credit=credit,
amount=1,
)
credit.refresh_from_db()
resp = app.get('/api/regie/foo/credits/history/', params={'payer_external_id': 'payer:1'})
assert resp.json['err'] == 0
assert resp.json['data'] == []
# credit fully assigned
CreditAssignment.objects.create(
invoice=invoice,
payment=payment,
credit=credit,
amount=41,
)
credit.refresh_from_db()
resp = app.get('/api/regie/foo/credits/history/', params={'payer_external_id': 'payer:1'})
assert resp.json['err'] == 0
assert resp.json['data'] == [
{
'id': str(credit.uuid),
'display_id': 'A%02d-%s-0000001'
% (
regie.pk,
credit.created_at.strftime('%y-%m'),
),
'label': 'A%02d-%s-0000001 - Credit from 01/09/2022'
% (
regie.pk,
credit.created_at.strftime('%y-%m'),
),
'total_amount': 42,
'remaining_amount': 0,
'created': datetime.date.today().isoformat(),
'has_pdf': True,
}
]
# publication date is in the future
credit.date_publication = datetime.date.today() + datetime.timedelta(days=1)
credit.save()
resp = app.get('/api/regie/foo/credits/history/', params={'payer_external_id': 'payer:1'})
assert resp.json['err'] == 0
assert resp.json['data'] == []
# credit is cancelled
credit.date_publication = datetime.date(2022, 10, 1)
credit.cancelled_at = now()
credit.save()
resp = app.get('/api/regie/foo/credits/history/', params={'payer_external_id': 'payer:1'})
assert resp.json['err'] == 0
assert resp.json['data'] == []
# other regie
other_regie = Regie.objects.create(label='Other Foo')
credit.cancelled_at = None
credit.regie = other_regie
credit.save()
resp = app.get('/api/regie/foo/credits/history/', params={'payer_external_id': 'payer:1'})
assert resp.json['err'] == 0
assert resp.json['data'] == []
# campaign is not finalized
campaign = Campaign.objects.create(
regie=regie,
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
date_publication=datetime.date(2022, 10, 1),
date_payment_deadline=datetime.date(2022, 10, 31),
date_due=datetime.date(2022, 10, 31),
date_debit=datetime.date(2022, 11, 15),
finalized=False,
)
pool = Pool.objects.create(
campaign=campaign,
draft=False,
)
credit.regie = regie
credit.pool = pool
credit.save()
resp = app.get('/api/regie/foo/credits/history/', params={'payer_external_id': 'payer:1'})
assert resp.json['err'] == 0
assert resp.json['data'] == []
# campaign is finalized
campaign.finalized = True
campaign.save()
resp = app.get('/api/regie/foo/credits/history/', params={'payer_external_id': 'payer:1'})
assert resp.json['err'] == 0
assert len(resp.json['data']) == 1
@mock.patch.object(Regie, 'get_payer_external_id_from_nameid', autospec=True)
def test_pdf_credit(mock_payer, app, user):
app.get('/api/regie/foo/credit/%s/pdf/' % str(uuid.uuid4()), status=403)
app.authorization = ('Basic', ('john.doe', 'password'))
app.get('/api/regie/foo/credit/%s/pdf/' % str(uuid.uuid4()), status=404)
regie = Regie.objects.create(label='Foo')
PaymentType.create_defaults(regie)
app.get('/api/regie/foo/credit/%s/pdf/' % str(uuid.uuid4()), status=404)
app.get('/api/regie/foo/credit/%s/pdf/' % str(uuid.uuid4()), params={'NameID': 'foobar'}, status=404)
credit = Credit.objects.create(
label='Credit from 01/09/2022',
date_publication=datetime.date(2022, 10, 1),
regie=regie,
payer_external_id='payer:1',
payer_first_name='First1',
payer_last_name='Name1',
payer_address='41 rue des kangourous\n99999 Kangourou Ville',
)
mock_payer.return_value = 'payer:1'
resp = app.get('/api/regie/foo/credit/%s/pdf/' % str(credit.uuid), params={'NameID': 'foobar'})
assert resp.headers['Content-Disposition'] == 'attachment; filename="%s.pdf"' % credit.formatted_number
# publication date is in the future
credit.date_publication = datetime.date.today() + datetime.timedelta(days=1)
credit.save()
app.get('/api/regie/foo/credit/%s/pdf/' % str(credit.uuid), params={'NameID': 'foobar'}, status=404)
# credit is cancelled
credit.date_publication = datetime.date(2022, 10, 1)
credit.cancelled_at = now()
credit.save()
app.get('/api/regie/foo/credit/%s/pdf/' % str(credit.uuid), params={'NameID': 'foobar'}, status=404)
# other regie
other_regie = Regie.objects.create(label='Other Foo')
credit.cancelled_at = None
credit.regie = other_regie
credit.save()
app.get('/api/regie/foo/credit/%s/pdf/' % str(credit.uuid), params={'NameID': 'foobar'}, status=404)
# campaign is not finalized
campaign = Campaign.objects.create(
regie=regie,
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
date_publication=datetime.date(2022, 10, 1),
date_payment_deadline=datetime.date(2022, 10, 31),
date_due=datetime.date(2022, 10, 31),
date_debit=datetime.date(2022, 11, 15),
finalized=False,
)
pool = Pool.objects.create(
campaign=campaign,
draft=False,
)
credit.regie = regie
credit.pool = pool
credit.save()
app.get('/api/regie/foo/credit/%s/pdf/' % str(credit.uuid), params={'NameID': 'foobar'}, status=404)
# campaign is finalized
campaign.finalized = True
campaign.save()
app.get(
'/api/regie/foo/credit/%s/pdf/' % str(credit.uuid),
params={'NameID': 'foobar'},
)
# no matching payer id
mock_payer.return_value = 'payer:unknown'
app.get('/api/regie/foo/credit/%s/' % str(credit.uuid), params={'NameID': 'foobar'}, status=404)
# payer error
mock_payer.side_effect = PayerError
app.get('/api/regie/foo/credit/%s/' % str(credit.uuid), params={'NameID': 'foobar'}, status=404)
def test_pdf_credit_for_payer(app, user):
app.get('/api/regie/foo/credit/%s/pdf/' % str(uuid.uuid4()), status=403)
app.authorization = ('Basic', ('john.doe', 'password'))
app.get('/api/regie/foo/credit/%s/pdf/' % str(uuid.uuid4()), status=404)
regie = Regie.objects.create(label='Foo')
PaymentType.create_defaults(regie)
app.get('/api/regie/foo/credit/%s/pdf/' % str(uuid.uuid4()), status=404)
app.get(
'/api/regie/foo/credit/%s/pdf/' % str(uuid.uuid4()),
params={'payer_external_id': 'payer:1'},
status=404,
)
credit = Credit.objects.create(
label='Credit from 01/09/2022',
date_publication=datetime.date(2022, 10, 1),
regie=regie,
payer_external_id='payer:1',
payer_first_name='First1',
payer_last_name='Name1',
payer_address='41 rue des kangourous\n99999 Kangourou Ville',
)
resp = app.get(
'/api/regie/foo/credit/%s/pdf/' % str(credit.uuid), params={'payer_external_id': 'payer:1'}
)
assert resp.headers['Content-Disposition'] == 'attachment; filename="%s.pdf"' % credit.formatted_number
# other regie
other_regie = Regie.objects.create(label='Other Foo')
credit.regie = other_regie
credit.save()
# publication date is in the future
credit.date_publication = datetime.date.today() + datetime.timedelta(days=1)
credit.save()
app.get(
'/api/regie/foo/credit/%s/pdf/' % str(credit.uuid),
params={'payer_external_id': 'payer:1'},
status=404,
)
# credit is cancelled
credit.date_publication = datetime.date(2022, 10, 1)
credit.cancelled_at = now()
credit.save()
app.get(
'/api/regie/foo/credit/%s/pdf/' % str(credit.uuid),
params={'payer_external_id': 'payer:1'},
status=404,
)
# other regie
other_regie = Regie.objects.create(label='Other Foo')
credit.cancelled_at = None
credit.regie = other_regie
credit.save()
app.get(
'/api/regie/foo/credit/%s/pdf/' % str(credit.uuid),
params={'payer_external_id': 'payer:1'},
status=404,
)
# campaign is not finalized
campaign = Campaign.objects.create(
regie=regie,
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
date_publication=datetime.date(2022, 10, 1),
date_payment_deadline=datetime.date(2022, 10, 31),
date_due=datetime.date(2022, 10, 31),
date_debit=datetime.date(2022, 11, 15),
finalized=False,
)
pool = Pool.objects.create(
campaign=campaign,
draft=False,
)
credit.regie = regie
credit.pool = pool
credit.save()
app.get(
'/api/regie/foo/credit/%s/pdf/' % str(credit.uuid),
params={'payer_external_id': 'payer:1'},
status=404,
)
# campaign is finalized
campaign.finalized = True
campaign.save()
app.get(
'/api/regie/foo/credit/%s/pdf/' % str(credit.uuid),
params={'payer_external_id': 'payer:1'},
)

View File

@ -0,0 +1,833 @@
import datetime
import decimal
import uuid
from unittest import mock
import pytest
from lingo.invoicing.models import (
Invoice,
InvoiceLine,
InvoiceLinePayment,
PayerError,
Payment,
PaymentType,
Regie,
)
pytestmark = pytest.mark.django_db
def test_add_payment(app, user):
app.post('/api/regie/foo/payments/', status=403)
app.authorization = ('Basic', ('john.doe', 'password'))
app.post('/api/regie/foo/payments/', status=404)
regie = Regie.objects.create(slug='foo')
resp = app.post('/api/regie/foo/payments/', status=400)
assert resp.json['err']
assert resp.json['errors'] == {
'amount': ['This field is required.'],
'payment_type': ['This field is required.'],
'elements_to_pay': ['This field is required.'],
}
params = {
'amount': 64,
'payment_type': 'foo', # unknown payment type
'elements_to_pay': 'foo, %s' % str(uuid.uuid4()),
}
resp = app.post('/api/regie/foo/payments/', params=params, status=400)
assert resp.json['err']
assert resp.json['errors'] == {
'payment_type': ['Object with slug=foo does not exist.'],
'elements_to_pay': {'0': ['Must be a valid UUID.']},
}
# unknown payment type for this regie
other_regie = Regie.objects.create(slug='bar')
PaymentType.create_defaults(other_regie)
params = {
'amount': 64,
'payment_type': 'check',
'elements_to_pay': str(uuid.uuid4()),
}
resp = app.post('/api/regie/foo/payments/', params=params, status=400)
assert resp.json['err']
assert resp.json['errors'] == {
'elements_to_pay': {'0': ['Unknown invoice.']},
'payment_type': ['Object with slug=check does not exist.'],
}
PaymentType.create_defaults(regie) # create default payment types
PaymentType.objects.filter(slug='check').update(disabled=True) # disabled payment type
params = {
'amount': 64,
'payment_type': 'check',
'elements_to_pay': str(uuid.uuid4()),
}
resp = app.post('/api/regie/foo/payments/', params=params, status=400)
assert resp.json['err']
assert resp.json['errors'] == {
'elements_to_pay': {'0': ['Unknown invoice.']},
'payment_type': ['Object with slug=check does not exist.'],
}
invoice11 = Invoice.objects.create(
date_publication=datetime.date(2022, 10, 1),
date_payment_deadline=datetime.date(2022, 10, 31),
date_due=datetime.date.today(),
regie=regie,
payer_external_id='payer:1',
)
line111 = InvoiceLine.objects.create(
event_date=datetime.date.today(),
invoice=invoice11,
quantity=1,
unit_amount=-2,
payer_external_id='payer:1',
)
line112 = InvoiceLine.objects.create(
event_date=datetime.date.today(),
invoice=invoice11,
quantity=1,
unit_amount=44,
payer_external_id='payer:1',
)
invoice12 = Invoice.objects.create(
date_publication=datetime.date(2022, 10, 1),
date_payment_deadline=datetime.date(2022, 10, 31),
date_due=datetime.date.today(),
regie=regie,
payer_external_id='payer:1',
)
line12 = InvoiceLine.objects.create(
event_date=datetime.date.today(),
invoice=invoice12,
quantity=1,
unit_amount=42,
payer_external_id='payer:1',
)
invoice13 = Invoice.objects.create(
date_publication=datetime.date(2022, 10, 1),
date_payment_deadline=datetime.date(2022, 10, 31),
date_due=datetime.date.today(),
regie=regie,
payer_external_id='payer:2',
)
line13 = InvoiceLine.objects.create(
event_date=datetime.date.today(),
invoice=invoice13,
quantity=1,
unit_amount=42,
payer_external_id='payer:2',
)
invoice14 = Invoice.objects.create(
date_publication=datetime.date(2022, 10, 1),
date_payment_deadline=datetime.date(2022, 10, 31),
date_due=datetime.date.today() - datetime.timedelta(days=1), # past date
regie=regie,
payer_external_id='payer:1',
)
line14 = InvoiceLine.objects.create(
event_date=datetime.date.today(),
invoice=invoice14,
quantity=1,
unit_amount=42,
payer_external_id='payer:1',
)
invoice21 = Invoice.objects.create(
date_publication=datetime.date(2022, 10, 1),
date_payment_deadline=datetime.date(2022, 10, 31),
date_due=datetime.date(2022, 10, 31),
regie=regie,
payer_external_id='payer:1',
)
InvoiceLine.objects.create(
event_date=datetime.date.today(),
invoice=invoice21,
quantity=1,
unit_amount=42,
payer_external_id='payer:1',
)
other_invoice = Invoice.objects.create(
date_publication=datetime.date(2022, 10, 1),
date_payment_deadline=datetime.date(2022, 10, 31),
date_due=datetime.date.today(),
regie=other_regie,
payer_external_id='payer:1',
)
other_line = InvoiceLine.objects.create(
event_date=datetime.date.today(),
invoice=other_invoice,
quantity=1,
unit_amount=42,
payer_external_id='payer:1',
)
params = {
'amount': 64,
'payment_type': 'cash',
'elements_to_pay': ','.join([str(invoice11.uuid), str(other_invoice.uuid)]), # not the same regie
}
resp = app.post('/api/regie/foo/payments/', params=params, status=400)
assert resp.json['err']
assert resp.json['errors'] == {'elements_to_pay': {'1': ['Unknown invoice.']}}
params = {
'amount': 64,
'payment_type': 'cash',
'elements_to_pay': ','.join([str(invoice11.uuid), str(invoice13.uuid)]), # not the same payer
}
resp = app.post('/api/regie/foo/payments/', params=params, status=400)
assert resp.json['err']
assert resp.json['errors'] == {
'elements_to_pay': ['Can not create payment for invoices of different payers.']
}
params = {
'amount': 64,
'payment_type': 'cash',
'elements_to_pay': ','.join([str(invoice11.uuid), str(invoice14.uuid)]), # too late for invoice14
}
resp = app.post('/api/regie/foo/payments/', params=params, status=400)
assert resp.json['err']
assert resp.json['errors'] == {'elements_to_pay': {'1': ['The invoice due date has passed.']}}
params = {
'amount': 0,
'payment_type': 'cash',
'elements_to_pay': ','.join([str(invoice11.uuid)]),
}
resp = app.post('/api/regie/foo/payments/', params=params, status=400)
assert resp.json['err']
assert resp.json['errors'] == {'amount': ['Ensure this value is greater than or equal to 0.01.']}
params = {
'amount': -0.01,
'payment_type': 'cash',
'elements_to_pay': ','.join([str(invoice11.uuid)]),
}
resp = app.post('/api/regie/foo/payments/', params=params, status=400)
assert resp.json['err']
assert resp.json['errors'] == {'amount': ['Ensure this value is greater than or equal to 0.01.']}
params = {
'amount': 10,
'payment_type': 'cash',
'elements_to_pay': ','.join([str(invoice11.uuid)]),
'check_number': '123456',
}
resp = app.post('/api/regie/foo/payments/', params=params)
assert Payment.objects.count() == 1
assert InvoiceLinePayment.objects.count() == 2
payment = Payment.objects.latest('pk')
assert payment.regie == regie
assert payment.amount == 10
assert payment.payment_type.slug == 'cash'
assert payment.payment_type.regie == regie
assert payment.order_id is None
assert payment.order_date is None
assert payment.number == 1
assert payment.formatted_number == 'R%02d-%s-0000001' % (
regie.pk,
datetime.date.today().strftime('%y-%m'),
)
assert payment.payment_info == {
'check_number': '123456',
}
(
invoice_line_payment1,
invoice_line_payment2,
) = payment.invoicelinepayment_set.order_by('pk')
assert invoice_line_payment1.amount == -2
assert invoice_line_payment1.line == line111
assert invoice_line_payment2.amount == 12
assert invoice_line_payment2.line == line112
invoice11.refresh_from_db()
assert invoice11.paid_amount == 10
assert invoice11.remaining_amount == 32
invoice12.refresh_from_db()
assert invoice12.paid_amount == 0
assert invoice12.remaining_amount == 42
PaymentType.objects.all().update(disabled=False)
params = {
'amount': 10,
'payment_type': 'check',
'elements_to_pay': ','.join([str(invoice11.uuid), str(invoice12.uuid)]),
'check_number': '123456',
'check_issuer': 'Foo',
'check_bank': 'Bar',
'payment_reference': 'Ref',
}
resp = app.post('/api/regie/foo/payments/', params=params)
assert Payment.objects.count() == 2
assert InvoiceLinePayment.objects.count() == 3
payment = Payment.objects.latest('pk')
assert payment.regie == regie
assert payment.amount == 10
assert payment.payment_type.slug == 'check'
assert payment.payment_type.regie == regie
assert payment.order_id is None
assert payment.order_date is None
assert payment.number == 2
assert payment.formatted_number == 'R%02d-%s-0000002' % (
regie.pk,
datetime.date.today().strftime('%y-%m'),
)
assert payment.payment_info == {
'check_number': '123456',
'check_issuer': 'Foo',
'check_bank': 'Bar',
'payment_reference': 'Ref',
}
(invoice_line_payment,) = payment.invoicelinepayment_set.order_by('pk')
assert invoice_line_payment.amount == 10
assert invoice_line_payment.line == line112
invoice11.refresh_from_db()
assert invoice11.paid_amount == 20
assert invoice11.remaining_amount == 22
invoice12.refresh_from_db()
assert invoice12.paid_amount == 0
assert invoice12.remaining_amount == 42
params = {
'amount': 22.01,
'payment_type': 'check',
'elements_to_pay': ','.join([str(invoice12.uuid), str(invoice11.uuid)]),
}
resp = app.post('/api/regie/foo/payments/', params=params)
assert Payment.objects.count() == 3
assert InvoiceLinePayment.objects.count() == 5
payment = Payment.objects.latest('pk')
assert payment.regie == regie
assert payment.amount == decimal.Decimal('22.01')
assert payment.payment_type.slug == 'check'
assert payment.payment_type.regie == regie
assert payment.order_id is None
assert payment.order_date is None
assert payment.number == 3
assert payment.formatted_number == 'R%02d-%s-0000003' % (
regie.pk,
datetime.date.today().strftime('%y-%m'),
)
invoice_line_payment1, invoice_line_payment2 = payment.invoicelinepayment_set.order_by('pk')
assert invoice_line_payment1.amount == 22
assert invoice_line_payment1.line == line112 # older invoice first
assert invoice_line_payment2.amount == decimal.Decimal('0.01')
assert invoice_line_payment2.line == line12
invoice11.refresh_from_db()
assert invoice11.paid_amount == 42
assert invoice11.remaining_amount == 0
invoice12.refresh_from_db()
assert invoice12.paid_amount == decimal.Decimal('0.01')
assert invoice12.remaining_amount == decimal.Decimal('41.99')
# to much
params = {
'amount': 42,
'payment_type': 'check',
'elements_to_pay': ','.join([str(invoice11.uuid), str(invoice12.uuid)]),
}
resp = app.post('/api/regie/foo/payments/', params=params, status=400)
assert resp.json['err']
assert resp.json['errors'] == {'amount': ['Amount is bigger than sum of invoices remaining amounts.']}
params = {
'amount': 41.99,
'payment_type': 'check',
'elements_to_pay': ','.join([str(invoice11.uuid), str(invoice12.uuid)]),
}
resp = app.post('/api/regie/foo/payments/', params=params)
assert Payment.objects.count() == 4
assert InvoiceLinePayment.objects.count() == 6
payment = Payment.objects.latest('pk')
assert payment.regie == regie
assert payment.amount == decimal.Decimal('41.99')
assert payment.payment_type.slug == 'check'
assert payment.payment_type.regie == regie
assert payment.order_id is None
assert payment.order_date is None
assert payment.number == 4
assert payment.formatted_number == 'R%02d-%s-0000004' % (
regie.pk,
datetime.date.today().strftime('%y-%m'),
)
(invoice_line_payment,) = payment.invoicelinepayment_set.order_by('pk')
assert invoice_line_payment.amount == decimal.Decimal('41.99')
assert invoice_line_payment.line == line12
invoice11.refresh_from_db()
assert invoice11.paid_amount == 42
assert invoice11.remaining_amount == 0
invoice12.refresh_from_db()
assert invoice12.paid_amount == 42
assert invoice12.remaining_amount == 0
# delete payements, and call endpoint with a list of lines
InvoiceLinePayment.objects.all().delete()
Payment.objects.all().delete()
params = {
'amount': 64,
'payment_type': 'cash',
'elements_to_pay': ','.join(
['line:%s' % line111.uuid, 'line:%s' % other_line.uuid]
), # not the same regie
}
resp = app.post('/api/regie/foo/payments/', params=params, status=400)
assert resp.json['err']
assert resp.json['errors'] == {'elements_to_pay': {'1': ['Unknown invoice line.']}}
params = {
'amount': 64,
'payment_type': 'cash',
'elements_to_pay': ','.join(
['line:%s' % line111.uuid, 'line:%s' % line13.uuid]
), # not the same payer
}
resp = app.post('/api/regie/foo/payments/', params=params, status=400)
assert resp.json['err']
assert resp.json['errors'] == {
'elements_to_pay': ['Can not create payment for invoice lines of different payers.']
}
params = {
'amount': 64,
'payment_type': 'cash',
'elements_to_pay': ','.join(
['line:%s' % line111.uuid, 'line:%s' % line14.uuid]
), # too late for line14
}
resp = app.post('/api/regie/foo/payments/', params=params, status=400)
assert resp.json['err']
assert resp.json['errors'] == {
'elements_to_pay': {'1': ['The invoice due date of this line has passed.']}
}
params = {
'amount': 10,
'payment_type': 'cash',
'elements_to_pay': ','.join(['line:%s' % line112.uuid, str(invoice13.uuid)]), # invoice13 is ignored
}
resp = app.post('/api/regie/foo/payments/', params=params)
assert Payment.objects.count() == 1
assert InvoiceLinePayment.objects.count() == 1
payment = Payment.objects.latest('pk')
assert payment.regie == regie
assert payment.amount == 10
assert payment.payment_type.slug == 'cash'
assert payment.payment_type.regie == regie
assert payment.order_id is None
assert payment.order_date is None
assert payment.number == 5
assert payment.formatted_number == 'R%02d-%s-0000005' % (
regie.pk,
datetime.date.today().strftime('%y-%m'),
)
(invoice_line_payment,) = payment.invoicelinepayment_set.order_by('pk')
assert invoice_line_payment.amount == 10
assert invoice_line_payment.line == line112
invoice11.refresh_from_db()
assert invoice11.paid_amount == 10
assert invoice11.remaining_amount == 32
invoice12.refresh_from_db()
assert invoice12.paid_amount == 0
assert invoice12.remaining_amount == 42
params = {
'amount': 10,
'payment_type': 'check',
'elements_to_pay': ','.join(
['line:%s' % line111.uuid, 'line:%s' % line112.uuid, 'line:%s' % line12.uuid]
),
}
resp = app.post('/api/regie/foo/payments/', params=params)
assert Payment.objects.count() == 2
assert InvoiceLinePayment.objects.count() == 3
payment = Payment.objects.latest('pk')
assert payment.regie == regie
assert payment.amount == 10
assert payment.payment_type.slug == 'check'
assert payment.payment_type.regie == regie
assert payment.order_id is None
assert payment.order_date is None
assert payment.number == 6
assert payment.formatted_number == 'R%02d-%s-0000006' % (
regie.pk,
datetime.date.today().strftime('%y-%m'),
)
(
invoice_line_payment1,
invoice_line_payment2,
) = payment.invoicelinepayment_set.order_by('pk')
assert invoice_line_payment1.amount == -2
assert invoice_line_payment1.line == line111
assert invoice_line_payment2.amount == 12
assert invoice_line_payment2.line == line112
invoice11.refresh_from_db()
assert invoice11.paid_amount == 20
assert invoice11.remaining_amount == 22
invoice12.refresh_from_db()
assert invoice12.paid_amount == 0
assert invoice12.remaining_amount == 42
params = {
'amount': 22.01,
'payment_type': 'check',
'elements_to_pay': ','.join(
['line:%s' % line111.uuid, 'line:%s' % line112.uuid, 'line:%s' % line12.uuid]
),
}
resp = app.post('/api/regie/foo/payments/', params=params)
assert Payment.objects.count() == 3
assert InvoiceLinePayment.objects.count() == 5
payment = Payment.objects.latest('pk')
assert payment.regie == regie
assert payment.amount == decimal.Decimal('22.01')
assert payment.payment_type.slug == 'check'
assert payment.payment_type.regie == regie
assert payment.order_id is None
assert payment.order_date is None
assert payment.number == 7
assert payment.formatted_number == 'R%02d-%s-0000007' % (
regie.pk,
datetime.date.today().strftime('%y-%m'),
)
invoice_line_payment1, invoice_line_payment2 = payment.invoicelinepayment_set.order_by('pk')
assert invoice_line_payment1.amount == 22
assert invoice_line_payment1.line == line112 # older invoice first
assert invoice_line_payment2.amount == decimal.Decimal('0.01')
assert invoice_line_payment2.line == line12
invoice11.refresh_from_db()
assert invoice11.paid_amount == 42
assert invoice11.remaining_amount == 0
invoice12.refresh_from_db()
assert invoice12.paid_amount == decimal.Decimal('0.01')
assert invoice12.remaining_amount == decimal.Decimal('41.99')
# to much
params = {
'amount': 42,
'payment_type': 'check',
'elements_to_pay': ','.join(
['line:%s' % line111.uuid, 'line:%s' % line112.uuid, 'line:%s' % line12.uuid]
),
}
resp = app.post('/api/regie/foo/payments/', params=params, status=400)
assert resp.json['err']
assert resp.json['errors'] == {
'amount': ['Amount is bigger than sum of invoice lines remaining amounts.']
}
params = {
'amount': 41.99,
'payment_type': 'check',
'elements_to_pay': ','.join(
['line:%s' % line111.uuid, 'line:%s' % line112.uuid, 'line:%s' % line12.uuid]
),
}
resp = app.post('/api/regie/foo/payments/', params=params)
assert Payment.objects.count() == 4
assert InvoiceLinePayment.objects.count() == 6
payment = Payment.objects.latest('pk')
assert payment.regie == regie
assert payment.amount == decimal.Decimal('41.99')
assert payment.payment_type.slug == 'check'
assert payment.payment_type.regie == regie
assert payment.order_id is None
assert payment.order_date is None
assert payment.number == 8
assert payment.formatted_number == 'R%02d-%s-0000008' % (
regie.pk,
datetime.date.today().strftime('%y-%m'),
)
(invoice_line_payment,) = payment.invoicelinepayment_set.order_by('pk')
assert invoice_line_payment.amount == decimal.Decimal('41.99')
assert invoice_line_payment.line == line12
invoice11.refresh_from_db()
assert invoice11.paid_amount == 42
assert invoice11.remaining_amount == 0
invoice12.refresh_from_db()
assert invoice12.paid_amount == 42
assert invoice12.remaining_amount == 0
@mock.patch.object(Regie, 'get_payer_external_id_from_nameid', autospec=True)
def test_list_payments(mock_payer, app, user):
app.get('/api/regie/foo/payments/', status=403)
app.authorization = ('Basic', ('john.doe', 'password'))
app.get('/api/regie/foo/payments/', status=404)
regie = Regie.objects.create(label='Foo')
PaymentType.create_defaults(regie)
app.get('/api/regie/foo/payments/', status=404)
invoice = Invoice.objects.create(
label='My invoice',
date_publication=datetime.date(2022, 10, 1),
date_payment_deadline=datetime.date.today(),
date_due=datetime.date(2022, 10, 31),
regie=regie,
payer_external_id='payer:1',
)
invoice.set_number()
invoice.save()
line = InvoiceLine.objects.create(
event_date=datetime.date.today(),
invoice=invoice,
quantity=1,
unit_amount=42,
)
invoice.refresh_from_db()
payment = Payment.objects.create(
regie=regie,
amount=42,
payment_type=PaymentType.objects.get(regie=regie, slug='cash'),
payer_external_id='payer:1',
)
payment.set_number()
payment.save()
InvoiceLinePayment.objects.create(
payment=payment,
line=line,
amount=42,
)
mock_payer.return_value = 'payer:1'
resp = app.get('/api/regie/foo/payments/', params={'NameID': 'foobar'})
assert resp.json['err'] == 0
assert resp.json['data'] == [
{
'id': str(payment.uuid),
'display_id': 'R%02d-%s-0000001'
% (
regie.pk,
payment.created_at.strftime('%y-%m'),
),
'payment_type': 'Cash',
'amount': 42,
'created': datetime.date.today().isoformat(),
'has_pdf': True,
}
]
assert mock_payer.call_args_list == [mock.call(regie, mock.ANY, 'foobar')]
# other regie
other_regie = Regie.objects.create(label='Other Foo')
payment.regie = other_regie
payment.save()
resp = app.get('/api/regie/foo/payments/', params={'NameID': 'foobar'})
assert resp.json['err'] == 0
assert resp.json['data'] == []
# no matching payer id
payment.regie = regie
payment.save()
mock_payer.return_value = 'payer:unknown'
resp = app.get('/api/regie/foo/payments/', params={'NameID': 'foobar'})
assert resp.json['err'] == 0
assert resp.json['data'] == []
# payer error
mock_payer.side_effect = PayerError
app.get('/api/regie/foo/payments/', params={'NameID': 'foobar'}, status=404)
def test_list_payments_for_payer(app, user):
app.get('/api/regie/foo/payments/', status=403)
app.authorization = ('Basic', ('john.doe', 'password'))
app.get('/api/regie/foo/payments/', status=404)
regie = Regie.objects.create(label='Foo')
PaymentType.create_defaults(regie)
app.get('/api/regie/foo/payments/', status=404)
resp = app.get('/api/regie/foo/payments/', params={'payer_external_id': 'payer:1'})
assert resp.json['err'] == 0
assert resp.json['data'] == []
invoice = Invoice.objects.create(
label='My invoice',
date_publication=datetime.date(2022, 10, 1),
date_payment_deadline=datetime.date.today(),
date_due=datetime.date(2022, 10, 31),
regie=regie,
payer_external_id='payer:1',
)
invoice.set_number()
invoice.save()
line = InvoiceLine.objects.create(
event_date=datetime.date.today(),
invoice=invoice,
quantity=1,
unit_amount=42,
)
invoice.refresh_from_db()
payment = Payment.objects.create(
regie=regie,
amount=42,
payment_type=PaymentType.objects.get(regie=regie, slug='cash'),
payer_external_id='payer:1',
)
payment.set_number()
payment.save()
InvoiceLinePayment.objects.create(
payment=payment,
line=line,
amount=42,
)
resp = app.get('/api/regie/foo/payments/', params={'payer_external_id': 'payer:1'})
assert resp.json['err'] == 0
assert resp.json['data'] == [
{
'id': str(payment.uuid),
'display_id': 'R%02d-%s-0000001'
% (
regie.pk,
payment.created_at.strftime('%y-%m'),
),
'payment_type': 'Cash',
'amount': 42,
'created': datetime.date.today().isoformat(),
'has_pdf': True,
}
]
# other regie
other_regie = Regie.objects.create(label='Other Foo')
payment.regie = other_regie
payment.save()
resp = app.get('/api/regie/foo/payments/', params={'payer_external_id': 'payer:1'})
assert resp.json['err'] == 0
assert resp.json['data'] == []
payment.regie = regie
payment.save()
@mock.patch.object(Regie, 'get_payer_external_id_from_nameid', autospec=True)
def test_pdf_payment(mock_payer, app, user):
app.get('/api/regie/foo/payment/%s/pdf/' % str(uuid.uuid4()), status=403)
app.authorization = ('Basic', ('john.doe', 'password'))
app.get('/api/regie/foo/payment/%s/pdf/' % str(uuid.uuid4()), status=404)
regie = Regie.objects.create(label='Foo')
PaymentType.create_defaults(regie)
app.get('/api/regie/foo/payment/%s/pdf/' % str(uuid.uuid4()), status=404)
app.get('/api/regie/foo/payment/%s/pdf/' % str(uuid.uuid4()), params={'NameID': 'foobar'}, status=404)
invoice = Invoice.objects.create(
label='My invoice',
date_publication=datetime.date(2022, 10, 1),
date_payment_deadline=datetime.date.today(),
date_due=datetime.date(2022, 10, 31),
regie=regie,
payer_external_id='payer:1',
)
invoice.set_number()
invoice.save()
line = InvoiceLine.objects.create(
event_date=datetime.date.today(),
invoice=invoice,
quantity=1,
unit_amount=42,
)
invoice.refresh_from_db()
payment = Payment.objects.create(
regie=regie,
amount=42,
payment_type=PaymentType.objects.get(regie=regie, slug='cash'),
payer_external_id='payer:1',
)
InvoiceLinePayment.objects.create(
payment=payment,
line=line,
amount=42,
)
mock_payer.return_value = 'payer:1'
resp = app.get('/api/regie/foo/payment/%s/pdf/' % str(payment.uuid), params={'NameID': 'foobar'})
assert resp.headers['Content-Disposition'] == 'attachment; filename="%s.pdf"' % payment.formatted_number
# other regie
other_regie = Regie.objects.create(label='Other Foo')
payment.regie = other_regie
payment.save()
app.get('/api/regie/foo/payment/%s/pdf/' % str(payment.uuid), params={'NameID': 'foobar'}, status=404)
# no matching payer id
mock_payer.return_value = 'payer:unknown'
app.get('/api/regie/foo/payment/%s/' % str(payment.uuid), params={'NameID': 'foobar'}, status=404)
# payer error
mock_payer.side_effect = PayerError
app.get('/api/regie/foo/payment/%s/' % str(payment.uuid), params={'NameID': 'foobar'}, status=404)
def test_pdf_payment_for_payer(app, user):
app.get('/api/regie/foo/payment/%s/pdf/' % str(uuid.uuid4()), status=403)
app.authorization = ('Basic', ('john.doe', 'password'))
app.get('/api/regie/foo/payment/%s/pdf/' % str(uuid.uuid4()), status=404)
regie = Regie.objects.create(label='Foo')
PaymentType.create_defaults(regie)
app.get('/api/regie/foo/payment/%s/pdf/' % str(uuid.uuid4()), status=404)
app.get(
'/api/regie/foo/payment/%s/pdf/' % str(uuid.uuid4()),
params={'payer_external_id': 'payer:1'},
status=404,
)
invoice = Invoice.objects.create(
label='My invoice',
date_publication=datetime.date(2022, 10, 1),
date_payment_deadline=datetime.date(2022, 10, 31),
date_due=datetime.date.today(),
regie=regie,
payer_external_id='payer:1',
)
invoice.set_number()
invoice.save()
line = InvoiceLine.objects.create(
event_date=datetime.date.today(),
invoice=invoice,
quantity=1,
unit_amount=42,
)
invoice.refresh_from_db()
payment = Payment.objects.create(
regie=regie,
amount=42,
payment_type=PaymentType.objects.get(regie=regie, slug='cash'),
payer_external_id='payer:1',
)
InvoiceLinePayment.objects.create(
payment=payment,
line=line,
amount=42,
)
resp = app.get(
'/api/regie/foo/payment/%s/pdf/' % str(payment.uuid), params={'payer_external_id': 'payer:1'}
)
assert resp.headers['Content-Disposition'] == 'attachment; filename="%s.pdf"' % payment.formatted_number
# other regie
other_regie = Regie.objects.create(label='Other Foo')
payment.regie = other_regie
payment.save()
app.get(
'/api/regie/foo/payment/%s/pdf/' % str(payment.uuid),
params={'payer_external_id': 'payer:1'},
status=404,
)

View File

@ -0,0 +1,221 @@
import datetime
import uuid
import pytest
from django.utils.timezone import now
from lingo.invoicing.models import Campaign, Credit, CreditAssignment, CreditLine, Pool, Refund, Regie
pytestmark = pytest.mark.django_db
def test_add_refund(app, user):
app.post('/api/regie/foo/refunds/', status=403)
app.authorization = ('Basic', ('john.doe', 'password'))
app.post('/api/regie/foo/refunds/', status=404)
regie = Regie.objects.create(slug='foo')
other_regie = Regie.objects.create(slug='bar')
resp = app.post('/api/regie/foo/refunds/', status=400)
assert resp.json['err']
assert resp.json['errors'] == {
'credit': ['This field is required.'],
}
params = {
'credit': str(uuid.uuid4()),
}
resp = app.post('/api/regie/foo/refunds/', params=params, status=400)
assert resp.json['err']
assert resp.json['errors'] == {'credit': ['Unknown credit.']}
credit = Credit.objects.create(
label='Credit from 01/09/2022',
date_publication=datetime.date(2022, 10, 1),
regie=regie,
payer_external_id='payer:1',
payer_first_name='First1',
payer_last_name='Name1',
payer_address='41 rue des kangourous\n99999 Kangourou Ville',
)
credit.set_number()
credit.save()
CreditLine.objects.create(
event_date=datetime.date(2022, 9, 1),
credit=credit,
quantity=42,
unit_amount=1,
label='Label 11',
user_external_id='user:1',
user_first_name='User1',
user_last_name='Name1',
)
# not the same regie
other_credit = Credit.objects.create(
label='Credit from 01/09/2022',
date_publication=datetime.date(2022, 10, 1),
regie=other_regie,
payer_external_id='payer:1',
payer_first_name='First1',
payer_last_name='Name1',
payer_address='41 rue des kangourous\n99999 Kangourou Ville',
)
CreditLine.objects.create(
credit=other_credit,
event_date=datetime.date(2022, 9, 1),
quantity=3,
unit_amount=1,
)
params = {
'credit': str(other_credit.uuid),
}
resp = app.post('/api/regie/foo/refunds/', params=params, status=400)
assert resp.json['err']
assert resp.json['errors'] == {'credit': ['Unknown credit.']}
other_credit = Credit.objects.create(
date_publication=datetime.date.today() + datetime.timedelta(days=1), # not publicated
regie=regie,
payer_external_id='payer:1',
)
CreditLine.objects.create(
credit=other_credit,
event_date=datetime.date(2022, 9, 1),
quantity=3,
unit_amount=1,
)
params = {
'credit': str(other_credit.uuid),
}
resp = app.post('/api/regie/foo/refunds/', params=params, status=400)
assert resp.json['err']
assert resp.json['errors'] == {'credit': ['Unknown credit.']}
other_credit = Credit.objects.create(
date_publication=datetime.date(2022, 10, 1),
regie=regie,
payer_external_id='payer:1',
cancelled_at=now(), # cancelled
)
CreditLine.objects.create(
credit=other_credit,
event_date=datetime.date(2022, 9, 1),
quantity=3,
unit_amount=1,
)
params = {
'credit': str(other_credit.uuid),
}
resp = app.post('/api/regie/foo/refunds/', params=params, status=400)
assert resp.json['err']
assert resp.json['errors'] == {'credit': ['Unknown credit.']}
campaign = Campaign.objects.create(
regie=regie,
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
date_publication=datetime.date(2022, 10, 1),
date_payment_deadline=datetime.date(2022, 10, 31),
date_due=datetime.date(2022, 10, 31),
date_debit=datetime.date(2022, 11, 15),
finalized=False,
)
pool = Pool.objects.create(
campaign=campaign,
draft=False,
)
other_credit = Credit.objects.create(
date_publication=datetime.date(2022, 10, 1),
regie=regie,
payer_external_id='payer:1',
pool=pool, # not finalized pool
)
CreditLine.objects.create(
credit=other_credit,
event_date=datetime.date(2022, 9, 1),
quantity=3,
unit_amount=1,
)
params = {
'credit': str(other_credit.uuid),
}
resp = app.post('/api/regie/foo/refunds/', params=params, status=400)
assert resp.json['err']
assert resp.json['errors'] == {'credit': ['Unknown credit.']}
params = {
'credit': str(credit.uuid),
}
resp = app.post('/api/regie/foo/refunds/', params=params)
assert Refund.objects.count() == 1
assert CreditAssignment.objects.count() == 1
refund = Refund.objects.latest('pk')
assert refund.regie == regie
assert refund.amount == 42
assert refund.number == 1
assert refund.formatted_number == 'V%02d-%s-0000001' % (
regie.pk,
datetime.date.today().strftime('%y-%m'),
)
assert refund.payer_external_id == 'payer:1'
assert refund.payer_first_name == 'First1'
assert refund.payer_last_name == 'Name1'
assert refund.payer_address == '41 rue des kangourous\n99999 Kangourou Ville'
assignment = CreditAssignment.objects.latest('pk')
assert assignment.credit == credit
assert assignment.refund == refund
assert assignment.invoice is None
assert assignment.payment is None
assert assignment.amount == 42
credit.refresh_from_db()
assert credit.total_amount == 42
assert credit.assigned_amount == 42
assert credit.remaining_amount == 0
# again
resp = app.post('/api/regie/foo/refunds/', params=params, status=400)
assert resp.json['err']
assert resp.json['errors'] == {'credit': ['Credit already completely assigned.']}
CreditLine.objects.create(
event_date=datetime.date(2022, 9, 1),
credit=credit,
quantity=13,
unit_amount=1,
label='Label 11',
user_external_id='user:1',
user_first_name='User1',
user_last_name='Name1',
)
credit.refresh_from_db()
campaign.finalized = True
campaign.save()
credit.pool = pool
credit.save()
assert credit.total_amount == 55
assert credit.assigned_amount == 42
assert credit.remaining_amount == 13
resp = app.post('/api/regie/foo/refunds/', params=params)
assert Refund.objects.count() == 2
assert CreditAssignment.objects.count() == 2
refund = Refund.objects.latest('pk')
assert refund.regie == regie
assert refund.amount == 13
assert refund.number == 2
assert refund.formatted_number == 'V%02d-%s-0000002' % (
regie.pk,
datetime.date.today().strftime('%y-%m'),
)
assert refund.payer_external_id == 'payer:1'
assert refund.payer_first_name == 'First1'
assert refund.payer_last_name == 'Name1'
assert refund.payer_address == '41 rue des kangourous\n99999 Kangourou Ville'
assignment = CreditAssignment.objects.latest('pk')
assert assignment.credit == credit
assert assignment.refund == refund
assert assignment.invoice is None
assert assignment.payment is None
assert assignment.amount == 13
credit.refresh_from_db()
assert credit.total_amount == 55
assert credit.assigned_amount == 55
assert credit.remaining_amount == 0

File diff suppressed because it is too large Load Diff

View File

@ -572,7 +572,7 @@ def test_regie_invoices(app, admin_user, orphan):
invoice3.pk,
)
lines_resp = app.get(lines_url)
assert len(lines_resp.pyquery('tr')) == 16
assert len(lines_resp.pyquery('tr')) == 17
assert [PyQuery(tr).text() for tr in lines_resp.pyquery('tr')] == [
'Demat: no',
'Direct debit: no',
@ -590,6 +590,7 @@ def test_regie_invoices(app, admin_user, orphan):
'Payment\nDate\nType\nAmount',
'No payments for this invoice',
'Remaining amount: 1.00€',
'Cancel invoice',
]
if not orphan:
assert [PyQuery(a).attr('href') for a in lines_resp.pyquery('tr a')] == [
@ -597,6 +598,11 @@ def test_regie_invoices(app, admin_user, orphan):
% (regie.pk, campaign2.pk, pool2.pk),
'/manage/invoicing/regie/%s/campaign/%s/pool/%s/journal/?invoice_line=%s'
% (regie.pk, campaign2.pk, pool2.pk, invoice_line31.pk),
'/manage/invoicing/regie/%s/invoice/%s/cancel/' % (regie.pk, invoice3.pk),
]
else:
assert [PyQuery(a).attr('href') for a in lines_resp.pyquery('tr a')] == [
'/manage/invoicing/regie/%s/invoice/%s/cancel/' % (regie.pk, invoice3.pk),
]
assert resp.pyquery(
@ -1135,3 +1141,111 @@ def test_regie_invoice_payments_pdf(app, admin_user):
invoice.cancelled_at = now()
invoice.save()
app.get('/manage/invoicing/regie/%s/invoice/%s/payments/pdf/?html' % (regie.pk, invoice.pk), status=200)
def test_regie_invoice_cancel(app, admin_user):
regie = Regie.objects.create(
label='Foo',
)
PaymentType.create_defaults(regie)
invoice1 = Invoice.objects.create(
date_publication=datetime.date(2022, 10, 1),
date_payment_deadline=datetime.date(2022, 10, 31),
date_due=datetime.date(2022, 10, 31),
regie=regie,
payer_external_id='payer:1',
payer_first_name='First1',
payer_last_name='Name1',
payer_address='41 rue des kangourous\n99999 Kangourou Ville',
payer_demat=True,
payer_direct_debit=False,
)
invoice1.set_number()
invoice1.save()
invoice2 = Invoice.objects.create(
date_publication=datetime.date(2022, 10, 1),
date_payment_deadline=datetime.date(2022, 10, 31),
date_due=datetime.date(2022, 10, 31),
date_debit=datetime.date(2022, 11, 15),
regie=regie,
payer_external_id='payer:1',
payer_first_name='First1',
payer_last_name='Name1',
payer_address='41 rue des kangourous\n99999 Kangourou Ville',
payer_demat=False,
payer_direct_debit=True,
)
invoice2.set_number()
invoice2.save()
InvoiceLine.objects.create(
event_date=datetime.date(2022, 9, 1),
invoice=invoice1,
quantity=1,
unit_amount=40,
user_external_id='user:1',
user_first_name='User1',
user_last_name='Name1',
)
invoice_line2 = InvoiceLine.objects.create(
event_date=datetime.date(2022, 9, 1),
invoice=invoice2,
quantity=1,
unit_amount=50,
user_external_id='user:1',
user_first_name='User1',
user_last_name='Name1',
)
payment = Payment.objects.create(
regie=regie,
amount=1,
payment_type=PaymentType.objects.get(regie=regie, slug='cash'),
)
payment.set_number()
payment.save()
InvoiceLinePayment.objects.create(
payment=payment,
line=invoice_line2,
amount=1,
)
invoice2.refresh_from_db()
assert invoice2.remaining_amount == 49
assert invoice2.paid_amount == 1
cancellation_reason = InvoiceCancellationReason.objects.create(label='Mistake')
InvoiceCancellationReason.objects.create(label='Disabled', disabled=True)
app = login(app)
resp = app.get('/manage/invoicing/ajax/regie/%s/invoice/%s/lines/' % (regie.pk, invoice1.pk))
resp = resp.click(href='/manage/invoicing/regie/%s/invoice/%s/cancel/' % (regie.pk, invoice1.pk))
assert resp.form['cancellation_reason'].options == [
('', True, '---------'),
(str(cancellation_reason.pk), False, 'Mistake'),
]
resp.form['cancellation_reason'] = cancellation_reason.pk
resp.form['cancellation_description'] = 'foo bar blah'
resp = resp.form.submit()
assert resp.location.endswith(
'/manage/invoicing/regie/%s/invoices/?number=%s' % (regie.pk, invoice1.formatted_number)
)
invoice1.refresh_from_db()
assert invoice1.cancelled_at is not None
assert invoice1.cancelled_by == admin_user
assert invoice1.cancellation_reason == cancellation_reason
assert invoice1.cancellation_description == 'foo bar blah'
assert invoice1.lines.count() == 1
invoice2.refresh_from_db()
assert invoice2.cancelled_at is None
# already cancelled
app.get('/manage/invoicing/regie/%s/invoice/%s/cancel/' % (regie.pk, invoice1.pk), status=404)
invoice1.cancelled_at = None
invoice1.save()
# other regie
other_regie = Regie.objects.create(label='Foo')
app.get('/manage/invoicing/regie/%s/invoice/%s/cancel/' % (other_regie.pk, invoice1.pk), status=404)
# invoice with payment
app.get('/manage/invoicing/regie/%s/invoice/%s/cancel/' % (regie.pk, invoice2.pk), status=404)

View File

@ -706,6 +706,8 @@ def test_regie_payment_cancel(app, admin_user):
app.get('/manage/invoicing/regie/%s/payment/%s/cancel/' % (regie.pk, payment.pk), status=404)
payment.cancelled_at = None
payment.save()
# other regie
other_regie = Regie.objects.create(label='Foo')
app.get('/manage/invoicing/regie/%s/payment/%s/cancel/' % (other_regie.pk, payment.pk), status=404)