0001-caluire-axel-add-pay_invoice-endpoint-53963.patch
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 |
- |