Projet

Général

Profil

0004-caluire-axel-add-invoice_pdf-endpoint-53884.patch

Nicolas Roche, 12 mai 2021 12:15

Télécharger (13 ko)

Voir les différences:

Subject: [PATCH 4/4] caluire-axel: add invoice_pdf endpoint (#53884)

 passerelle/contrib/caluire_axel/models.py     |  59 ++++++++++
 passerelle/contrib/caluire_axel/schemas.py    |   1 +
 .../caluire_axel/xsd/Q_GetPdfFacture.xsd      |  20 ++++
 .../caluire_axel/xsd/R_GetPdfFacture.xsd      |  34 ++++++
 tests/test_caluire_axel.py                    | 103 ++++++++++++++++++
 5 files changed, 217 insertions(+)
 create mode 100644 passerelle/contrib/caluire_axel/xsd/Q_GetPdfFacture.xsd
 create mode 100644 passerelle/contrib/caluire_axel/xsd/R_GetPdfFacture.xsd
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
-