Projet

Général

Profil

0001-toulouse_axel-endpoint-to-pay-an-invoice-39005.patch

Lauréline Guérin, 20 janvier 2020 11:47

Télécharger (33,3 ko)

Voir les différences:

Subject: [PATCH] toulouse_axel: endpoint to pay an invoice (#39005)

 functests/toulouse_axel/test_toulouse_axel.py |  29 +--
 passerelle/contrib/toulouse_axel/models.py    | 178 ++++++++++++++----
 .../xsd/Dui/Q_FormPaiementDui.xsd             |  44 +++++
 .../xsd/Dui/R_FormPaiementDui.xsd             |  36 ++++
 tests/data/toulouse_axel/invoices.xml         |  12 ++
 tests/test_toulouse_axel.py                   | 178 +++++++++++++++---
 tests/test_toulouse_axel_schema.py            |  10 +
 7 files changed, 400 insertions(+), 87 deletions(-)
 create mode 100644 passerelle/contrib/toulouse_axel/xsd/Dui/Q_FormPaiementDui.xsd
 create mode 100644 passerelle/contrib/toulouse_axel/xsd/Dui/R_FormPaiementDui.xsd
functests/toulouse_axel/test_toulouse_axel.py
4 4

  
5 5
def test_link(conn, user):
6 6
    print("Get update management dates")
7
    url = conn + '/update_management_dates'
7
    url = conn + '/management_dates'
8 8
    resp = requests.get(url)
9 9
    resp.raise_for_status()
10 10
    res = resp.json()
......
129 129
        pprint.pprint(res)
130 130
        print('\n')
131 131

  
132
    print("Get invoices")
133
    url = conn + '/invoices?NameID=%s' % name_id
134
    resp = requests.get(url)
135
    resp.raise_for_status()
136
    res = resp.json()
137
    assert res['err'] == 0
138
    pprint.pprint(res)
139
    print('\n')
140

  
141
    data = res
142
    for invoice in data['data']:
143
        print("GET invoice info")
144
        url = conn + '/invoice/%s?NameID=%s' % (invoice['id'], name_id)
145
        resp = requests.get(url)
146
        resp.raise_for_status()
147
        res = resp.json()
148
        assert res['err'] == 0
149
        pprint.pprint(res)
150
        print('\n')
151

  
152
        if res['data']['has_pdf']:
153
            print("GET invoice PDF")
154
            url = conn + '/invoice/%s/pdf?NameID=%s' % (invoice['id'], name_id)
155
            resp = requests.get(url)
156
            resp.raise_for_status()
157
            print('\n')
158

  
159 132
    print("Deleting link")
160 133
    url = conn + '/unlink?NameID=%s' % name_id
161 134
    resp = requests.post(url)
passerelle/contrib/toulouse_axel/models.py
17 17
import base64
18 18
import copy
19 19
import datetime
20
import json
20 21
import logging
21 22
import os
22 23
import re
......
50 51
        }
51 52
    ]
52 53
}
54
datetime_type = {
55
    'type': 'string',
56
    'pattern': '[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}',
57
}
58
json_date_format = '%Y-%m-%d'
59
json_datetime_format = '%Y-%m-%dT%H:%M:%S'
60
xml_date_format = '%d/%m/%Y'
61
xml_datetime_format = '%d/%m/%Y %H:%M:%S'
62

  
63

  
64
PAYMENT_SCHEMA = {
65
    'type': 'object',
66
    'properties': {
67
        'transaction_date': copy.deepcopy(datetime_type),
68
        'transaction_id': {
69
            'type': 'string',
70
        }
71
    },
72
    'required': ['transaction_date', 'transaction_id']
73
}
53 74

  
54 75

  
55 76
def indent(tree, space="  ", level=0):
......
97 118
    return obj
98 119

  
99 120

  
121
def encode_datetime(obj):
122
    try:
123
        return datetime.datetime.strptime(obj, json_datetime_format).strftime(xml_datetime_format)
124
    except ValueError:
125
        return obj
126

  
127

  
100 128
class AxelSchema(JSONSchemaFromXMLSchema):
101 129
    type_map = {
102 130
        '{urn:AllAxelTypes}DATEREQUIREDType': 'date',
......
114 142

  
115 143
    def encode_date(self, obj):
116 144
        try:
117
            return datetime.datetime.strptime(obj, '%Y-%m-%d').strftime('%d/%m/%Y')
145
            return datetime.datetime.strptime(obj, json_date_format).strftime(xml_date_format)
118 146
        except ValueError:
119 147
            return obj
120 148

  
......
124 152
        return self.encode_date(obj)
125 153

  
126 154
    def decode_date(self, data):
127
        value = datetime.datetime.strptime(data.text, '%d/%m/%Y').strftime('%Y-%m-%d')
155
        value = datetime.datetime.strptime(data.text, xml_date_format).strftime(json_date_format)
128 156
        return xmlschema.ElementData(tag=data.tag, text=value, content=data.content, attributes=data.attributes)
129 157

  
130 158
    def decode_date_optional(self, data):
......
249 277
ref_verif_dui = Operation('RefVerifDui')
250 278
ref_famille_dui = Operation('RefFamilleDui')
251 279
form_maj_famille_dui = Operation('FormMajFamilleDui')
280
form_paiement_dui = Operation('FormPaiementDui')
252 281
ref_facture_a_payer = Operation('RefFactureAPayer')
253 282
ref_facture_pdf = Operation('RefFacturePDF', prefix='')
254 283

  
......
471 500
            }
472 501
        }
473 502

  
474
    def get_link(self, name_id, error_status=200):
503
    def get_link(self, name_id):
475 504
        try:
476 505
            return self.link_set.get(name_id=name_id)
477 506
        except Link.DoesNotExist:
478
            raise APIError('Person not found', err_code='not-found', http_status=error_status)
507
            raise APIError('Person not found', err_code='not-found')
479 508

  
480 509
    @endpoint(
481 510
        description=_('Delete link between user and Toulouse Axel'),
......
724 753
            }
725 754
        }
726 755

  
727
    def get_invoices(self, name_id, error_status=200):
728
        link = self.get_link(name_id, error_status=error_status)
756
    def normalize_invoice(self, invoice, dui):
757
        invoice_id = '%s-%s' % (dui, invoice['IDFACTURE'])
758
        data = {
759
            'id': invoice_id,
760
            'display_id': str(invoice['IDFACTURE']),
761
            'label': invoice['LIBELLE'],
762
            'amount': invoice['RESTEAPAYER'],
763
            'total_amount': invoice['MONTANTTOTAL'],
764
            'created': invoice['DATEEMISSION'],
765
            'pay_limit_date': invoice['DATEECHEANCE'],
766
            'has_pdf': True if invoice['EXISTEPDF'] == '1' else False,
767
            'paid': False,
768
            'vendor': {'toulouse-axel': invoice},
769
        }
770
        pay_limit_date = datetime.datetime.strptime(invoice['DATEECHEANCE'], '%Y-%m-%d').date()
771
        data['online_payment'] = data['amount'] > 0 and pay_limit_date >= datetime.date.today()
772
        return data
773

  
774
    def get_invoices(self, regie_id, dui=None, name_id=None):
775
        assert name_id or dui
776
        if name_id:
777
            dui = self.get_link(name_id).dui
778

  
729 779
        try:
730
            result = ref_facture_a_payer(self, {'PORTAIL': {'DUI': {'IDDUI': link.dui}}})
780
            result = ref_facture_a_payer(self, {'PORTAIL': {'DUI': {'IDDUI': dui}}})
731 781
        except AxelError as e:
732 782
            raise APIError(
733 783
                'Axel error: %s' % e,
734 784
                err_code='error',
735
                http_status=error_status,
736 785
                data={'xml_request': e.xml_request,
737 786
                      'xml_response': e.xml_response})
787

  
738 788
        data = result.json_response['DATA']['PORTAIL']['DUI']
739 789
        result = []
740 790
        for facture in data.get('FACTURES', []):
741
            result.append({
742
                'id': facture['IDFACTURE'],
743
                'label': facture['LIBELLE'],
744
                'amount': facture['RESTEAPAYER'],
745
                'total_amount': facture['MONTANTTOTAL'],
746
                'online_payment': True,
747
                'created': facture['DATEEMISSION'],
748
                'pay_limit_date': facture['DATEECHEANCE'],
749
                'has_pdf': True if facture['EXISTEPDF'] == '1' else False,
750
                'paid': False,
751
                'vendor': {'toulouse-axel': facture},
752
            })
791
            if facture['IDREGIE'] != regie_id:
792
                continue
793
            result.append(self.normalize_invoice(facture, dui))
753 794
        return result
754 795

  
755
    def get_invoice(self, name_id, invoice_id, error_status=200):
756
        invoices_data = self.get_invoices(name_id, error_status=error_status)
796
    def get_invoice(self, regie_id, invoice_id, dui=None, name_id=None):
797
        invoices_data = self.get_invoices(regie_id=regie_id, dui=dui, name_id=name_id)
757 798
        for invoice in invoices_data:
758
            if str(invoice['id']) == invoice_id:
799
            if invoice['display_id'] == invoice_id:
759 800
                return invoice
760 801

  
761 802
    @endpoint(
762
        description=_("Get invoices to pay"),
803
        name='regie',
763 804
        perm='can_access',
805
        pattern=r'^(?P<regie_id>[\w-]+)/invoices/?$',
806
        example_pattern='{regie_id}/invoices/',
807
        description=_("Get invoices to pay"),
764 808
        parameters={
765 809
            'NameID': {'description': _('Publik ID')},
810
            'regie_id': {'description': _('Regie identifier'), 'example_value': '42-PERISCOL'}
766 811
        })
767
    def invoices(self, request, NameID):
768
        invoices_data = self.get_invoices(NameID)
812
    def invoices(self, request, regie_id, NameID):
813
        invoices_data = self.get_invoices(regie_id=regie_id, name_id=NameID)
769 814
        return {'data': invoices_data}
770 815

  
771 816
    @endpoint(
817
        name='regie',
772 818
        perm='can_access',
773
        pattern=r'^(?P<invoice_id>\w+)/?$',
819
        pattern=r'^(?P<regie_id>[\w-]+)/invoice/(?P<invoice_id>\w+-\d+)/?$',
820
        example_pattern='{regie_id}/invoice/{invoice_id}/',
774 821
        description=_('Get invoice details'),
775 822
        parameters={
776 823
            'NameID': {'description': _('Publik ID')},
777
            'invoice_id': {'description': _('Invoice identifier')}
824
            'regie_id': {'description': _('Regie identifier'), 'example_value': '42-PERISCOL'},
825
            'invoice_id': {'description': _('Invoice identifier'), 'example_value': 'DUI-42'}
778 826
        })
779
    def invoice(self, request, invoice_id, NameID):
780
        invoice = self.get_invoice(NameID, invoice_id)
827
    def invoice(self, request, regie_id, invoice_id, NameID):
828
        invoice_id = invoice_id.split('-')[1]
829
        invoice = self.get_invoice(regie_id=regie_id, name_id=NameID, invoice_id=invoice_id)
781 830
        if invoice is None:
782 831
            raise APIError('Invoice not found', err_code='not-found')
783 832

  
784 833
        return {'data': invoice}
785 834

  
786 835
    @endpoint(
787
        name='invoice',
836
        name='regie',
788 837
        perm='can_access',
789
        pattern=r'^(?P<invoice_id>\w+)/pdf/?$',
838
        pattern=r'^(?P<regie_id>[\w-]+)/invoice/(?P<invoice_id>\w+-\d+)/pdf/?$',
839
        example_pattern='{regie_id}/invoice/{invoice_id}/pdf/',
790 840
        description=_('Get invoice as a PDF file'),
791 841
        parameters={
792 842
            'NameID': {'description': _('Publik ID')},
793
            'invoice_id': {'description': _('Invoice identifier')}
843
            'regie_id': {'description': _('Regie identifier'), 'example_value': '42-PERISCOL'},
844
            'invoice_id': {'description': _('Invoice identifier'), 'example_value': 'DUI-42'}
794 845
        })
795
    def invoice_pdf(self, request, invoice_id, NameID):
846
    def invoice_pdf(self, request, regie_id, invoice_id, NameID):
796 847
        # check that invoice is related to current user
797
        invoice = self.get_invoice(NameID, invoice_id, error_status=404)
848
        invoice_id = invoice_id.split('-')[1]
849
        try:
850
            invoice = self.get_invoice(regie_id=regie_id, name_id=NameID, invoice_id=invoice_id)
851
        except APIError as e:
852
            e.http_status = 404
853
            raise
798 854
        if invoice is None:
799 855
            raise APIError('Invoice not found', err_code='not-found', http_status=404)
800 856
        # check that PDF is available
......
802 858
            raise APIError('PDF not available', err_code='not-available', http_status=404)
803 859

  
804 860
        try:
805
            result = ref_facture_pdf(self, {'PORTAIL': {'FACTUREPDF': {'IDFACTURE': int(invoice_id)}}})
861
            result = ref_facture_pdf(self, {'PORTAIL': {'FACTUREPDF': {'IDFACTURE': int(invoice['display_id'])}}})
806 862
        except AxelError as e:
807 863
            raise APIError(
808 864
                'Axel error: %s' % e,
......
819 875
        response.write(b64content)
820 876
        return response
821 877

  
878
    @endpoint(
879
        name='regie',
880
        methods=['post'],
881
        perm='can_access',
882
        pattern=r'^(?P<regie_id>[\w-]+)/invoice/(?P<invoice_id>\w+-\d+)/pay/?$',
883
        example_pattern='{regie_id}/invoice/{invoice_id}/pay/',
884
        description=_('Notify an invoice as paid'),
885
        parameters={
886
            'regie_id': {'description': _('Regie identifier'), 'example_value': '42-PERISCOL'},
887
            'invoice_id': {'description': _('Invoice identifier'), 'example_value': 'DUI-42'}
888
        },
889
        post={
890
            'request_body': {
891
                'schema': {
892
                    'application/json': PAYMENT_SCHEMA,
893
                }
894
            }
895
        })
896
    def pay_invoice(self, request, regie_id, invoice_id, **kwargs):
897
        data = json.loads(request.body)
898
        dui, invoice_id = invoice_id.split('-')
899

  
900
        invoices_data = self.get_invoices(regie_id=regie_id, dui=dui)
901
        transaction_amount = None
902
        for invoice in invoices_data:
903
            if invoice['display_id'] == invoice_id:
904
                transaction_amount = invoice['amount']
905
                break
906
        if transaction_amount is None:
907
            raise APIError('Invoice not found', err_code='not-found')
908

  
909
        transaction_id = data['transaction_id']
910
        transaction_date = encode_datetime(data['transaction_date'])
911
        post_data = {
912
            'IDFACTURE': int(invoice_id),
913
            'IDREGIEENCAISSEMENT': '',
914
            'MONTANTPAYE': transaction_amount,
915
            'DATEPAIEMENT': transaction_date,
916
            'REFERENCE': transaction_id,
917
        }
918
        try:
919
            form_paiement_dui(self, {'PORTAIL': {'DUI': post_data}})
920
        except AxelError as e:
921
            raise APIError(
922
                'Axel error: %s' % e,
923
                err_code='error',
924
                data={'xml_request': e.xml_request,
925
                      'xml_response': e.xml_response})
926
        return {'data': True}
927

  
822 928

  
823 929
class Link(models.Model):
824 930
    resource = models.ForeignKey(ToulouseAxel, on_delete=models.CASCADE)
passerelle/contrib/toulouse_axel/xsd/Dui/Q_FormPaiementDui.xsd
1
<?xml version="1.0" encoding="utf-8" ?>
2
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:all="urn:AllAxelTypes">
3
	
4
	<xsd:import schemaLocation="../AllAxelTypes.xsd" namespace="urn:AllAxelTypes"  />
5
	
6
	<xsd:complexType name="DUIType">
7
		<xsd:sequence>
8
			<xsd:element ref="IDFACTURE" />
9
			<xsd:element ref="IDREGIEENCAISSEMENT"/>
10
			<xsd:element ref="MONTANTPAYE"/>
11
			<xsd:element ref="DATEPAIEMENT"/>
12
			<xsd:element ref="REFERENCE"/>
13
		</xsd:sequence> 
14
	</xsd:complexType>
15
	
16
	<xsd:complexType name="PORTAILType">
17
		<xsd:sequence>
18
				<xsd:element ref="DUI"/>
19
		</xsd:sequence>	
20
	</xsd:complexType>
21
			
22
	<xsd:simpleType name="CHOIXType">
23
		<xsd:restriction base="xsd:string">
24
			<xsd:enumeration value="1" />
25
			<xsd:enumeration value="0" />
26
		</xsd:restriction>
27
	</xsd:simpleType>
28
	
29
	<xsd:simpleType name="IDENTREGIEType">
30
		<xsd:restriction base="xsd:string">
31
			<xsd:maxLength value="10" />
32
		</xsd:restriction>
33
	</xsd:simpleType>
34
	
35
	<xsd:element name="IDFACTURE" type="xsd:unsignedInt"/>
36
	<xsd:element name="IDREGIEENCAISSEMENT" type="IDENTREGIEType"/>
37
	<xsd:element name="MONTANTPAYE" type="all:MONTANTREQUIREDType"/>
38
	<xsd:element name="DATEPAIEMENT" type="all:DATETIMEType"/>
39
	<xsd:element name="REFERENCE" type="xsd:string"/>
40
	
41
	<xsd:element name="DUI" type="DUIType"/>
42
	
43
	<xsd:element name="PORTAIL" type="PORTAILType"/>
44
</xsd:schema>
passerelle/contrib/toulouse_axel/xsd/Dui/R_FormPaiementDui.xsd
1
<?xml version="1.0" encoding="utf-8" ?>
2
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:all="urn:AllAxelTypes">
3
	
4
	<xsd:import schemaLocation="../AllAxelTypes.xsd" namespace="urn:AllAxelTypes"  />
5

  
6
	<xsd:redefine schemaLocation="../R_ShemaResultat.xsd">
7
	    <xsd:simpleType name="TYPEType">
8
			<xsd:restriction base="TYPEType">
9
				<xsd:enumeration value="FormPaiementDui" />
10
			</xsd:restriction>
11
	    </xsd:simpleType>
12
		
13
		<xsd:complexType name="PORTAILType">
14
			<xsd:complexContent>
15
				<xsd:extension base="PORTAILType">
16
					<xsd:sequence>
17
						<xsd:element ref="DUI" minOccurs="0"/>
18
					</xsd:sequence>	
19
				</xsd:extension>
20
			 </xsd:complexContent>
21
		</xsd:complexType>
22
	</xsd:redefine>
23
	
24
	<xsd:complexType name="DUIType">
25
		<xsd:sequence>
26
			<xsd:element ref="IDDUI" />
27
			<xsd:element ref="CODE" />
28
		</xsd:sequence> 
29
	</xsd:complexType>
30

  
31
	<xsd:element name="CODE" type="all:unsignedInt-or-empty"/>	
32
	<xsd:element name="IDDUI" type="all:IDENTType"/>
33
	
34
	<xsd:element name="DUI" type="DUIType" />
35
	
36
</xsd:schema>
tests/data/toulouse_axel/invoices.xml
27 27
      <EXISTEPDF>0</EXISTEPDF>
28 28
      <IDFACTURE>43</IDFACTURE>
29 29
    </FACTURES>
30
    <FACTURES>
31
      <NUMFACTURE>44</NUMFACTURE>
32
      <DATEEMISSION>12/01/2020</DATEEMISSION>
33
      <DATEECHEANCE>15/01/2020</DATEECHEANCE>
34
      <LIBELLE>PRESTATIONS PERISCOLAIRES DECEMBRE 2019</LIBELLE>
35
      <IDFACTURATION>4244-35AA</IDFACTURATION>
36
      <MONTANTTOTAL>44.94</MONTANTTOTAL>
37
      <RESTEAPAYER>44.94</RESTEAPAYER>
38
      <IDREGIE>AUTREREGIE</IDREGIE>
39
      <EXISTEPDF>1</EXISTEPDF>
40
      <IDFACTURE>44</IDFACTURE>
41
    </FACTURES>
30 42
  </DUI>
31 43
</PORTAIL>
tests/test_toulouse_axel.py
17 17
from contextlib import contextmanager
18 18
import copy
19 19
import datetime
20
import decimal
20 21
import json
21 22
import mock
22 23
import os
......
32 33
    OperationResult,
33 34
    ToulouseAxel,
34 35
    form_maj_famille_dui,
36
    form_paiement_dui,
35 37
    ref_date_gestion_dui,
36 38
    ref_famille_dui,
37 39
    ref_facture_a_payer,
......
327 329
            })
328 330

  
329 331

  
332
@pytest.mark.parametrize('content', [
333
    '<PORTAIL><DUI/></PORTAIL>',
334
])
335
def test_operation_form_paiement_dui(resource, content):
336
    with mock_getdata(content, 'FormPaiementDui'):
337
        with pytest.raises(AxelError):
338
            form_paiement_dui(resource, {
339
                'PORTAIL': {
340
                    'DUI': {
341
                        'IDFACTURE': '42',
342
                        'IDREGIEENCAISSEMENT': '',
343
                        'MONTANTPAYE': '42.42',
344
                        'DATEPAIEMENT': '01/01/2020 12:12:12',
345
                        'REFERENCE': '42',
346
                    }
347
                }
348
            })
349

  
350

  
330 351
def test_management_dates_endpoint_axel_error(app, resource):
331 352
    with mock.patch('passerelle.contrib.toulouse_axel.models.ref_date_gestion_dui') as operation:
332 353
        operation.side_effect = AxelError('FooBar')
......
1195 1216
    Link.objects.create(resource=resource, name_id='yyy', dui='XXX', person_id='42')
1196 1217
    with mock.patch('passerelle.contrib.toulouse_axel.models.ref_facture_a_payer') as operation:
1197 1218
        operation.side_effect = AxelError('FooBar')
1198
        resp = app.get('/toulouse-axel/test/invoices?NameID=yyy')
1219
        resp = app.get('/toulouse-axel/test/regie/MAREGIE/invoices?NameID=yyy')
1199 1220
    assert resp.json['err_desc'] == "Axel error: FooBar"
1200 1221
    assert resp.json['err'] == 'error'
1201 1222

  
1202 1223

  
1203 1224
def test_invoices_endpoint_no_result(app, resource):
1204
    resp = app.get('/toulouse-axel/test/invoices?NameID=yyy')
1225
    resp = app.get('/toulouse-axel/test/regie/MAREGIE/invoices?NameID=yyy')
1205 1226
    assert resp.json['err_desc'] == "Person not found"
1206 1227
    assert resp.json['err'] == 'not-found'
1207 1228

  
......
1216 1237
    </DUI>
1217 1238
</PORTAIL>'''
1218 1239
    with mock_getdata(content, 'RefFactureAPayer'):
1219
        resp = app.get('/toulouse-axel/test/invoices?NameID=yyy')
1240
        resp = app.get('/toulouse-axel/test/regie/MAREGIE/invoices?NameID=yyy')
1220 1241
    assert resp.json['err'] == 0
1221 1242
    assert resp.json['data'] == []
1222 1243

  
......
1227 1248
    with open(filepath) as xml:
1228 1249
        content = xml.read()
1229 1250
    with mock_getdata(content, 'RefFactureAPayer'):
1230
        resp = app.get('/toulouse-axel/test/invoices?NameID=yyy')
1251
        resp = app.get('/toulouse-axel/test/regie/MAREGIE/invoices?NameID=yyy')
1231 1252
    assert resp.json['err'] == 0
1232 1253
    assert resp.json['data'] == [
1233 1254
        {
1234
            'id': 42,
1255
            'id': 'XXX-42',
1256
            'display_id': '42',
1235 1257
            'label': 'PRESTATIONS PERISCOLAIRES SEPTEMBRE-OCTOBRE 2019',
1236 1258
            'amount': '44.94',
1237 1259
            'total_amount': '44.94',
1238
            'online_payment': True,
1260
            'online_payment': False,
1239 1261
            'created': '2019-11-12',
1240 1262
            'pay_limit_date': '2019-12-04',
1241 1263
            'has_pdf': True,
......
1256 1278
            }
1257 1279
        },
1258 1280
        {
1259
            'id': 43,
1281
            'id': 'XXX-43',
1282
            'display_id': '43',
1260 1283
            'label': 'PRESTATIONS PERISCOLAIRES NOVEMBRE 2019',
1261 1284
            'amount': '44.94',
1262 1285
            'total_amount': '44.94',
1263
            'online_payment': True,
1286
            'online_payment': False,
1264 1287
            'created': '2019-12-12',
1265 1288
            'pay_limit_date': '2020-01-04',
1266 1289
            'has_pdf': False,
......
1281 1304
            }
1282 1305
        }
1283 1306
    ]
1307
    with mock_getdata(content, 'RefFactureAPayer'):
1308
        resp = app.get('/toulouse-axel/test/regie/AUTREREGIE/invoices?NameID=yyy')
1309
    assert resp.json['err'] == 0
1310
    assert resp.json['data'] == [
1311
        {
1312
            'id': 'XXX-44',
1313
            'display_id': '44',
1314
            'label': 'PRESTATIONS PERISCOLAIRES DECEMBRE 2019',
1315
            'amount': '44.94',
1316
            'total_amount': '44.94',
1317
            'online_payment': False,
1318
            'created': '2020-01-12',
1319
            'pay_limit_date': '2020-01-15',
1320
            'has_pdf': True,
1321
            'paid': False,
1322
            'vendor': {
1323
                'toulouse-axel': {
1324
                    'IDFACTURATION': '4244-35AA',
1325
                    'IDFACTURE': 44,
1326
                    'IDREGIE': 'AUTREREGIE',
1327
                    'DATEECHEANCE': '2020-01-15',
1328
                    'DATEEMISSION': '2020-01-12',
1329
                    'EXISTEPDF': '1',
1330
                    'LIBELLE': 'PRESTATIONS PERISCOLAIRES DECEMBRE 2019',
1331
                    'MONTANTTOTAL': '44.94',
1332
                    'NUMFACTURE': 44,
1333
                    'RESTEAPAYER': '44.94',
1334
                 }
1335
            }
1336
        }
1337
    ]
1284 1338

  
1285 1339

  
1286 1340
def test_invoice_endpoint_axel_error(app, resource):
1287 1341
    Link.objects.create(resource=resource, name_id='yyy', dui='XXX', person_id='42')
1288 1342
    with mock.patch('passerelle.contrib.toulouse_axel.models.ref_facture_a_payer') as operation:
1289 1343
        operation.side_effect = AxelError('FooBar')
1290
        resp = app.get('/toulouse-axel/test/invoice/42?NameID=yyy')
1344
        resp = app.get('/toulouse-axel/test/regie/MAREGIE/invoice/XXX-42?NameID=yyy')
1291 1345
    assert resp.json['err_desc'] == "Axel error: FooBar"
1292 1346
    assert resp.json['err'] == 'error'
1293 1347

  
1294 1348

  
1295 1349
def test_invoice_endpoint_no_result(app, resource):
1296
    resp = app.get('/toulouse-axel/test/invoice/42?NameID=yyy')
1350
    resp = app.get('/toulouse-axel/test/regie/MAREGIE/invoice/XXX-42?NameID=yyy')
1297 1351
    assert resp.json['err_desc'] == "Person not found"
1298 1352
    assert resp.json['err'] == 'not-found'
1299 1353

  
......
1302 1356
    with open(filepath) as xml:
1303 1357
        content = xml.read()
1304 1358
    with mock_getdata(content, 'RefFactureAPayer'):
1305
        resp = app.get('/toulouse-axel/test/invoice/35?NameID=yyy')
1359
        resp = app.get('/toulouse-axel/test/regie/MAREGIE/invoice/XXX-35?NameID=yyy')
1360
    assert resp.json['err_desc'] == "Invoice not found"
1361
    assert resp.json['err'] == 'not-found'
1362
    with mock_getdata(content, 'RefFactureAPayer'):
1363
        resp = app.get('/toulouse-axel/test/regie/MAREGIE/invoice/XXX-44?NameID=yyy')
1306 1364
    assert resp.json['err_desc'] == "Invoice not found"
1307 1365
    assert resp.json['err'] == 'not-found'
1308 1366

  
......
1313 1371
    with open(filepath) as xml:
1314 1372
        content = xml.read()
1315 1373
    with mock_getdata(content, 'RefFactureAPayer'):
1316
        resp = app.get('/toulouse-axel/test/invoice/42?NameID=yyy')
1374
        resp = app.get('/toulouse-axel/test/regie/MAREGIE/invoice/XXX-42?NameID=yyy')
1317 1375
    assert resp.json['err'] == 0
1318 1376
    assert resp.json['data'] == {
1319
        'id': 42,
1377
        'id': 'XXX-42',
1378
        'display_id': '42',
1320 1379
        'label': 'PRESTATIONS PERISCOLAIRES SEPTEMBRE-OCTOBRE 2019',
1321 1380
        'amount': '44.94',
1322 1381
        'total_amount': '44.94',
1323
        'online_payment': True,
1382
        'online_payment': False,
1324 1383
        'created': '2019-11-12',
1325 1384
        'pay_limit_date': '2019-12-04',
1326 1385
        'has_pdf': True,
......
1346 1405
    Link.objects.create(resource=resource, name_id='yyy', dui='XXX', person_id='42')
1347 1406
    with mock.patch('passerelle.contrib.toulouse_axel.models.ref_facture_a_payer') as operation:
1348 1407
        operation.side_effect = AxelError('FooBar')
1349
        resp = app.get('/toulouse-axel/test/invoice/42/pdf?NameID=yyy', status=404)
1408
        resp = app.get('/toulouse-axel/test/regie/MAREGIE/invoice/XXX-42/pdf?NameID=yyy', status=404)
1350 1409
    assert resp.json['err_desc'] == "Axel error: FooBar"
1351 1410
    assert resp.json['err'] == 'error'
1352 1411

  
......
1356 1415
    with mock_getdata(content, 'RefFactureAPayer'):
1357 1416
        with mock.patch('passerelle.contrib.toulouse_axel.models.ref_facture_pdf') as operation:
1358 1417
            operation.side_effect = AxelError('FooBar')
1359
            resp = app.get('/toulouse-axel/test/invoice/42/pdf?NameID=yyy', status=404)
1418
            resp = app.get('/toulouse-axel/test/regie/MAREGIE/invoice/XXX-42/pdf?NameID=yyy', status=404)
1360 1419
    assert resp.json['err_desc'] == "Axel error: FooBar"
1361 1420
    assert resp.json['err'] == 'error'
1362 1421

  
1363 1422

  
1364 1423
def test_invoice_pdf_endpoint_no_result(app, resource):
1365
    resp = app.get('/toulouse-axel/test/invoice/42/pdf?NameID=yyy', status=404)
1424
    resp = app.get('/toulouse-axel/test/regie/MAREGIE/invoice/XXX-42/pdf?NameID=yyy', status=404)
1366 1425
    assert resp.json['err_desc'] == "Person not found"
1367 1426
    assert resp.json['err'] == 'not-found'
1368 1427

  
......
1371 1430
    with open(filepath) as xml:
1372 1431
        content = xml.read()
1373 1432
    with mock_getdata(content, 'RefFactureAPayer'):
1374
        resp = app.get('/toulouse-axel/test/invoice/35/pdf?NameID=yyy', status=404)
1433
        resp = app.get('/toulouse-axel/test/regie/MAREGIE/invoice/XXX-35/pdf?NameID=yyy', status=404)
1375 1434
    assert resp.json['err_desc'] == "Invoice not found"
1376 1435
    assert resp.json['err'] == 'not-found'
1377 1436

  
1378 1437
    with mock_getdata(content, 'RefFactureAPayer'):
1379
        resp = app.get('/toulouse-axel/test/invoice/43/pdf?NameID=yyy', status=404)
1438
        resp = app.get('/toulouse-axel/test/regie/MAREGIE/invoice/XXX-44/pdf?NameID=yyy', status=404)
1439
    assert resp.json['err_desc'] == "Invoice not found"
1440
    assert resp.json['err'] == 'not-found'
1441

  
1442
    with mock_getdata(content, 'RefFactureAPayer'):
1443
        resp = app.get('/toulouse-axel/test/regie/MAREGIE/invoice/XXX-43/pdf?NameID=yyy', status=404)
1380 1444
    assert resp.json['err_desc'] == "PDF not available"
1381 1445
    assert resp.json['err'] == 'not-available'
1382 1446

  
......
1384 1448
    <PDF FILE=''></PDF>
1385 1449
</PORTAIL>'''
1386 1450
    with mock.patch('passerelle.contrib.toulouse_axel.models.ToulouseAxel.get_invoice') as invoice:
1387
        invoice.return_value = {'has_pdf': True}
1451
        invoice.return_value = {'has_pdf': True, 'display_id': '42'}
1388 1452
        with mock_getdata(pdf_content, 'RefFacturePDF'):
1389
            resp = app.get('/toulouse-axel/test/invoice/42/pdf?NameID=yyy', status=404)
1453
            resp = app.get('/toulouse-axel/test/regie/MAREGIE/invoice/XXX-42/pdf?NameID=yyy', status=404)
1390 1454
    assert resp.json['err_desc'] == "PDF error"
1391 1455
    assert resp.json['err'] == 'error'
1392 1456

  
......
1397 1461
    <PDF FILE='aGVsbG8gd29ybGQ='></PDF>
1398 1462
</PORTAIL>'''
1399 1463
    with mock.patch('passerelle.contrib.toulouse_axel.models.ToulouseAxel.get_invoice') as invoice:
1400
        invoice.return_value = {'has_pdf': True}
1464
        invoice.return_value = {'has_pdf': True, 'display_id': '42'}
1401 1465
        with mock_getdata(pdf_content, 'RefFacturePDF'):
1402
            app.get('/toulouse-axel/test/invoice/42/pdf?NameID=yyy')
1466
            app.get('/toulouse-axel/test/regie/MAREGIE/invoice/XXX-42/pdf?NameID=yyy')
1467

  
1468

  
1469
def test_pay_invoice_endpoint_axel_error(app, resource):
1470
    payload = {
1471
        'transaction_date': '2020-01-01T12:00:00',
1472
        'transaction_id': 'foo',
1473
    }
1474
    Link.objects.create(resource=resource, name_id='yyy', dui='XXX', person_id='42')
1475
    with mock.patch('passerelle.contrib.toulouse_axel.models.ref_facture_a_payer') as operation:
1476
        operation.side_effect = AxelError('FooBar')
1477
        resp = app.post_json('/toulouse-axel/test/regie/MAREGIE/invoice/XXX-42/pay?NameID=yyy', params=payload)
1478
    assert resp.json['err_desc'] == "Axel error: FooBar"
1479
    assert resp.json['err'] == 'error'
1480

  
1481
    filepath = os.path.join(os.path.dirname(__file__), 'data/toulouse_axel/invoices.xml')
1482
    with open(filepath) as xml:
1483
        content = xml.read()
1484
    with mock_getdata(content, 'RefFactureAPayer'):
1485
        with mock.patch('passerelle.contrib.toulouse_axel.models.form_paiement_dui') as operation:
1486
            operation.side_effect = AxelError('FooBar')
1487
            resp = app.post_json('/toulouse-axel/test/regie/MAREGIE/invoice/XXX-42/pay?NameID=yyy', params=payload)
1488
    assert resp.json['err_desc'] == "Axel error: FooBar"
1489
    assert resp.json['err'] == 'error'
1490

  
1491

  
1492
def test_pay_invoice_endpoint_no_result(app, resource):
1493
    payload = {
1494
        'transaction_date': '2020-01-01T12:00:00',
1495
        'transaction_id': 'foo',
1496
    }
1497
    filepath = os.path.join(os.path.dirname(__file__), 'data/toulouse_axel/invoices.xml')
1498
    with open(filepath) as xml:
1499
        content = xml.read()
1500
    with mock_getdata(content, 'RefFactureAPayer'):
1501
        resp = app.post_json('/toulouse-axel/test/regie/MAREGIE/invoice/XXX-35/pay?NameID=yyy', params=payload)
1502
    assert resp.json['err_desc'] == "Invoice not found"
1503
    assert resp.json['err'] == 'not-found'
1504
    with mock_getdata(content, 'RefFactureAPayer'):
1505
        resp = app.post_json('/toulouse-axel/test/regie/MAREGIE/invoice/XXX-44/pay?NameID=yyy', params=payload)
1506
    assert resp.json['err_desc'] == "Invoice not found"
1507
    assert resp.json['err'] == 'not-found'
1508

  
1509

  
1510
def test_pay_invoice_endpoint(app, resource):
1511
    payload = {
1512
        'transaction_date': '2020-01-01T12:00:00',
1513
        'transaction_id': 'foo',
1514
    }
1515
    Link.objects.create(resource=resource, name_id='yyy', dui='XXX', person_id='42')
1516
    filepath = os.path.join(os.path.dirname(__file__), 'data/toulouse_axel/invoices.xml')
1517
    with open(filepath) as xml:
1518
        content = xml.read()
1519
    with mock_getdata(content, 'RefFactureAPayer'):
1520
        with mock.patch('passerelle.contrib.toulouse_axel.models.form_paiement_dui') as operation:
1521
            resp = app.post_json('/toulouse-axel/test/regie/MAREGIE/invoice/XXX-42/pay?NameID=yyy', params=payload)
1522
    assert resp.json['err'] == 0
1523
    assert resp.json['data'] is True
1524
    assert operation.call_args_list[0][0][1] == {
1525
        'PORTAIL': {
1526
            'DUI': {
1527
                'DATEPAIEMENT': '01/01/2020 12:00:00',
1528
                'IDFACTURE': 42,
1529
                'IDREGIEENCAISSEMENT': '',
1530
                'MONTANTPAYE': decimal.Decimal('44.94'),
1531
                'REFERENCE': 'foo',
1532
            }
1533
        }
1534
    }
tests/test_toulouse_axel_schema.py
17 17
import os
18 18

  
19 19
from passerelle.contrib.toulouse_axel.models import AxelSchema
20
from passerelle.contrib.toulouse_axel.models import encode_datetime
20 21

  
21 22
import pytest
22 23
import xmlschema
......
60 61
        assert json_data['DATE'] is None
61 62

  
62 63

  
64
def test_encode_datetime():
65
    # wrong format
66
    assert encode_datetime('foo') == 'foo'
67
    assert encode_datetime('2019-12-12') == '2019-12-12'
68
    assert encode_datetime('2019-12-12T12:01:72') == '2019-12-12T12:01:72'
69
    # ok
70
    assert encode_datetime('2019-12-12T12:01:42') == '12/12/2019 12:01:42'
71

  
72

  
63 73
@pytest.mark.parametrize('bool_type', ['OUINONREQUIREDType', 'OUINONType'])
64 74
@pytest.mark.parametrize('value, expected, py_expected', [
65 75
    ('OUI', 'OUI', True),
66
-