Facturation: ajouter les endpoints nécessaires au bon fonctionnement des cellules facture de combo (#76495) #52

Merged
lguerin merged 3 commits from wip/76495-api-invoices-for-combo-cells into main 2023-04-24 09:28:50 +02:00
9 changed files with 907 additions and 11 deletions

View File

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

je n'ai pas fait le endpoint pdf, je le rajouterai quand on saura éditer des pdf

je n'ai pas fait le endpoint pdf, je le rajouterai quand on saura éditer des pdf
re_path(
r'^regie/(?P<regie_identifier>[\w-]+)/injected-lines/$',
views.injected_lines,

View File

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

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.

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

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.

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.

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',)
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',)

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).

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&timestamp=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&timestamp=2023-04-13T09%3A18%3A51Z&nonce=2fb8367cbc9c49d7132666332819b858&signature=PSGecbF0U4H5zQEFRK5tx1QjGAx5QvuJSvRWVRkIS4w%3D (401)
b'{"err":1,"err_desc":"user-not-found"}'
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&timestamp=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&timestamp=2023-04-13T09%3A18%3A51Z&nonce=2fb8367cbc9c49d7132666332819b858&signature=PSGecbF0U4H5zQEFRK5tx1QjGAx5QvuJSvRWVRkIS4w%3D (401) b'{"err":1,"err_desc":"user-not-found"}' ```

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.

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.

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

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

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)...

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)...

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.
> 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. ```

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.

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

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

View File

@ -44,6 +44,7 @@ class Migration(migrations.Migration):
('cash', 'Cash'),
('check', 'Check'),
('directdebit', 'Direct debit'),
('online', 'Online'),
],
max_length=20,
),

View File

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

View File

@ -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'):

View File

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

View File

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

View File

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

View File

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