Projet

Général

Profil

0001-caluire-axel-add-pay_invoice-endpoint-53963.patch

Nicolas Roche, 21 mai 2021 19:12

Télécharger (10,5 ko)

Voir les différences:

Subject: [PATCH] caluire-axel: add pay_invoice endpoint (#53963)

 passerelle/contrib/caluire_axel/models.py  | 50 ++++++++++-
 passerelle/contrib/caluire_axel/schemas.py | 11 +++
 tests/test_caluire_axel.py                 | 97 ++++++++++++++++++++++
 3 files changed, 157 insertions(+), 1 deletion(-)
passerelle/contrib/caluire_axel/models.py
18 18
import datetime
19 19

  
20 20
from django.core.cache import cache
21 21
from django.db import models
22 22
from django.http import HttpResponse
23 23
from django.utils.translation import ugettext_lazy as _
24 24

  
25 25
from passerelle.base.models import BaseResource
26
from passerelle.compat import json_loads
26 27
from passerelle.contrib.utils import axel
27 28
from passerelle.utils.api import endpoint
28 29
from passerelle.utils.jsonresponse import APIError
29 30

  
30 31
from . import schemas, utils
31 32

  
32 33

  
33 34
class CaluireAxel(BaseResource):
......
421 422
                self,
422 423
                {
423 424
                    'PORTAIL': {
424 425
                        'GETFACTURESAPAYER': {
425 426
                            'IDENTFAMILLE': family_id,
426 427
                            'IDENTREGIEFACT': regie_id,
427 428
                        }
428 429
                    }
429
                }
430
                },
430 431
            )
431 432
        except axel.AxelError as e:
432 433
            raise APIError(
433 434
                'Axel error: %s' % e,
434 435
                err_code='error',
435 436
                data={'xml_request': e.xml_request, 'xml_response': e.xml_response},
436 437
            )
437 438

  
......
618 619
        )
619 620
        if not b64content:
620 621
            raise APIError('PDF error', err_code='error', http_status=404)
621 622
        response = HttpResponse(content_type='application/pdf')
622 623
        response['Content-Disposition'] = 'attachment; filename="%s.pdf"' % invoice_id
623 624
        response.write(b64content)
624 625
        return response
625 626

  
627
    @endpoint(
628
        display_category=_('Invoices'),
629
        display_order=5,
630
        name='regie',
631
        methods=['post'],
632
        perm='can_access',
633
        pattern=r'^(?P<regie_id>[\w-]+)/invoice/(?P<invoice_id>\w+-\d+)/pay/?$',
634
        example_pattern='{regie_id}/invoice/{invoice_id}/pay',
635
        description=_('Notify an invoice as paid'),
636
        parameters={
637
            'regie_id': {'description': _('Regie identifier'), 'example_value': 'ENF'},
638
            'invoice_id': {'description': _('Invoice identifier'), 'example_value': 'IDFAM-42'},
639
        },
640
        post={
641
            'request_body': {
642
                'schema': {
643
                    'application/json': schemas.PAYMENT_SCHEMA,
644
                }
645
            }
646
        },
647
    )
648
    def pay_invoice(self, request, regie_id, invoice_id, **kwargs):
649
        data = json_loads(request.body)
650
        family_id, invoice_id = invoice_id.split('-')
651

  
652
        invoice = self.get_invoice(regie_id=regie_id, family_id=family_id, invoice_id=invoice_id)
653
        if invoice is None:
654
            raise APIError('Invoice not found', err_code='not-found')
655

  
656
        transaction_amount = invoice['amount']
657
        payment_mode_id = data['payment_mode_id']
658
        post_data = {
659
            'IDFACTURE': int(invoice_id),
660
            'IDENTREGIEENC': regie_id,
661
            'MONTANT': transaction_amount,
662
            'IDENTMODEREGLEMENT': payment_mode_id,
663
        }
664
        try:
665
            schemas.set_paiement(self, {'PORTAIL': {'SETPAIEMENT': post_data}})
666
        except axel.AxelError as e:
667
            raise APIError(
668
                'Axel error: %s' % e,
669
                err_code='error',
670
                data={'xml_request': e.xml_request, 'xml_response': e.xml_response},
671
            )
672
        return {'data': True}
673

  
626 674

  
627 675
class Link(models.Model):
628 676
    resource = models.ForeignKey(CaluireAxel, on_delete=models.CASCADE)
629 677
    name_id = models.CharField(blank=False, max_length=256)
630 678
    family_id = models.CharField(blank=False, max_length=128)
631 679
    person_id = models.CharField(blank=False, max_length=128)
632 680

  
633 681
    class Meta:
passerelle/contrib/caluire_axel/schemas.py
76 76
get_famille_individus = Operation('GetFamilleIndividus')
77 77
get_individu = Operation('GetIndividu')
78 78
get_list_ecole = Operation('GetListEcole')
79 79
get_list_activites = Operation('GetListActivites')
80 80
create_inscription_activite = Operation('CreateInscriptionActivite')
81 81
get_factures_a_payer = Operation('GetFacturesaPayer')
82 82
get_list_factures = Operation('GetListFactures')
83 83
get_pdf_facture = Operation('GetPdfFacture')
84
set_paiement = Operation('SetPaiement')
84 85

  
85 86

  
86 87
LINK_SCHEMA = copy.deepcopy(
87 88
    find_individus.request_schema['properties']['PORTAIL']['properties']['FINDINDIVIDU']
88 89
)
89 90
for key in ['NAISSANCE', 'CODEPOSTAL', 'VILLE', 'TEL', 'MAIL']:
90 91
    LINK_SCHEMA['properties'].pop(key)
91 92
    LINK_SCHEMA['required'].remove(key)
......
111 112
    },
112 113
    'required': [
113 114
        'child_id',
114 115
        'activity_id',
115 116
        'registration_start_date',
116 117
        'registration_end_date',
117 118
    ],
118 119
}
120

  
121
PAYMENT_SCHEMA = {
122
    'type': 'object',
123
    'properties': {
124
        'payment_mode_id': {
125
            'type': 'string',
126
        },
127
    },
128
    'required': ['payment_mode_id'],
129
}
tests/test_caluire_axel.py
11 11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12 12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 13
# GNU Affero General Public License for more details.
14 14
#
15 15
# You should have received a copy of the GNU Affero General Public License
16 16
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 17

  
18 18
import copy
19
import decimal
19 20
import os
20 21
import xml.etree.ElementTree as ET
21 22
from contextlib import contextmanager
22 23

  
23 24
import mock
24 25
import pytest
25 26
import utils
26 27
import xmlschema
......
1237 1238
            app.get('/caluire-axel/test/regie/MAREGIE/invoice/XXX-42/pdf?NameID=yyy')
1238 1239
    assert invoice.call_args_list[0][1]['historical'] is False
1239 1240

  
1240 1241
    with mock.patch('passerelle.contrib.caluire_axel.models.CaluireAxel.get_invoice') as invoice:
1241 1242
        invoice.return_value = {'has_pdf': True, 'display_id': '42'}
1242 1243
        with mock_getdata(pdf_content, 'GetPdfFacture'):
1243 1244
            app.get('/caluire-axel/test/regie/MAREGIE/invoice/historical-XXX-42/pdf?NameID=yyy')
1244 1245
    assert invoice.call_args_list[0][1]['historical'] is True
1246

  
1247

  
1248
def test_pay_invoice_endpoint_axel_error(app, resource):
1249
    payload = {
1250
        'payment_mode_id': '',
1251
    }
1252
    Link.objects.create(resource=resource, name_id='yyy', family_id='XXX', person_id='42')
1253
    with mock.patch('passerelle.contrib.caluire_axel.schemas.get_factures_a_payer') as operation:
1254
        operation.side_effect = AxelError('FooBar')
1255
        resp = app.post_json('/caluire-axel/test/regie/MAREGIE/invoice/XXX-42/pay?NameID=yyy', params=payload)
1256
    assert resp.json['err_desc'] == "Axel error: FooBar"
1257
    assert resp.json['err'] == 'error'
1258

  
1259
    filepath = os.path.join(os.path.dirname(__file__), 'data/caluire_axel/invoices.xml')
1260
    with open(filepath) as xml:
1261
        content = (
1262
            '''<PORTAIL>
1263
  <GETFACTURESAPAYER>
1264
    %s
1265
  </GETFACTURESAPAYER>
1266
</PORTAIL>'''
1267
            % xml.read()
1268
        )
1269
    with mock_getdata(content, 'GetFacturesaPayer'):
1270
        with mock.patch('passerelle.contrib.caluire_axel.schemas.set_paiement') as operation:
1271
            operation.side_effect = AxelError('FooBar')
1272
            resp = app.post_json(
1273
                '/caluire-axel/test/regie/MAREGIE/invoice/XXX-42/pay?NameID=yyy', params=payload
1274
            )
1275
    assert resp.json['err_desc'] == "Axel error: FooBar"
1276
    assert resp.json['err'] == 'error'
1277

  
1278

  
1279
def test_pay_invoice_endpoint_bad_request(app, resource):
1280
    resp = app.post_json(
1281
        '/caluire-axel/test/regie/MAREGIE/invoice/XXX-42/pay?NameID=yyy', params={}, status=400
1282
    )
1283
    assert resp.json['err_desc'] == "'payment_mode_id' is a required property"
1284

  
1285

  
1286
def test_pay_invoice_endpoint_no_result(app, resource):
1287
    payload = {
1288
        'payment_mode_id': '',
1289
    }
1290
    filepath = os.path.join(os.path.dirname(__file__), 'data/caluire_axel/invoices.xml')
1291
    with open(filepath) as xml:
1292
        content = (
1293
            '''<PORTAIL>
1294
  <GETFACTURESAPAYER>
1295
    %s
1296
  </GETFACTURESAPAYER>
1297
</PORTAIL>'''
1298
            % xml.read()
1299
        )
1300
    with mock_getdata(content, 'GetFacturesaPayer'):
1301
        resp = app.post_json('/caluire-axel/test/regie/MAREGIE/invoice/XXX-35/pay?NameID=yyy', params=payload)
1302
    assert resp.json['err_desc'] == "Invoice not found"
1303
    assert resp.json['err'] == 'not-found'
1304
    with mock_getdata(content, 'GetFacturesaPayer'):
1305
        resp = app.post_json('/caluire-axel/test/regie/MAREGIE/invoice/XXX-44/pay?NameID=yyy', params=payload)
1306
    assert resp.json['err_desc'] == "Invoice not found"
1307
    assert resp.json['err'] == 'not-found'
1308

  
1309

  
1310
def test_pay_invoice_endpoint(app, resource):
1311
    payload = {
1312
        'payment_mode_id': '',
1313
    }
1314
    Link.objects.create(resource=resource, name_id='yyy', family_id='XXX', person_id='42')
1315
    filepath = os.path.join(os.path.dirname(__file__), 'data/caluire_axel/invoices.xml')
1316
    with open(filepath) as xml:
1317
        content = (
1318
            '''<PORTAIL>
1319
  <GETFACTURESAPAYER>
1320
    %s
1321
  </GETFACTURESAPAYER>
1322
</PORTAIL>'''
1323
            % xml.read()
1324
        )
1325
    with mock_getdata(content, 'GetFacturesaPayer'):
1326
        with mock.patch('passerelle.contrib.caluire_axel.schemas.set_paiement') as operation:
1327
            resp = app.post_json(
1328
                '/caluire-axel/test/regie/MAREGIE/invoice/XXX-42/pay?NameID=yyy', params=payload
1329
            )
1330
    assert resp.json['err'] == 0
1331
    assert resp.json['data'] is True
1332
    assert operation.call_args_list[0][0][1] == {
1333
        'PORTAIL': {
1334
            'SETPAIEMENT': {
1335
                'IDFACTURE': 42,
1336
                'IDENTREGIEENC': 'MAREGIE',
1337
                'MONTANT': decimal.Decimal('4.94'),
1338
                'IDENTMODEREGLEMENT': '',
1339
            }
1340
        }
1341
    }
1245
-