0004-caluire-axel-add-invoice_pdf-endpoint-53884.patch
passerelle/contrib/caluire_axel/models.py | ||
---|---|---|
9 | 9 |
# This program is distributed in the hope that it will be useful, |
10 | 10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | 11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | 12 |
# GNU Affero General Public License for more details. |
13 | 13 |
# |
14 | 14 |
# You should have received a copy of the GNU Affero General Public License |
15 | 15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | 16 | |
17 |
import base64 |
|
17 | 18 |
import datetime |
18 | 19 | |
19 | 20 |
from django.core.cache import cache |
20 | 21 |
from django.db import models |
22 |
from django.http import HttpResponse |
|
21 | 23 |
from django.utils.translation import ugettext_lazy as _ |
22 | 24 | |
23 | 25 |
from passerelle.base.models import BaseResource |
24 | 26 |
from passerelle.contrib.utils import axel |
25 | 27 |
from passerelle.utils.api import endpoint |
26 | 28 |
from passerelle.utils.jsonresponse import APIError |
27 | 29 | |
28 | 30 |
from . import schemas, utils |
... | ... | |
431 | 433 |
historical=historical, |
432 | 434 |
nb_mounts_limit=nb_mounts_limit, |
433 | 435 |
) |
434 | 436 |
if invoice is None: |
435 | 437 |
raise APIError('Invoice not found', err_code='not-found') |
436 | 438 | |
437 | 439 |
return {'data': invoice} |
438 | 440 | |
441 |
@endpoint( |
|
442 |
display_category=_('Invoices'), |
|
443 |
display_order=4, |
|
444 |
name='regie', |
|
445 |
perm='can_access', |
|
446 |
pattern=r'^(?P<regie_id>[\w-]+)/invoice/(?P<invoice_id>(historical-)?\w+-\d+)/pdf/?$', |
|
447 |
example_pattern='{regie_id}/invoice/{invoice_id}/pdf', |
|
448 |
description=_('Get invoice as a PDF file'), |
|
449 |
parameters={ |
|
450 |
'NameID': {'description': _('Publik ID')}, |
|
451 |
'regie_id': {'description': _('Regie identifier'), 'example_value': '42-PERISCOL'}, |
|
452 |
'invoice_id': {'description': _('Invoice identifier'), 'example_value': 'DUI-42'}, |
|
453 |
'nb_mounts_limit': {'description': _('Number of months of history'), 'example_value': '12'}, |
|
454 |
}, |
|
455 |
) |
|
456 |
def invoice_pdf(self, request, regie_id, invoice_id, NameID, nb_mounts_limit='12'): |
|
457 |
# check that invoice is related to current user |
|
458 |
real_invoice_id = invoice_id.split('-')[-1] |
|
459 |
historical = invoice_id.startswith('historical-') |
|
460 |
try: |
|
461 |
invoice = self.get_invoice( |
|
462 |
regie_id=regie_id, |
|
463 |
invoice_id=real_invoice_id, |
|
464 |
name_id=NameID, |
|
465 |
historical=historical, |
|
466 |
nb_mounts_limit=nb_mounts_limit, |
|
467 |
) |
|
468 |
except APIError as e: |
|
469 |
e.http_status = 404 |
|
470 |
raise |
|
471 |
if invoice is None: |
|
472 |
raise APIError('Invoice not found', err_code='not-found', http_status=404) |
|
473 |
# check that PDF is available |
|
474 |
if not invoice['has_pdf']: |
|
475 |
raise APIError('PDF not available', err_code='not-available', http_status=404) |
|
476 | ||
477 |
try: |
|
478 |
result = schemas.get_pdf_facture( |
|
479 |
self, {'PORTAIL': {'GETPDFFACTURE': {'IDFACTURE': int(invoice['display_id'])}}} |
|
480 |
) |
|
481 |
except axel.AxelError as e: |
|
482 |
raise APIError( |
|
483 |
'Axel error: %s' % e, |
|
484 |
err_code='error', |
|
485 |
http_status=404, |
|
486 |
data={'xml_request': e.xml_request, 'xml_response': e.xml_response}, |
|
487 |
) |
|
488 |
b64content = base64.b64decode( |
|
489 |
result.json_response['DATA']['PORTAIL']['GETPDFFACTURE']['FACTUREPDF'] or '' |
|
490 |
) |
|
491 |
if not b64content: |
|
492 |
raise APIError('PDF error', err_code='error', http_status=404) |
|
493 |
response = HttpResponse(content_type='application/pdf') |
|
494 |
response['Content-Disposition'] = 'attachment; filename="%s.pdf"' % invoice_id |
|
495 |
response.write(b64content) |
|
496 |
return response |
|
497 | ||
439 | 498 | |
440 | 499 |
class Link(models.Model): |
441 | 500 |
resource = models.ForeignKey(CaluireAxel, on_delete=models.CASCADE) |
442 | 501 |
name_id = models.CharField(blank=False, max_length=256) |
443 | 502 |
family_id = models.CharField(blank=False, max_length=128) |
444 | 503 |
person_id = models.CharField(blank=False, max_length=128) |
445 | 504 | |
446 | 505 |
class Meta: |
passerelle/contrib/caluire_axel/schemas.py | ||
---|---|---|
73 | 73 | |
74 | 74 | |
75 | 75 |
find_individus = Operation('FindIndividus') |
76 | 76 |
get_famille_individus = Operation('GetFamilleIndividus') |
77 | 77 |
get_individu = Operation('GetIndividu') |
78 | 78 |
get_list_ecole = Operation('GetListEcole') |
79 | 79 |
get_factures_a_payer = Operation('GetFacturesaPayer') |
80 | 80 |
get_list_factures = Operation('GetListFactures') |
81 |
get_pdf_facture = Operation('GetPdfFacture') |
|
81 | 82 | |
82 | 83 | |
83 | 84 |
LINK_SCHEMA = copy.deepcopy( |
84 | 85 |
find_individus.request_schema['properties']['PORTAIL']['properties']['FINDINDIVIDU'] |
85 | 86 |
) |
86 | 87 |
for key in ['NAISSANCE', 'CODEPOSTAL', 'VILLE', 'TEL', 'MAIL']: |
87 | 88 |
LINK_SCHEMA['properties'].pop(key) |
88 | 89 |
LINK_SCHEMA['required'].remove(key) |
passerelle/contrib/caluire_axel/xsd/Q_GetPdfFacture.xsd | ||
---|---|---|
1 |
<?xml version="1.0" encoding="utf-8" ?> |
|
2 |
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"> |
|
3 |
|
|
4 |
<xsd:complexType name="GETPDFFACTUREType"> |
|
5 |
<xsd:sequence> |
|
6 |
<xsd:element ref="IDFACTURE" /> |
|
7 |
</xsd:sequence> |
|
8 |
</xsd:complexType> |
|
9 |
|
|
10 |
<xsd:complexType name="PORTAILType"> |
|
11 |
<xsd:sequence> |
|
12 |
<xsd:element ref="GETPDFFACTURE" /> |
|
13 |
</xsd:sequence> |
|
14 |
</xsd:complexType> |
|
15 |
|
|
16 |
<xsd:element name="IDFACTURE" type="xsd:positiveInteger"/> |
|
17 |
<xsd:element name="GETPDFFACTURE" type="GETPDFFACTUREType"/> |
|
18 |
<xsd:element name="PORTAIL" type="PORTAILType"/> |
|
19 |
|
|
20 |
</xsd:schema> |
passerelle/contrib/caluire_axel/xsd/R_GetPdfFacture.xsd | ||
---|---|---|
1 |
<?xml version="1.0" encoding="utf-8" ?> |
|
2 |
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"> |
|
3 |
|
|
4 |
<xsd:redefine schemaLocation="./R_ShemaResultat.xsd"> |
|
5 |
<xsd:simpleType name="TYPEType"> |
|
6 |
<xsd:restriction base="TYPEType"> |
|
7 |
<xsd:enumeration value="GetPdfFacture" /> |
|
8 |
</xsd:restriction> |
|
9 |
</xsd:simpleType> |
|
10 |
|
|
11 |
<xsd:complexType name="PORTAILType"> |
|
12 |
<xsd:complexContent> |
|
13 |
<xsd:extension base="PORTAILType"> |
|
14 |
<xsd:sequence> |
|
15 |
<xsd:element ref="GETPDFFACTURE" minOccurs="0" maxOccurs="1"/> |
|
16 |
</xsd:sequence> |
|
17 |
</xsd:extension> |
|
18 |
</xsd:complexContent> |
|
19 |
</xsd:complexType> |
|
20 |
</xsd:redefine> |
|
21 |
|
|
22 |
<xsd:complexType name="GETPDFFACTUREType"> |
|
23 |
<xsd:sequence> |
|
24 |
<xsd:element ref="CODE"/> |
|
25 |
<xsd:element ref="FACTUREPDF"/> |
|
26 |
</xsd:sequence> |
|
27 |
</xsd:complexType> |
|
28 |
|
|
29 |
<xsd:element name="CODE" type="xsd:integer"/> |
|
30 |
<xsd:element name="FACTUREPDF" type="xsd:string"/> |
|
31 |
|
|
32 |
<xsd:element name="GETPDFFACTURE" type="GETPDFFACTUREType"/> |
|
33 |
|
|
34 |
</xsd:schema> |
tests/test_caluire_axel.py | ||
---|---|---|
1020 | 1020 |
'ENCAISSE': '40.00', |
1021 | 1021 |
'FACTURATION': 'PRESTATIONS PERISCOLAIRES SEPTEMBRE-OCTOBRE 2019', |
1022 | 1022 |
'DATEECHEANCE': '2019-12-04', |
1023 | 1023 |
'DATEEMISSION': '2019-11-12', |
1024 | 1024 |
'EXISTEPDF': True, |
1025 | 1025 |
}, |
1026 | 1026 |
}, |
1027 | 1027 |
} |
1028 | ||
1029 | ||
1030 |
def test_invoice_pdf_endpoint_axel_error(app, resource): |
|
1031 |
Link.objects.create(resource=resource, name_id='yyy', family_id='XXX', person_id='42') |
|
1032 |
with mock.patch('passerelle.contrib.caluire_axel.schemas.get_factures_a_payer') as operation: |
|
1033 |
operation.side_effect = AxelError('FooBar') |
|
1034 |
resp = app.get('/caluire-axel/test/regie/MAREGIE/invoice/XXX-42/pdf?NameID=yyy', status=404) |
|
1035 |
assert resp.json['err_desc'] == "Axel error: FooBar" |
|
1036 |
assert resp.json['err'] == 'error' |
|
1037 | ||
1038 |
filepath = os.path.join(os.path.dirname(__file__), 'data/caluire_axel/invoices.xml') |
|
1039 |
with open(filepath) as xml: |
|
1040 |
content = ( |
|
1041 |
'''<PORTAIL> |
|
1042 |
<GETFACTURESAPAYER> |
|
1043 |
%s |
|
1044 |
</GETFACTURESAPAYER> |
|
1045 |
</PORTAIL>''' |
|
1046 |
% xml.read() |
|
1047 |
) |
|
1048 |
with mock_getdata(content, 'GetFacturesaPayer'): |
|
1049 |
with mock.patch('passerelle.contrib.caluire_axel.schemas.get_pdf_facture') as operation: |
|
1050 |
operation.side_effect = AxelError('FooBar') |
|
1051 |
resp = app.get('/caluire-axel/test/regie/MAREGIE/invoice/XXX-42/pdf?NameID=yyy', status=404) |
|
1052 |
assert resp.json['err_desc'] == "Axel error: FooBar" |
|
1053 |
assert resp.json['err'] == 'error' |
|
1054 | ||
1055 | ||
1056 |
def test_invoice_pdf_endpoint_bad_request(app, resource): |
|
1057 |
Link.objects.create(resource=resource, name_id='yyy', family_id='XXX', person_id='42') |
|
1058 |
with mock_getdata(None, 'GetListFactures'): |
|
1059 |
resp = app.get( |
|
1060 |
'/caluire-axel/test/regie/MAREGIE/invoice/historical-XXX-42/pdf?NameID=yyy&nb_mounts_limit=not_a_number', |
|
1061 |
status=404, |
|
1062 |
) |
|
1063 |
assert resp.json['err_desc'] == "nb_mounts_limit must be an integer" |
|
1064 |
assert resp.json['err'] == 'bad-request' |
|
1065 | ||
1066 | ||
1067 |
def test_invoice_pdf_endpoint_no_result(app, resource): |
|
1068 |
resp = app.get('/caluire-axel/test/regie/MAREGIE/invoice/XXX-42/pdf?NameID=yyy', status=404) |
|
1069 |
assert resp.json['err_desc'] == "Person not found" |
|
1070 |
assert resp.json['err'] == 'not-found' |
|
1071 | ||
1072 |
Link.objects.create(resource=resource, name_id='yyy', family_id='XXX', person_id='42') |
|
1073 |
filepath = os.path.join(os.path.dirname(__file__), 'data/caluire_axel/invoices.xml') |
|
1074 |
with open(filepath) as xml: |
|
1075 |
content = ( |
|
1076 |
'''<PORTAIL> |
|
1077 |
<GETFACTURESAPAYER> |
|
1078 |
%s |
|
1079 |
</GETFACTURESAPAYER> |
|
1080 |
</PORTAIL>''' |
|
1081 |
% xml.read() |
|
1082 |
) |
|
1083 |
with mock_getdata(content, 'GetFacturesaPayer'): |
|
1084 |
resp = app.get('/caluire-axel/test/regie/MAREGIE/invoice/XXX-35/pdf?NameID=yyy', status=404) |
|
1085 |
assert resp.json['err_desc'] == "Invoice not found" |
|
1086 |
assert resp.json['err'] == 'not-found' |
|
1087 | ||
1088 |
with mock_getdata(content, 'GetFacturesaPayer'): |
|
1089 |
resp = app.get('/caluire-axel/test/regie/MAREGIE/invoice/XXX-44/pdf?NameID=yyy', status=404) |
|
1090 |
assert resp.json['err_desc'] == "Invoice not found" |
|
1091 |
assert resp.json['err'] == 'not-found' |
|
1092 | ||
1093 |
with mock_getdata(content, 'GetFacturesaPayer'): |
|
1094 |
resp = app.get('/caluire-axel/test/regie/MAREGIE/invoice/XXX-43/pdf?NameID=yyy', status=404) |
|
1095 |
assert resp.json['err_desc'] == "PDF not available" |
|
1096 |
assert resp.json['err'] == 'not-available' |
|
1097 | ||
1098 |
pdf_content = '''<PORTAIL> |
|
1099 |
<GETPDFFACTURE> |
|
1100 |
<CODE>1</CODE> |
|
1101 |
<FACTUREPDF></FACTUREPDF> |
|
1102 |
</GETPDFFACTURE> |
|
1103 |
</PORTAIL>''' |
|
1104 |
with mock.patch('passerelle.contrib.caluire_axel.models.CaluireAxel.get_invoice') as invoice: |
|
1105 |
invoice.return_value = {'has_pdf': True, 'display_id': '42'} |
|
1106 |
with mock_getdata(pdf_content, 'GetPdfFacture'): |
|
1107 |
resp = app.get('/caluire-axel/test/regie/MAREGIE/invoice/XXX-42/pdf?NameID=yyy', status=404) |
|
1108 |
assert resp.json['err_desc'] == "PDF error" |
|
1109 |
assert resp.json['err'] == 'error' |
|
1110 | ||
1111 | ||
1112 |
def test_invoice_pdf_endpoint(app, resource): |
|
1113 |
Link.objects.create(resource=resource, name_id='yyy', family_id='XXX', person_id='42') |
|
1114 |
pdf_content = '''<PORTAIL> |
|
1115 |
<GETPDFFACTURE> |
|
1116 |
<CODE>1</CODE> |
|
1117 |
<FACTUREPDF>aGVsbG8gd29ybGQ=</FACTUREPDF> |
|
1118 |
</GETPDFFACTURE> |
|
1119 |
</PORTAIL>''' |
|
1120 |
with mock.patch('passerelle.contrib.caluire_axel.models.CaluireAxel.get_invoice') as invoice: |
|
1121 |
invoice.return_value = {'has_pdf': True, 'display_id': '42'} |
|
1122 |
with mock_getdata(pdf_content, 'GetPdfFacture'): |
|
1123 |
app.get('/caluire-axel/test/regie/MAREGIE/invoice/XXX-42/pdf?NameID=yyy') |
|
1124 |
assert invoice.call_args_list[0][1]['historical'] is False |
|
1125 | ||
1126 |
with mock.patch('passerelle.contrib.caluire_axel.models.CaluireAxel.get_invoice') as invoice: |
|
1127 |
invoice.return_value = {'has_pdf': True, 'display_id': '42'} |
|
1128 |
with mock_getdata(pdf_content, 'GetPdfFacture'): |
|
1129 |
app.get('/caluire-axel/test/regie/MAREGIE/invoice/historical-XXX-42/pdf?NameID=yyy') |
|
1130 |
assert invoice.call_args_list[0][1]['historical'] is True |
|
1028 |
- |