Facturation: ajouter les endpoints nécessaires au bon fonctionnement des cellules facture de combo (#76495) #52
|
@ -34,6 +34,28 @@ urlpatterns = [
|
|||
views.invoicing_regies,
|
||||
name='api-invoicing-regies',
|
||||
),
|
||||
re_path(
|
||||
r'^regie/(?P<regie_identifier>[\w-]+)/invoices/$',
|
||||
views.invoicing_invoices,
|
||||
name='api-invoicing-invoices',
|
||||
),
|
||||
re_path(
|
||||
r'^regie/(?P<regie_identifier>[\w-]+)/invoices/history/$',
|
||||
views.invoicing_history_invoices,
|
||||
name='api-invoicing-history-invoices',
|
||||
),
|
||||
re_path(
|
||||
r'^regie/(?P<regie_identifier>[\w-]+)/invoice/'
|
||||
r'(?P<invoice_identifier>[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12})/$',
|
||||
views.invoicing_invoice,
|
||||
name='api-invoicing-invoice',
|
||||
),
|
||||
re_path(
|
||||
r'^regie/(?P<regie_identifier>[\w-]+)/invoice/'
|
||||
r'(?P<invoice_identifier>[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12})/pay/$',
|
||||
views.invoicing_invoice_pay,
|
||||
name='api-invoicing-invoice-pay',
|
||||
),
|
||||
bdauvergne marked this conversation as resolved
Outdated
|
||||
re_path(
|
||||
r'^regie/(?P<regie_identifier>[\w-]+)/injected-lines/$',
|
||||
views.injected_lines,
|
||||
|
|
|
@ -14,6 +14,10 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import datetime
|
||||
|
||||
import pytz
|
||||
from django.http import Http404
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils.translation import gettext_noop as N_
|
||||
from rest_framework import permissions
|
||||
|
@ -22,7 +26,8 @@ from rest_framework.views import APIView
|
|||
from lingo.agendas.models import Agenda
|
||||
from lingo.api import serializers
|
||||
from lingo.api.utils import APIErrorBadRequest, Response
|
||||
from lingo.invoicing.models import InjectedLine, Payment, Regie
|
||||
from lingo.invoicing.models import InjectedLine, Invoice, Payment, Regie
|
||||
from lingo.pricing.models import AgendaPricing, PayerError
|
||||
|
||||
|
||||
class AgendaCheckTypeList(APIView):
|
||||
|
@ -75,6 +80,114 @@ class InvoicingRegies(APIView):
|
|||
invoicing_regies = InvoicingRegies.as_view()
|
||||
|
||||
|
||||
class InvoiceMixin:
|
||||
def get_payer_external_ids(self, request, regie, nameid):
|
||||
if not nameid:
|
||||
raise Http404
|
||||
payer_external_ids = set()
|
||||
agenda_pricing_qs = (
|
||||
AgendaPricing.objects.filter(
|
||||
agendas__regie=regie,
|
||||
)
|
||||
.distinct()
|
||||
.order_by('pk')
|
||||
)
|
||||
for agenda_pricing in agenda_pricing_qs:
|
||||
lguerin
commented
Il faut parcourir les grilles tarifaires attachées aux agendas de la régie, c'est vraiment pas top. Il faut parcourir les grilles tarifaires attachées aux agendas de la régie, c'est vraiment pas top.
A revoir avec le plan long terme pour paramétrer les infos du payeur.
|
||||
try:
|
||||
payer_external_ids.add(agenda_pricing.get_payer_external_id_from_nameid(request, nameid))
|
||||
except PayerError:
|
||||
pass
|
||||
return list(payer_external_ids)
|
||||
|
||||
|
||||
class InvoicingInvoices(InvoiceMixin, APIView):
|
||||
permission_classes = (permissions.IsAuthenticated,)
|
||||
|
||||
def get_invoices_queryset(self, request, regie):
|
||||
payer_external_ids = self.get_payer_external_ids(request, regie, request.GET.get('NameID'))
|
||||
return Invoice.objects.filter(
|
||||
regie=regie,
|
||||
remaining_amount__gt=0,
|
||||
date_publication__lte=datetime.date.today(),
|
||||
payer_external_id__in=payer_external_ids,
|
||||
).order_by('-created_at')
|
||||
|
||||
def get(self, request, regie_identifier):
|
||||
regie = get_object_or_404(Regie, slug=regie_identifier)
|
||||
invoices = self.get_invoices_queryset(request, regie)
|
||||
return Response({'data': [invoice.normalize() for invoice in invoices]})
|
||||
|
||||
|
||||
invoicing_invoices = InvoicingInvoices.as_view()
|
||||
|
||||
|
||||
class InvoicingHistoryInvoices(InvoicingInvoices):
|
||||
def get_invoices_queryset(self, request, regie):
|
||||
payer_external_ids = self.get_payer_external_ids(request, regie, request.GET.get('NameID'))
|
||||
return Invoice.objects.filter(
|
||||
regie=regie,
|
||||
remaining_amount=0,
|
||||
date_publication__lte=datetime.date.today(),
|
||||
payer_external_id__in=payer_external_ids,
|
||||
).order_by('-created_at')
|
||||
|
||||
|
||||
invoicing_history_invoices = InvoicingHistoryInvoices.as_view()
|
||||
|
||||
|
||||
class InvoicingInvoice(InvoiceMixin, APIView):
|
||||
permission_classes = (permissions.IsAuthenticated,)
|
||||
|
||||
def get(self, request, regie_identifier, invoice_identifier):
|
||||
regie = get_object_or_404(Regie, slug=regie_identifier)
|
||||
payer_external_ids = self.get_payer_external_ids(request, regie, request.GET.get('NameID'))
|
||||
invoice = get_object_or_404(
|
||||
Invoice,
|
||||
uuid=invoice_identifier,
|
||||
regie=regie,
|
||||
date_publication__lte=datetime.date.today(),
|
||||
payer_external_id__in=payer_external_ids,
|
||||
)
|
||||
return Response({'data': invoice.normalize()})
|
||||
|
||||
|
||||
invoicing_invoice = InvoicingInvoice.as_view()
|
||||
|
||||
|
||||
class InvoicingInvoicePay(APIView):
|
||||
# XXX ?
|
||||
authentication_classes = []
|
||||
permission_classes = ()
|
||||
bdauvergne marked this conversation as resolved
Outdated
lguerin
commented
Heu là je ne sais pas. Heu là je ne sais pas.
Il a fallu que j'ajoute ça pour pouvoir faire un paiement depuis le portail, mais je ne sais pas s'il ne me manquerait pas un bout de config.
bdauvergne
commented
Si tu charges debian_config_common.py dans ton debian/debian_config.py normalement il n'y a rien à faire (ne même pas déclarer authentication/permission_classes, les valeurs par défaut suffisent):
Si tu charges debian_config_common.py dans ton debian/debian_config.py normalement il n'y a rien à faire (ne même pas déclarer authentication/permission_classes, les valeurs par défaut suffisent):
```
$ grep -C3 -H -n REST.*DEFAULT.*ION debian/debian_config_common.py
debian/debian_config_common.py-406- if 'REST_FRAMEWORK' not in globals():
debian/debian_config_common.py-407- REST_FRAMEWORK = {}
debian/debian_config_common.py-408- if 'authentic2' not in INSTALLED_APPS:
debian/debian_config_common.py:409: REST_FRAMEWORK['DEFAULT_AUTHENTICATION_CLASSES'] = (
debian/debian_config_common.py-410- 'hobo.rest_authentication.PublikAuthentication',
debian/debian_config_common.py-411- 'hobo.rest_authentication.APIClientAuthentication',
debian/debian_config_common.py-412- )
debian/debian_config_common.py-413- else:
debian/debian_config_common.py:414: REST_FRAMEWORK['DEFAULT_AUTHENTICATION_CLASSES'] = ('hobo.rest_authentication.PublikAuthentication',)
debian/debian_config_common.py:415: REST_FRAMEWORK['DEFAULT_PERMISSION_CLASSES'] = ('rest_framework.permissions.IsAuthenticated',)
debian/debian_config_common.py-416- REST_FRAMEWORK['DEFAULT_RENDERER_CLASSES'] = ('rest_framework.renderers.JSONRenderer',)
bdauvergne
commented
Si ça n'a pas marché tout seul il y a peut-être une souci au niveau des clés déclarés coté portail et coté lingo (voir le hobo.json dans le répertoire du tenant si c'est cohérent, sinon juste refaire un déploiement factice, i.e. éditer le nom du service dans hobo, ne rien changer, puis sauver). Si ça n'a pas marché tout seul il y a peut-être une souci au niveau des clés déclarés coté portail et coté lingo (voir le hobo.json dans le répertoire du tenant si c'est cohérent, sinon juste refaire un déploiement factice, i.e. éditer le nom du service dans hobo, ne rien changer, puis sauver).
lguerin
commented
J'ai bien hobo.rest_authentication.PublikAuthentication et hobo.rest_authentication.APIClientAuthentication dans DEFAULT_AUTHENTICATION_CLASSES, et les mêmes clés côté lingo et combo J'ai ça dans les logs côté lingo:
Et côté combo:
J'ai bien hobo.rest_authentication.PublikAuthentication et hobo.rest_authentication.APIClientAuthentication dans DEFAULT_AUTHENTICATION_CLASSES, et les mêmes clés côté lingo et combo
J'ai ça dans les logs côté lingo:
```
Unauthorized: /api/regie/blah/invoice/544c7a9a-652a-4628-9599-3b8709e60890/pay/
"POST /api/regie/blah/invoice/544c7a9a-652a-4628-9599-3b8709e60890/pay/?NameID=&email=&orig=combo.dev.publik.love&algo=sha256×tamp=2023-04-13T09%3A18%3A51Z&nonce=2fb8367cbc9c49d7132666332819b858&signature=PSGecbF0U4H5zQEFRK5tx1QjGAx5QvuJSvRWVRkIS4w%3D HTTP/1.0" 401 37
```
Et côté combo:
```
failed to POST https://lingo.dev.publik.love/api/regie/blah/invoice/544c7a9a-652a-4628-9599-3b8709e60890/pay/?NameID=&email=&orig=combo.dev.publik.love&algo=sha256×tamp=2023-04-13T09%3A18%3A51Z&nonce=2fb8367cbc9c49d7132666332819b858&signature=PSGecbF0U4H5zQEFRK5tx1QjGAx5QvuJSvRWVRkIS4w%3D (401)
b'{"err":1,"err_desc":"user-not-found"}'
```
bdauvergne
commented
D'ac, le souci c'est ça 👍
déjà c'est bizarre d'avoir les deux, mais surtout ils sont vides. Soit tu n'en mets pas et tu t'authentifies "en tant que service" avec tous les droits possibles, soit c'est défini et tu as les "droits" de l'utilisateur correspondant en fonction du contexte. Ici il y a un truc qui déconne dans combo, on dirait qu'on est pas loggé sur le portail. D'ac, le souci c'est ça 👍
?NameID=&email=
déjà c'est bizarre d'avoir les deux, mais surtout ils sont vides. Soit tu n'en mets pas et tu t'authentifies "en tant que service" avec tous les droits possibles, soit c'est défini et tu as les "droits" de l'utilisateur correspondant en fonction du contexte.
Ici il y a un truc qui déconne dans combo, on dirait qu'on est pas loggé sur le portail.
lguerin
commented
la méthode
pas de user en param, puisque ça peut être fait dans un cron la méthode `pay_invoice` (combo/lingo) envoie:
```
response = requests.post(url, remote_service='auto', data=json.dumps(data), headers=headers)
```
pas de user en param, puisque ça peut être fait dans un cron
bdauvergne
commented
Généralement pour les factures on notifie passerelle qui n'utilise pas rest-framework et ses classes d'authentification, c'est bien possible qu'on découvre que c'est incompatible à cette occasion, mais clairement la classe hobo.rest_authentication.PublikAuthentication n'accepte pas la présence d'un champ NameID vide (et pour les paiements sur panier c'est vers w.c.s. qui pareil ne se comporte pas de la même manière je suppose). À faire :
Mais le comportement de passerelle et w.c.s. est potentiellement problématique vu qu'un NameID vide est considéré comme une autorisation par service et pas par utilisateur (ça permettrait de lire des choses qu'on ne devrait pas)... Généralement pour les factures on notifie passerelle qui n'utilise pas rest-framework et ses classes d'authentification, c'est bien possible qu'on découvre que c'est incompatible à cette occasion, mais clairement la classe hobo.rest_authentication.PublikAuthentication n'accepte pas la présence d'un champ NameID vide (et pour les paiements sur panier c'est vers w.c.s. qui pareil ne se comporte pas de la même manière je suppose).
À faire :
* vérifier que w.c.s et passerelle considèrent qu'un NameID vide c'est comme pas de NameID
* modifier PublikAuthentication pour prendre ce comportement, puisque c'est le comportement existant, ou alors pay_invoice().
Mais le comportement de passerelle et w.c.s. est potentiellement problématique vu qu'un NameID vide est considéré comme une autorisation par service et pas par utilisateur (ça permettrait de lire des choses qu'on ne devrait pas)...
fpeters
commented
Pour w.c.s. un NameID vide n'est pas équivalent à l'absence de NameID,
> vérifier que w.c.s et passerelle considèrent qu'un NameID vide c'est comme pas de NameID
Pour w.c.s. un NameID vide n'est *pas* équivalent à l'absence de NameID,
```
# email or NameID were given as empty to the query string, this maps
# [to] the anonymous user case.
```
bdauvergne
commented
Pour les endpoints de l'API peut-être mais pas pour les triggers si item.by n'est pas défini :
item.by n'est pas configuré je pense en général pour un trigger "pay" et donc ça marche. Je dirai que la correction est à faire coté combo, il ne doit pas mettre de NameID/email pour une notification de paiement. C'est sans objet. > Pour w.c.s. un NameID vide n'est *pas* équivalent à l'absence de NameID,
>
> ```
> # email or NameID were given as empty to the query string, this maps
> # [to] the anonymous user case.
> ```
Pour les endpoints de l'API peut-être mais pas pour les triggers si item.by n'est pas défini :
```
signed_request = is_url_signed()
user = get_user_from_api_query_string() or get_request().user
....
if signed_request and not item.by:
pass
else:
if not user:
raise errors.AccessForbiddenError('user not authenticated')
if not item.check_auth(self.formdata, user):
raise errors.AccessForbiddenError('unsufficient roles')
```
item.by n'est pas configuré je pense en général pour un trigger "pay" et donc ça marche. Je dirai que la correction est à faire coté combo, il ne doit pas mettre de NameID/email pour une notification de paiement. C'est sans objet.
|
||||
|
||||
def post(self, request, regie_identifier, invoice_identifier):
|
||||
data = request.data
|
||||
order_id = data.get('transaction_id')
|
||||
order_date = data.get('transaction_date')
|
||||
if order_date:
|
||||
try:
|
||||
order_date = pytz.utc.localize(datetime.datetime.strptime(order_date, '%Y-%m-%dT%H:%M:%S'))
|
||||
except ValueError:
|
||||
order_date = None
|
||||
invoice = get_object_or_404(
|
||||
Invoice,
|
||||
uuid=invoice_identifier,
|
||||
regie__slug=regie_identifier,
|
||||
remaining_amount__gt=0,
|
||||
)
|
||||
Payment.make_payment(
|
||||
regie=invoice.regie,
|
||||
invoices=[invoice],
|
||||
amount=invoice.remaining_amount,
|
||||
lguerin
commented
reprendre le montant trouvé dans data après merge de #76572 reprendre le montant trouvé dans data après merge de #76572
|
||||
payment_type='online',
|
||||
order_id=order_id,
|
||||
order_date=order_date,
|
||||
)
|
||||
return Response({'data': True})
|
||||
|
||||
|
||||
invoicing_invoice_pay = InvoicingInvoicePay.as_view()
|
||||
|
||||
|
||||
class InjectedLines(APIView):
|
||||
permission_classes = (permissions.IsAuthenticated,)
|
||||
serializer_class = serializers.InjectedLineSerializer
|
||||
|
|
|
@ -44,6 +44,7 @@ class Migration(migrations.Migration):
|
|||
('cash', 'Cash'),
|
||||
('check', 'Check'),
|
||||
('directdebit', 'Direct debit'),
|
||||
('online', 'Online'),
|
||||
],
|
||||
max_length=20,
|
||||
),
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('invoicing', '0029_invoice_uuid'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='payment',
|
||||
name='order_date',
|
||||
field=models.DateTimeField(null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='payment',
|
||||
name='order_id',
|
||||
field=models.CharField(max_length=200, null=True),
|
||||
),
|
||||
]
|
|
@ -15,6 +15,7 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import copy
|
||||
import datetime
|
||||
import decimal
|
||||
import sys
|
||||
import traceback
|
||||
|
@ -406,6 +407,27 @@ class Invoice(AbstractInvoice):
|
|||
)
|
||||
self.formatted_number = self.regie.format_number(self.created_at, self.number)
|
||||
|
||||
def normalize(self):
|
||||
paid = bool(self.remaining_amount == 0)
|
||||
payable = True
|
||||
if paid:
|
||||
payable = False
|
||||
elif self.date_payment_deadline < datetime.date.today():
|
||||
payable = False
|
||||
return {
|
||||
'id': str(self.uuid),
|
||||
'display_id': self.formatted_number,
|
||||
'label': self.label,
|
||||
'paid': paid,
|
||||
'amount': self.remaining_amount,
|
||||
'total_amount': self.total_amount,
|
||||
'amount_paid': self.paid_amount,
|
||||
'created': self.created_at.date(),
|
||||
'pay_limit_date': self.date_payment_deadline if not paid else '',
|
||||
'online_payment': not self.payer_direct_debit if payable else False,
|
||||
'has_pdf': False,
|
||||
}
|
||||
|
||||
|
||||
class InjectedLine(models.Model):
|
||||
event_date = models.DateField()
|
||||
|
@ -562,6 +584,7 @@ PAYMENT_TYPES = [
|
|||
('cash', _('Cash')),
|
||||
('check', _('Check')),
|
||||
('directdebit', _('Direct debit')),
|
||||
('online', _('Online')),
|
||||
]
|
||||
|
||||
|
||||
|
@ -571,16 +594,20 @@ class Payment(models.Model):
|
|||
max_digits=9, decimal_places=2, validators=[validators.MinValueValidator(decimal.Decimal('0.01'))]
|
||||
)
|
||||
payment_type = models.CharField(max_length=20, choices=PAYMENT_TYPES)
|
||||
order_id = models.CharField(max_length=200, null=True)
|
||||
order_date = models.DateTimeField(null=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
@classmethod
|
||||
def make_payment(cls, regie, invoices, amount, payment_type):
|
||||
def make_payment(cls, regie, invoices, amount, payment_type, order_id=None, order_date=None):
|
||||
invoices = Invoice.objects.select_for_update().filter(uuid__in=[i.uuid for i in invoices])
|
||||
with transaction.atomic():
|
||||
payment = cls.objects.create(
|
||||
regie=regie,
|
||||
amount=amount,
|
||||
payment_type=payment_type,
|
||||
order_id=order_id,
|
||||
order_date=order_date,
|
||||
)
|
||||
remaining_amount = amount
|
||||
for invoice in invoices.order_by('date_publication', 'created_at'):
|
||||
|
|
|
@ -231,16 +231,17 @@ class Pricing(models.Model):
|
|||
def get_payer_variables_keys(self):
|
||||
return [
|
||||
'payer_external_id',
|
||||
'payer_external_id_from_nameid',
|
||||
'payer_first_name',
|
||||
'payer_last_name',
|
||||
'payer_demat',
|
||||
'payer_direct_debit',
|
||||
]
|
||||
|
||||
def get_payer_external_id(self, request, original_context):
|
||||
def get_payer_external_id(self, request, original_context, key='payer_external_id'):
|
||||
context = RequestContext(request)
|
||||
context.push(original_context)
|
||||
tplt = self.payer_variables.get('payer_external_id') or ''
|
||||
tplt = self.payer_variables.get(key) or ''
|
||||
if not tplt:
|
||||
raise PayerError(details={'reason': 'empty-template'})
|
||||
try:
|
||||
|
@ -253,13 +254,16 @@ class Pricing(models.Model):
|
|||
except VariableDoesNotExist:
|
||||
raise PayerError(details={'reason': 'variable-error'})
|
||||
|
||||
def get_payer_external_id_from_nameid(self, request, original_context):
|
||||
return self.get_payer_external_id(request, original_context, key='payer_external_id_from_nameid')
|
||||
|
||||
def get_payer_data(self, request, original_context):
|
||||
result = {}
|
||||
context = RequestContext(request)
|
||||
context.push(original_context)
|
||||
bool_keys = ['payer_demat', 'payer_direct_debit']
|
||||
for key in self.get_payer_variables_keys():
|
||||
if key == 'payer_external_id':
|
||||
if key in ['payer_external_id', 'payer_external_id_from_nameid']:
|
||||
continue
|
||||
tplt = self.payer_variables.get(key) or ''
|
||||
if not tplt:
|
||||
|
@ -477,6 +481,10 @@ class AgendaPricing(models.Model):
|
|||
context['user_external_raw_id'] = user_external_id.split(':')[1]
|
||||
return self.pricing.get_payer_external_id(request, context)
|
||||
|
||||
def get_payer_external_id_from_nameid(self, request, nameid):
|
||||
context = {'nameid': nameid}
|
||||
return self.pricing.get_payer_external_id_from_nameid(request, context)
|
||||
|
||||
def get_payer_data(self, request, payer_external_id):
|
||||
context = {'payer_external_id': payer_external_id}
|
||||
if ':' in payer_external_id:
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import datetime
|
||||
import decimal
|
||||
import uuid
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
from django.utils.timezone import make_aware
|
||||
|
||||
from lingo.agendas.models import Agenda
|
||||
from lingo.invoicing.models import (
|
||||
Campaign,
|
||||
InjectedLine,
|
||||
|
@ -14,6 +17,7 @@ from lingo.invoicing.models import (
|
|||
Pool,
|
||||
Regie,
|
||||
)
|
||||
from lingo.pricing.models import AgendaPricing, Pricing
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
@ -37,6 +41,647 @@ def test_regies(app, user):
|
|||
]
|
||||
|
||||
|
||||
@mock.patch.object(AgendaPricing, 'get_payer_external_id_from_nameid', autospec=True)
|
||||
def test_list_invoices(mock_payer, app, user):
|
||||
app.get('/api/regie/foo/invoices/', status=403)
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
|
||||
app.get('/api/regie/foo/invoices/', status=404)
|
||||
|
||||
regie = Regie.objects.create(label='Foo')
|
||||
app.get('/api/regie/foo/invoices/', status=404)
|
||||
|
||||
resp = app.get('/api/regie/foo/invoices/', params={'NameID': 'foobar'})
|
||||
assert resp.json['err'] == 0
|
||||
assert resp.json['data'] == []
|
||||
|
||||
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.today(),
|
||||
date_issue=datetime.date(2022, 10, 31),
|
||||
date_debit=datetime.date(2022, 11, 15),
|
||||
)
|
||||
pool = Pool.objects.create(
|
||||
campaign=campaign,
|
||||
draft=False,
|
||||
)
|
||||
invoice = Invoice.objects.create(
|
||||
label='My invoice',
|
||||
date_publication=campaign.date_publication,
|
||||
date_payment_deadline=datetime.date.today(),
|
||||
date_issue=campaign.date_issue,
|
||||
regie=regie,
|
||||
pool=pool,
|
||||
payer_external_id='payer:1',
|
||||
)
|
||||
InvoiceLine.objects.create(
|
||||
event_date=datetime.date.today(),
|
||||
invoice=invoice,
|
||||
quantity=1,
|
||||
unit_amount=42,
|
||||
total_amount=42,
|
||||
status='success',
|
||||
pool=pool,
|
||||
)
|
||||
invoice.refresh_from_db()
|
||||
|
||||
# no agenda pricing configured, no payer from name id
|
||||
resp = app.get('/api/regie/foo/invoices/', params={'NameID': 'foobar'})
|
||||
assert resp.json['err'] == 0
|
||||
assert resp.json['data'] == []
|
||||
|
||||
pricing = Pricing.objects.create(label='Model')
|
||||
agenda1 = Agenda.objects.create(label='Foo Bar 1', regie=regie)
|
||||
agenda2 = Agenda.objects.create(label='Foo Bar 2', regie=regie)
|
||||
agenda3 = Agenda.objects.create(label='Foo Bar 3')
|
||||
|
||||
# agenda pricing configured, bot not for the regie
|
||||
agenda_pricing = AgendaPricing.objects.create(
|
||||
pricing=pricing,
|
||||
date_start=datetime.date(year=2021, month=9, day=1),
|
||||
date_end=datetime.date(year=2022, month=9, day=1),
|
||||
)
|
||||
agenda_pricing.agendas.add(agenda3)
|
||||
mock_payer.return_value = 'payer:1'
|
||||
resp = app.get('/api/regie/foo/invoices/', params={'NameID': 'foobar'})
|
||||
assert resp.json['err'] == 0
|
||||
assert resp.json['data'] == []
|
||||
assert mock_payer.call_args_list == []
|
||||
|
||||
# agenda pricing configured for the regie, but invoice remaining_amount is 0
|
||||
InvoiceLine.objects.all().delete()
|
||||
invoice.refresh_from_db()
|
||||
assert invoice.remaining_amount == 0
|
||||
agenda_pricing.agendas.add(agenda2)
|
||||
resp = app.get('/api/regie/foo/invoices/', params={'NameID': 'foobar'})
|
||||
assert resp.json['err'] == 0
|
||||
assert resp.json['data'] == []
|
||||
assert mock_payer.call_args_list == [mock.call(agenda_pricing, mock.ANY, 'foobar')]
|
||||
|
||||
# invoice with something to pay
|
||||
InvoiceLine.objects.create(
|
||||
event_date=datetime.date.today(),
|
||||
invoice=invoice,
|
||||
quantity=1,
|
||||
unit_amount=42,
|
||||
total_amount=42,
|
||||
status='success',
|
||||
pool=pool,
|
||||
)
|
||||
invoice.refresh_from_db()
|
||||
resp = app.get('/api/regie/foo/invoices/', params={'NameID': 'foobar'})
|
||||
assert resp.json['err'] == 0
|
||||
assert resp.json['data'] == [
|
||||
{
|
||||
'amount': 42,
|
||||
'amount_paid': 0,
|
||||
'created': datetime.date.today().isoformat(),
|
||||
'display_id': '',
|
||||
'has_pdf': False,
|
||||
'id': str(invoice.uuid),
|
||||
'label': 'My invoice',
|
||||
'online_payment': True,
|
||||
'paid': False,
|
||||
'pay_limit_date': datetime.date.today().isoformat(),
|
||||
'total_amount': 42,
|
||||
}
|
||||
]
|
||||
|
||||
# publication date is in the future
|
||||
invoice.date_publication = datetime.date.today() + datetime.timedelta(days=1)
|
||||
invoice.save()
|
||||
resp = app.get('/api/regie/foo/invoices/', params={'NameID': 'foobar'})
|
||||
assert resp.json['err'] == 0
|
||||
assert resp.json['data'] == []
|
||||
|
||||
# other regie
|
||||
other_regie = Regie.objects.create(label='Other Foo')
|
||||
invoice.date_publication = datetime.date.today()
|
||||
invoice.regie = other_regie
|
||||
invoice.save()
|
||||
resp = app.get('/api/regie/foo/invoices/', params={'NameID': 'foobar'})
|
||||
assert resp.json['err'] == 0
|
||||
assert resp.json['data'] == []
|
||||
|
||||
# part of amount was already paid
|
||||
invoice.regie = regie
|
||||
invoice.save()
|
||||
payment = Payment.objects.create(
|
||||
regie=regie,
|
||||
amount=1,
|
||||
payment_type='cash',
|
||||
)
|
||||
invoice_payment = InvoicePayment.objects.create(
|
||||
payment=payment,
|
||||
invoice=invoice,
|
||||
amount=1,
|
||||
)
|
||||
resp = app.get('/api/regie/foo/invoices/', params={'NameID': 'foobar'})
|
||||
assert resp.json['err'] == 0
|
||||
assert resp.json['data'] == [
|
||||
{
|
||||
'amount': 41,
|
||||
'amount_paid': 1,
|
||||
'created': datetime.date.today().isoformat(),
|
||||
'display_id': '',
|
||||
'has_pdf': False,
|
||||
'id': str(invoice.uuid),
|
||||
'label': 'My invoice',
|
||||
'online_payment': True,
|
||||
'paid': False,
|
||||
'pay_limit_date': datetime.date.today().isoformat(),
|
||||
'total_amount': 42,
|
||||
}
|
||||
]
|
||||
|
||||
# invoice is paid
|
||||
invoice_payment.amount = 42
|
||||
invoice_payment.save()
|
||||
resp = app.get('/api/regie/foo/invoices/', params={'NameID': 'foobar'})
|
||||
assert resp.json['err'] == 0
|
||||
assert resp.json['data'] == []
|
||||
|
||||
# more than one payer id
|
||||
invoice_payment.amount = 2
|
||||
invoice_payment.save()
|
||||
mock_payer.reset_mock()
|
||||
agenda_pricing2 = AgendaPricing.objects.create(
|
||||
pricing=pricing,
|
||||
date_start=datetime.date(year=2021, month=9, day=1),
|
||||
date_end=datetime.date(year=2022, month=9, day=1),
|
||||
)
|
||||
agenda_pricing2.agendas.add(agenda1)
|
||||
mock_payer.side_effect = ['payer:1', 'payer:other']
|
||||
resp = app.get('/api/regie/foo/invoices/', params={'NameID': 'foobar'})
|
||||
assert resp.json['err'] == 0
|
||||
assert resp.json['data'] == [
|
||||
{
|
||||
'amount': 40,
|
||||
'amount_paid': 2,
|
||||
'created': datetime.date.today().isoformat(),
|
||||
'display_id': '',
|
||||
'has_pdf': False,
|
||||
'id': str(invoice.uuid),
|
||||
'label': 'My invoice',
|
||||
'online_payment': True,
|
||||
'paid': False,
|
||||
'pay_limit_date': datetime.date.today().isoformat(),
|
||||
'total_amount': 42,
|
||||
}
|
||||
]
|
||||
assert mock_payer.call_args_list == [
|
||||
mock.call(agenda_pricing, mock.ANY, 'foobar'),
|
||||
mock.call(agenda_pricing2, mock.ANY, 'foobar'),
|
||||
]
|
||||
|
||||
# no matching payer id
|
||||
mock_payer.side_effect = None
|
||||
mock_payer.return_value = 'payer:unknown'
|
||||
resp = app.get('/api/regie/foo/invoices/', params={'NameID': 'foobar'})
|
||||
assert resp.json['err'] == 0
|
||||
assert resp.json['data'] == []
|
||||
|
||||
|
||||
@mock.patch.object(AgendaPricing, 'get_payer_external_id_from_nameid', autospec=True)
|
||||
def test_list_history_invoices(mock_payer, app, user):
|
||||
app.get('/api/regie/foo/invoices/history/', status=403)
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
|
||||
app.get('/api/regie/foo/invoices/history/', status=404)
|
||||
|
||||
regie = Regie.objects.create(label='Foo')
|
||||
app.get('/api/regie/foo/invoices/history/', status=404)
|
||||
|
||||
resp = app.get('/api/regie/foo/invoices/history/', params={'NameID': 'foobar'})
|
||||
assert resp.json['err'] == 0
|
||||
assert resp.json['data'] == []
|
||||
|
||||
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.today(),
|
||||
date_issue=datetime.date(2022, 10, 31),
|
||||
date_debit=datetime.date(2022, 11, 15),
|
||||
)
|
||||
pool = Pool.objects.create(
|
||||
campaign=campaign,
|
||||
draft=False,
|
||||
)
|
||||
invoice = Invoice.objects.create(
|
||||
label='My invoice',
|
||||
date_publication=campaign.date_publication,
|
||||
date_payment_deadline=datetime.date.today(),
|
||||
date_issue=campaign.date_issue,
|
||||
regie=regie,
|
||||
pool=pool,
|
||||
payer_external_id='payer:1',
|
||||
)
|
||||
InvoiceLine.objects.create(
|
||||
event_date=datetime.date.today(),
|
||||
invoice=invoice,
|
||||
quantity=1,
|
||||
unit_amount=42,
|
||||
total_amount=42,
|
||||
status='success',
|
||||
pool=pool,
|
||||
)
|
||||
invoice.refresh_from_db()
|
||||
payment = Payment.objects.create(
|
||||
regie=regie,
|
||||
amount=42,
|
||||
payment_type='cash',
|
||||
)
|
||||
invoice_payment = InvoicePayment.objects.create(
|
||||
payment=payment,
|
||||
invoice=invoice,
|
||||
amount=42,
|
||||
)
|
||||
|
||||
# no agenda pricing configured, no payer from name id
|
||||
resp = app.get('/api/regie/foo/invoices/history/', params={'NameID': 'foobar'})
|
||||
assert resp.json['err'] == 0
|
||||
assert resp.json['data'] == []
|
||||
|
||||
pricing = Pricing.objects.create(label='Model')
|
||||
agenda1 = Agenda.objects.create(label='Foo Bar 1', regie=regie)
|
||||
agenda2 = Agenda.objects.create(label='Foo Bar 2', regie=regie)
|
||||
agenda3 = Agenda.objects.create(label='Foo Bar 3')
|
||||
|
||||
# agenda pricing configured, bot not for the regie
|
||||
agenda_pricing = AgendaPricing.objects.create(
|
||||
pricing=pricing,
|
||||
date_start=datetime.date(year=2021, month=9, day=1),
|
||||
date_end=datetime.date(year=2022, month=9, day=1),
|
||||
)
|
||||
agenda_pricing.agendas.add(agenda3)
|
||||
mock_payer.return_value = 'payer:1'
|
||||
resp = app.get('/api/regie/foo/invoices/history/', params={'NameID': 'foobar'})
|
||||
assert resp.json['err'] == 0
|
||||
assert resp.json['data'] == []
|
||||
assert mock_payer.call_args_list == []
|
||||
|
||||
# agenda pricing configured for the regie, but invoice remaining_amount is not 0
|
||||
invoice_payment.amount = 2
|
||||
invoice_payment.save()
|
||||
invoice.refresh_from_db()
|
||||
assert invoice.remaining_amount != 0
|
||||
agenda_pricing.agendas.add(agenda2)
|
||||
resp = app.get('/api/regie/foo/invoices/history/', params={'NameID': 'foobar'})
|
||||
assert resp.json['err'] == 0
|
||||
assert resp.json['data'] == []
|
||||
assert mock_payer.call_args_list == [mock.call(agenda_pricing, mock.ANY, 'foobar')]
|
||||
|
||||
# invoice with nothing to pay
|
||||
invoice_payment.amount = 42
|
||||
invoice_payment.save()
|
||||
invoice.refresh_from_db()
|
||||
resp = app.get('/api/regie/foo/invoices/history/', params={'NameID': 'foobar'})
|
||||
assert resp.json['err'] == 0
|
||||
assert resp.json['data'] == [
|
||||
{
|
||||
'amount': 0,
|
||||
'amount_paid': 42,
|
||||
'created': datetime.date.today().isoformat(),
|
||||
'display_id': '',
|
||||
'has_pdf': False,
|
||||
'id': str(invoice.uuid),
|
||||
'label': 'My invoice',
|
||||
'online_payment': False,
|
||||
'paid': True,
|
||||
'pay_limit_date': '',
|
||||
'total_amount': 42,
|
||||
}
|
||||
]
|
||||
|
||||
# publication date is in the future
|
||||
invoice.date_publication = datetime.date.today() + datetime.timedelta(days=1)
|
||||
invoice.save()
|
||||
resp = app.get('/api/regie/foo/invoices/history/', params={'NameID': 'foobar'})
|
||||
assert resp.json['err'] == 0
|
||||
assert resp.json['data'] == []
|
||||
|
||||
# other regie
|
||||
other_regie = Regie.objects.create(label='Other Foo')
|
||||
invoice.date_publication = datetime.date.today()
|
||||
invoice.regie = other_regie
|
||||
invoice.save()
|
||||
resp = app.get('/api/regie/foo/invoices/history/', params={'NameID': 'foobar'})
|
||||
assert resp.json['err'] == 0
|
||||
assert resp.json['data'] == []
|
||||
|
||||
# more than one payer id
|
||||
invoice.regie = regie
|
||||
invoice.save()
|
||||
mock_payer.reset_mock()
|
||||
agenda_pricing2 = AgendaPricing.objects.create(
|
||||
pricing=pricing,
|
||||
date_start=datetime.date(year=2021, month=9, day=1),
|
||||
date_end=datetime.date(year=2022, month=9, day=1),
|
||||
)
|
||||
agenda_pricing2.agendas.add(agenda1)
|
||||
mock_payer.side_effect = ['payer:1', 'payer:other']
|
||||
resp = app.get('/api/regie/foo/invoices/history/', params={'NameID': 'foobar'})
|
||||
assert resp.json['err'] == 0
|
||||
assert resp.json['data'] == [
|
||||
{
|
||||
'amount': 0,
|
||||
'amount_paid': 42,
|
||||
'created': datetime.date.today().isoformat(),
|
||||
'display_id': '',
|
||||
'has_pdf': False,
|
||||
'id': str(invoice.uuid),
|
||||
'label': 'My invoice',
|
||||
'online_payment': False,
|
||||
'paid': True,
|
||||
'pay_limit_date': '',
|
||||
'total_amount': 42,
|
||||
}
|
||||
]
|
||||
assert mock_payer.call_args_list == [
|
||||
mock.call(agenda_pricing, mock.ANY, 'foobar'),
|
||||
mock.call(agenda_pricing2, mock.ANY, 'foobar'),
|
||||
]
|
||||
|
||||
# no matching payer id
|
||||
mock_payer.side_effect = None
|
||||
mock_payer.return_value = 'payer:unknown'
|
||||
resp = app.get('/api/regie/foo/invoices/history/', params={'NameID': 'foobar'})
|
||||
assert resp.json['err'] == 0
|
||||
assert resp.json['data'] == []
|
||||
|
||||
|
||||
@mock.patch.object(AgendaPricing, 'get_payer_external_id_from_nameid', autospec=True)
|
||||
def test_detail_invoice(mock_payer, app, user):
|
||||
app.get('/api/regie/foo/invoice/%s/' % str(uuid.uuid4()), status=403)
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
|
||||
app.get('/api/regie/foo/invoice/%s/' % str(uuid.uuid4()), status=404)
|
||||
|
||||
regie = Regie.objects.create(label='Foo')
|
||||
app.get('/api/regie/foo/invoice/%s/' % str(uuid.uuid4()), status=404)
|
||||
|
||||
resp = app.get('/api/regie/foo/invoice/%s/' % str(uuid.uuid4()), params={'NameID': 'foobar'}, status=404)
|
||||
|
||||
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.today(),
|
||||
date_issue=datetime.date(2022, 10, 31),
|
||||
date_debit=datetime.date(2022, 11, 15),
|
||||
)
|
||||
pool = Pool.objects.create(
|
||||
campaign=campaign,
|
||||
draft=False,
|
||||
)
|
||||
invoice = Invoice.objects.create(
|
||||
label='My invoice',
|
||||
date_publication=campaign.date_publication,
|
||||
date_payment_deadline=datetime.date.today(),
|
||||
date_issue=campaign.date_issue,
|
||||
regie=regie,
|
||||
pool=pool,
|
||||
payer_external_id='payer:1',
|
||||
)
|
||||
InvoiceLine.objects.create(
|
||||
event_date=datetime.date.today(),
|
||||
invoice=invoice,
|
||||
quantity=1,
|
||||
unit_amount=42,
|
||||
total_amount=42,
|
||||
status='success',
|
||||
pool=pool,
|
||||
)
|
||||
invoice.refresh_from_db()
|
||||
|
||||
# no agenda pricing configured, no payer from name id
|
||||
resp = app.get('/api/regie/foo/invoice/%s/' % str(invoice.uuid), params={'NameID': 'foobar'}, status=404)
|
||||
|
||||
pricing = Pricing.objects.create(label='Model')
|
||||
agenda1 = Agenda.objects.create(label='Foo Bar 1', regie=regie)
|
||||
agenda2 = Agenda.objects.create(label='Foo Bar 2', regie=regie)
|
||||
agenda3 = Agenda.objects.create(label='Foo Bar 3')
|
||||
|
||||
# agenda pricing configured, bot not for the regie
|
||||
agenda_pricing = AgendaPricing.objects.create(
|
||||
pricing=pricing,
|
||||
date_start=datetime.date(year=2021, month=9, day=1),
|
||||
date_end=datetime.date(year=2022, month=9, day=1),
|
||||
)
|
||||
agenda_pricing.agendas.add(agenda3)
|
||||
mock_payer.return_value = 'payer:1'
|
||||
resp = app.get('/api/regie/foo/invoice/%s/' % str(invoice.uuid), params={'NameID': 'foobar'}, status=404)
|
||||
assert mock_payer.call_args_list == []
|
||||
|
||||
# agenda pricing configured for the regie
|
||||
agenda_pricing.agendas.add(agenda2)
|
||||
resp = app.get('/api/regie/foo/invoice/%s/' % str(invoice.uuid), params={'NameID': 'foobar'})
|
||||
assert resp.json['err'] == 0
|
||||
assert resp.json['data'] == {
|
||||
'amount': 42,
|
||||
'amount_paid': 0,
|
||||
'created': datetime.date.today().isoformat(),
|
||||
'display_id': '',
|
||||
'has_pdf': False,
|
||||
'id': str(invoice.uuid),
|
||||
'label': 'My invoice',
|
||||
'online_payment': True,
|
||||
'paid': False,
|
||||
'pay_limit_date': datetime.date.today().isoformat(),
|
||||
'total_amount': 42,
|
||||
}
|
||||
|
||||
# publication date is in the future
|
||||
invoice.date_publication = datetime.date.today() + datetime.timedelta(days=1)
|
||||
invoice.save()
|
||||
resp = app.get('/api/regie/foo/invoice/%s/' % str(invoice.uuid), params={'NameID': 'foobar'}, status=404)
|
||||
|
||||
# other regie
|
||||
other_regie = Regie.objects.create(label='Other Foo')
|
||||
invoice.date_publication = datetime.date.today()
|
||||
invoice.regie = other_regie
|
||||
invoice.save()
|
||||
resp = app.get('/api/regie/foo/invoice/%s/' % str(invoice.uuid), params={'NameID': 'foobar'}, status=404)
|
||||
|
||||
# part of amount was already paid
|
||||
invoice.regie = regie
|
||||
invoice.save()
|
||||
payment = Payment.objects.create(
|
||||
regie=regie,
|
||||
amount=1,
|
||||
payment_type='cash',
|
||||
)
|
||||
invoice_payment = InvoicePayment.objects.create(
|
||||
payment=payment,
|
||||
invoice=invoice,
|
||||
amount=1,
|
||||
)
|
||||
resp = app.get('/api/regie/foo/invoice/%s/' % str(invoice.uuid), params={'NameID': 'foobar'})
|
||||
assert resp.json['err'] == 0
|
||||
assert resp.json['data'] == {
|
||||
'amount': 41,
|
||||
'amount_paid': 1,
|
||||
'created': datetime.date.today().isoformat(),
|
||||
'display_id': '',
|
||||
'has_pdf': False,
|
||||
'id': str(invoice.uuid),
|
||||
'label': 'My invoice',
|
||||
'online_payment': True,
|
||||
'paid': False,
|
||||
'pay_limit_date': datetime.date.today().isoformat(),
|
||||
'total_amount': 42,
|
||||
}
|
||||
|
||||
# invoice is paid
|
||||
invoice_payment.amount = 42
|
||||
invoice_payment.save()
|
||||
resp = app.get('/api/regie/foo/invoice/%s/' % str(invoice.uuid), params={'NameID': 'foobar'})
|
||||
assert resp.json['err'] == 0
|
||||
assert resp.json['data'] == {
|
||||
'amount': 0,
|
||||
'amount_paid': 42,
|
||||
'created': datetime.date.today().isoformat(),
|
||||
'display_id': '',
|
||||
'has_pdf': False,
|
||||
'id': str(invoice.uuid),
|
||||
'label': 'My invoice',
|
||||
'online_payment': False,
|
||||
'paid': True,
|
||||
'pay_limit_date': '',
|
||||
'total_amount': 42,
|
||||
}
|
||||
|
||||
# more than one payer id
|
||||
mock_payer.reset_mock()
|
||||
agenda_pricing2 = AgendaPricing.objects.create(
|
||||
pricing=pricing,
|
||||
date_start=datetime.date(year=2021, month=9, day=1),
|
||||
date_end=datetime.date(year=2022, month=9, day=1),
|
||||
)
|
||||
agenda_pricing2.agendas.add(agenda1)
|
||||
mock_payer.side_effect = ['payer:1', 'payer:other']
|
||||
resp = app.get('/api/regie/foo/invoice/%s/' % str(invoice.uuid), params={'NameID': 'foobar'})
|
||||
assert resp.json['err'] == 0
|
||||
assert mock_payer.call_args_list == [
|
||||
mock.call(agenda_pricing, mock.ANY, 'foobar'),
|
||||
mock.call(agenda_pricing2, mock.ANY, 'foobar'),
|
||||
]
|
||||
|
||||
# no matching payer id
|
||||
mock_payer.side_effect = None
|
||||
mock_payer.return_value = 'payer:unknown'
|
||||
resp = app.get('/api/regie/foo/invoice/%s/' % str(invoice.uuid), params={'NameID': 'foobar'}, status=404)
|
||||
|
||||
|
||||
def test_pay_invoice(app, user):
|
||||
app.post('/api/regie/foo/invoice/%s/pay/' % str(uuid.uuid4()), status=404)
|
||||
|
||||
regie = Regie.objects.create(label='Foo')
|
||||
app.post('/api/regie/foo/invoice/%s/pay/' % str(uuid.uuid4()), status=404)
|
||||
|
||||
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.today(),
|
||||
date_issue=datetime.date(2022, 10, 31),
|
||||
date_debit=datetime.date(2022, 11, 15),
|
||||
)
|
||||
pool = Pool.objects.create(
|
||||
campaign=campaign,
|
||||
draft=False,
|
||||
)
|
||||
invoice = Invoice.objects.create(
|
||||
label='My invoice',
|
||||
date_publication=campaign.date_publication,
|
||||
date_payment_deadline=datetime.date.today(),
|
||||
date_issue=campaign.date_issue,
|
||||
regie=regie,
|
||||
pool=pool,
|
||||
payer_external_id='payer:1',
|
||||
)
|
||||
app.post('/api/regie/foo/invoice/%s/pay/' % str(uuid.uuid4()), status=404)
|
||||
|
||||
InvoiceLine.objects.create(
|
||||
event_date=datetime.date.today(),
|
||||
invoice=invoice,
|
||||
quantity=1,
|
||||
unit_amount=42,
|
||||
total_amount=42,
|
||||
status='success',
|
||||
pool=pool,
|
||||
)
|
||||
invoice.refresh_from_db()
|
||||
|
||||
resp = app.post('/api/regie/foo/invoice/%s/pay/' % str(invoice.uuid))
|
||||
assert resp.json == {'data': True, 'err': 0}
|
||||
assert Payment.objects.count() == 1
|
||||
assert InvoicePayment.objects.count() == 1
|
||||
payment = Payment.objects.latest('pk')
|
||||
assert payment.regie == regie
|
||||
assert payment.amount == 42
|
||||
assert payment.payment_type == 'online'
|
||||
assert payment.order_id is None
|
||||
assert payment.order_date is None
|
||||
(invoice_payment,) = payment.invoicepayment_set.order_by('pk')
|
||||
assert invoice_payment.amount == 42
|
||||
assert invoice_payment.invoice == invoice
|
||||
invoice.refresh_from_db()
|
||||
assert invoice.remaining_amount == 0
|
||||
assert invoice.paid_amount == 42
|
||||
|
||||
InvoicePayment.objects.all().delete()
|
||||
Payment.objects.all().delete()
|
||||
resp = app.post(
|
||||
'/api/regie/foo/invoice/%s/pay/' % str(invoice.uuid),
|
||||
params={'transaction_id': 'foobar', 'transaction_date': 'foobaz'},
|
||||
)
|
||||
assert resp.json == {'data': True, 'err': 0}
|
||||
assert Payment.objects.count() == 1
|
||||
assert InvoicePayment.objects.count() == 1
|
||||
payment = Payment.objects.latest('pk')
|
||||
assert payment.regie == regie
|
||||
assert payment.amount == 42
|
||||
assert payment.payment_type == 'online'
|
||||
assert payment.order_id == 'foobar'
|
||||
assert payment.order_date is None
|
||||
(invoice_payment,) = payment.invoicepayment_set.order_by('pk')
|
||||
assert invoice_payment.amount == 42
|
||||
assert invoice_payment.invoice == invoice
|
||||
invoice.refresh_from_db()
|
||||
assert invoice.remaining_amount == 0
|
||||
assert invoice.paid_amount == 42
|
||||
|
||||
InvoicePayment.objects.all().delete()
|
||||
Payment.objects.all().delete()
|
||||
resp = app.post(
|
||||
'/api/regie/foo/invoice/%s/pay/' % str(invoice.uuid),
|
||||
params={'transaction_id': 'foobar', 'transaction_date': '2023-04-13T16:06:42'},
|
||||
)
|
||||
assert resp.json == {'data': True, 'err': 0}
|
||||
assert Payment.objects.count() == 1
|
||||
assert InvoicePayment.objects.count() == 1
|
||||
payment = Payment.objects.latest('pk')
|
||||
assert payment.regie == regie
|
||||
assert payment.amount == 42
|
||||
assert payment.payment_type == 'online'
|
||||
assert payment.order_id == 'foobar'
|
||||
assert payment.order_date == make_aware(datetime.datetime(2023, 4, 13, 18, 6, 42))
|
||||
(invoice_payment,) = payment.invoicepayment_set.order_by('pk')
|
||||
assert invoice_payment.amount == 42
|
||||
assert invoice_payment.invoice == invoice
|
||||
invoice.refresh_from_db()
|
||||
assert invoice.remaining_amount == 0
|
||||
assert invoice.paid_amount == 42
|
||||
|
||||
|
||||
def test_add_injected_line(app, user):
|
||||
app.post('/api/regie/foo/injected-lines/', status=403)
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
|
@ -340,6 +985,8 @@ def test_add_payment(app, user):
|
|||
assert payment.regie == regie
|
||||
assert payment.amount == 10
|
||||
assert payment.payment_type == 'cash'
|
||||
assert payment.order_id is None
|
||||
assert payment.order_date is None
|
||||
(invoice_payment,) = payment.invoicepayment_set.order_by('pk')
|
||||
assert invoice_payment.amount == 10
|
||||
assert invoice_payment.invoice == invoice11
|
||||
|
@ -362,6 +1009,8 @@ def test_add_payment(app, user):
|
|||
assert payment.regie == regie
|
||||
assert payment.amount == 10
|
||||
assert payment.payment_type == 'check'
|
||||
assert payment.order_id is None
|
||||
assert payment.order_date is None
|
||||
(invoice_payment,) = payment.invoicepayment_set.order_by('pk')
|
||||
assert invoice_payment.amount == 10
|
||||
assert invoice_payment.invoice == invoice11
|
||||
|
@ -384,6 +1033,8 @@ def test_add_payment(app, user):
|
|||
assert payment.regie == regie
|
||||
assert payment.amount == decimal.Decimal('22.01')
|
||||
assert payment.payment_type == 'check'
|
||||
assert payment.order_id is None
|
||||
assert payment.order_date is None
|
||||
invoice_payment1, invoice_payment2 = payment.invoicepayment_set.order_by('pk')
|
||||
assert invoice_payment1.amount == 22
|
||||
assert invoice_payment1.invoice == invoice11 # older invoice first
|
||||
|
@ -418,6 +1069,8 @@ def test_add_payment(app, user):
|
|||
assert payment.regie == regie
|
||||
assert payment.amount == decimal.Decimal('41.99')
|
||||
assert payment.payment_type == 'check'
|
||||
assert payment.order_id is None
|
||||
assert payment.order_date is None
|
||||
(invoice_payment,) = payment.invoicepayment_set.order_by('pk')
|
||||
assert invoice_payment.amount == decimal.Decimal('41.99')
|
||||
assert invoice_payment.invoice == invoice12
|
||||
|
|
|
@ -175,28 +175,32 @@ def test_pricing_edit_payer_variables(app, admin_user):
|
|||
resp = app.get('/manage/pricing/model/%s/' % pricing.pk)
|
||||
assert '<label>Payer variables:</label>' in resp.text
|
||||
assert '<dt><b>payer_external_id:</b></dt>' in resp
|
||||
assert '<dt><b>payer_external_id_from_nameid:</b></dt>' in resp
|
||||
assert '<dt><b>payer_first_name:</b></dt>' in resp
|
||||
assert '<dt><b>payer_last_name:</b></dt>' in resp
|
||||
assert '<dt><b>payer_demat:</b></dt>' in resp
|
||||
assert '<dt><b>payer_direct_debit:</b></dt>' in resp
|
||||
assert resp.text.count('<dd><pre></pre></dd>') == 5
|
||||
assert resp.text.count('<dd><pre></pre></dd>') == 6
|
||||
resp = resp.click(href='/manage/pricing/model/%s/payer/' % pricing.pk)
|
||||
assert resp.form['form-TOTAL_FORMS'].value == '5'
|
||||
assert resp.form['form-TOTAL_FORMS'].value == '6'
|
||||
assert resp.form['form-0-key'].value == 'payer_external_id'
|
||||
assert resp.form['form-0-value'].value == ''
|
||||
assert resp.form['form-1-key'].value == 'payer_first_name'
|
||||
assert resp.form['form-1-key'].value == 'payer_external_id_from_nameid'
|
||||
assert resp.form['form-1-value'].value == ''
|
||||
assert resp.form['form-2-key'].value == 'payer_last_name'
|
||||
assert resp.form['form-2-key'].value == 'payer_first_name'
|
||||
assert resp.form['form-2-value'].value == ''
|
||||
assert resp.form['form-3-key'].value == 'payer_demat'
|
||||
assert resp.form['form-3-key'].value == 'payer_last_name'
|
||||
assert resp.form['form-3-value'].value == ''
|
||||
assert resp.form['form-4-key'].value == 'payer_direct_debit'
|
||||
assert resp.form['form-4-key'].value == 'payer_demat'
|
||||
assert resp.form['form-4-value'].value == ''
|
||||
assert resp.form['form-5-key'].value == 'payer_direct_debit'
|
||||
assert resp.form['form-5-value'].value == ''
|
||||
resp.form['form-0-value'] = 'payer:42'
|
||||
resp = resp.form.submit().follow()
|
||||
pricing.refresh_from_db()
|
||||
assert pricing.payer_variables == {
|
||||
'payer_external_id': 'payer:42',
|
||||
'payer_external_id_from_nameid': '',
|
||||
'payer_first_name': '',
|
||||
'payer_last_name': '',
|
||||
'payer_demat': '',
|
||||
|
|
|
@ -1746,6 +1746,54 @@ def test_get_payer_external_id(mock_send, context, nocache):
|
|||
assert 'filter-foo=42&' in mock_send.call_args_list[0][0][0].url
|
||||
|
||||
|
||||
@mock.patch('requests.Session.send', side_effect=mocked_requests_send)
|
||||
def test_get_payer_external_id_from_nameid(mock_send, context, nocache):
|
||||
pricing = Pricing.objects.create(label='Foo bar')
|
||||
agenda_pricing = AgendaPricing.objects.create(
|
||||
pricing=pricing,
|
||||
date_start=datetime.date(year=2021, month=9, day=1),
|
||||
date_end=datetime.date(year=2021, month=10, day=1),
|
||||
)
|
||||
|
||||
values = [
|
||||
('bar', 'bar'),
|
||||
('{{ 40|add:2 }}', '42'),
|
||||
('{{ cards|objects:"foo"|first|get:"id" }}', '1'),
|
||||
]
|
||||
for value, result in values:
|
||||
pricing.payer_variables = {
|
||||
'payer_external_id_from_nameid': value,
|
||||
}
|
||||
pricing.save()
|
||||
assert (
|
||||
agenda_pricing.get_payer_external_id_from_nameid(request=context['request'], nameid='foobar')
|
||||
== result
|
||||
)
|
||||
|
||||
values = [
|
||||
('', 'empty-template'),
|
||||
('{{ "" }}', 'empty-result'),
|
||||
('{% for %}', 'syntax-error'),
|
||||
('{{ "foo"|add:user.email }}', 'variable-error'),
|
||||
]
|
||||
for value, error in values:
|
||||
pricing.payer_variables = {
|
||||
'payer_external_id_from_nameid': value,
|
||||
}
|
||||
pricing.save()
|
||||
with pytest.raises(PayerError) as e:
|
||||
agenda_pricing.get_payer_external_id_from_nameid(request=context['request'], nameid='foobar')
|
||||
assert e.value.details == {'reason': error}
|
||||
|
||||
pricing.payer_variables = {
|
||||
'payer_external_id_from_nameid': '{{ cards|objects:"qf"|filter_by_user:nameid|first|get:"id" }}',
|
||||
}
|
||||
pricing.save()
|
||||
mock_send.reset_mock()
|
||||
agenda_pricing.get_payer_external_id_from_nameid(request=context['request'], nameid='foobar')
|
||||
assert 'filter-user-uuid=foobar&' in mock_send.call_args_list[0][0][0].url
|
||||
|
||||
|
||||
@mock.patch('requests.Session.send', side_effect=mocked_requests_send)
|
||||
def test_get_payer_data(mock_send, context, nocache):
|
||||
pricing = Pricing.objects.create(label='Foo bar')
|
||||
|
|
je n'ai pas fait le endpoint pdf, je le rajouterai quand on saura éditer des pdf