Projet

Général

Profil

0001-caluire-axel-add-invoices-endpoint-53884.patch

Nicolas Roche, 02 juin 2021 17:31

Télécharger (15,6 ko)

Voir les différences:

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

 passerelle/contrib/caluire_axel/models.py     |  52 ++++++++-
 passerelle/contrib/caluire_axel/schemas.py    |   1 +
 passerelle/contrib/caluire_axel/utils.py      |  36 ++++++
 .../caluire_axel/xsd/Q_GetFacturesaPayer.xsd  |  26 +++++
 .../caluire_axel/xsd/R_GetFacturesaPayer.xsd  |  55 +++++++++
 tests/data/caluire_axel/invoices.xml          |  19 ++++
 tests/test_caluire_axel.py                    | 106 ++++++++++++++++++
 7 files changed, 294 insertions(+), 1 deletion(-)
 create mode 100644 passerelle/contrib/caluire_axel/xsd/Q_GetFacturesaPayer.xsd
 create mode 100644 passerelle/contrib/caluire_axel/xsd/R_GetFacturesaPayer.xsd
 create mode 100644 tests/data/caluire_axel/invoices.xml
passerelle/contrib/caluire_axel/models.py
32 32

  
33 33
class CaluireAxel(BaseResource):
34 34

  
35 35
    wsdl_url = models.CharField(
36 36
        max_length=128, blank=False, verbose_name=_('WSDL URL'), help_text=_('Caluire Axel WSDL URL')
37 37
    )
38 38

  
39 39
    category = _('Business Process Connectors')
40
    _category_ordering = [_('Family account'), _('Schooling')]
40
    _category_ordering = [_('Family account'), _('Schooling'), _('Invoices')]
41 41

  
42 42
    class Meta:
43 43
        verbose_name = _('Caluire Axel')
44 44

  
45 45
    def check_status(self):
46 46
        response = self.requests.get(self.wsdl_url)
47 47
        response.raise_for_status()
48 48

  
......
531 531
                booking['disabled'] = True
532 532
            else:
533 533
                booking['details']['out_of_delay'] = False
534 534
                booking['disabled'] = True if day_date < datetime.date.today() or color == 'grey' else False
535 535
            bookings.append(booking)
536 536

  
537 537
        return {'data': bookings}
538 538

  
539
    def get_invoices(self, regie_id, family_id):
540
        try:
541
            result = schemas.get_factures_a_payer(
542
                self,
543
                {
544
                    'PORTAIL': {
545
                        'GETFACTURESAPAYER': {
546
                            'IDENTFAMILLE': family_id,
547
                            'IDENTREGIEFACT': regie_id,
548
                        }
549
                    }
550
                },
551
            )
552

  
553
        except axel.AxelError as e:
554
            raise APIError(
555
                'Axel error: %s' % e,
556
                err_code='error',
557
                data={'xml_request': e.xml_request, 'xml_response': e.xml_response},
558
            )
559

  
560
        code = result.json_response['DATA']['PORTAIL']['GETFACTURESAPAYER']['CODE']
561
        if code < 0:
562
            raise APIError('Wrong get-invoices status', err_code='get-invoices-code-error-%s' % code)
563

  
564
        data = result.json_response['DATA']['PORTAIL']['GETFACTURESAPAYER']
565
        result = []
566

  
567
        for facture in data.get('FACTURE', []):
568
            result.append(utils.normalize_invoice(facture, family_id))
569
        return result
570

  
571
    @endpoint(
572
        display_category=_('Invoices'),
573
        display_order=1,
574
        name='regie',
575
        perm='can_access',
576
        pattern=r'^(?P<regie_id>[\w-]+)/invoices/?$',
577
        example_pattern='{regie_id}/invoices',
578
        description=_("Get invoices to pay"),
579
        parameters={
580
            'NameID': {'description': _('Publik ID')},
581
            'regie_id': {'description': _('Regie identifier'), 'example_value': 'ENF'},
582
        },
583
    )
584
    def invoices(self, request, regie_id, NameID):
585
        link = self.get_link(NameID)
586
        invoices_data = self.get_invoices(regie_id=regie_id, family_id=link.family_id)
587
        return {'data': invoices_data}
588

  
539 589

  
540 590
class Link(models.Model):
541 591
    resource = models.ForeignKey(CaluireAxel, on_delete=models.CASCADE)
542 592
    name_id = models.CharField(blank=False, max_length=256)
543 593
    family_id = models.CharField(blank=False, max_length=128)
544 594
    person_id = models.CharField(blank=False, max_length=128)
545 595

  
546 596
    class Meta:
passerelle/contrib/caluire_axel/schemas.py
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_list_activites = Operation('GetListActivites')
80 80
create_inscription_activite = Operation('CreateInscriptionActivite', data_method='setData')
81 81
get_agenda = Operation('GetAgenda')
82
get_factures_a_payer = Operation('GetFacturesaPayer')
82 83

  
83 84

  
84 85
LINK_SCHEMA = copy.deepcopy(
85 86
    find_individus.request_schema['properties']['PORTAIL']['properties']['FINDINDIVIDU']
86 87
)
87 88
for key in ['NAISSANCE', 'CODEPOSTAL', 'VILLE', 'TEL', 'MAIL']:
88 89
    LINK_SCHEMA['properties'].pop(key)
89 90
    LINK_SCHEMA['required'].remove(key)
passerelle/contrib/caluire_axel/utils.py
16 16
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 17

  
18 18

  
19 19
def get_reference_year_from_date(booking_date):
20 20
    if booking_date.month <= 8:
21 21
        # between january and august, reference year is the year just before
22 22
        return booking_date.year - 1
23 23
    return booking_date.year
24

  
25

  
26
def normalize_invoice(invoice, family_id, historical=False, vendor_base=None):
27
    vendor = vendor_base or {}
28
    vendor.update(invoice)
29
    invoice_id = '%s-%s' % (family_id, invoice['IDFACTURE'])
30
    if historical:
31
        invoice_id = 'historical-%s' % invoice_id
32
    data = {
33
        'id': invoice_id,
34
        'display_id': str(invoice['IDFACTURE']),
35
        'label': invoice['FACTURATION'],
36
        'paid': False,
37
        'total_amount': invoice['MONTANT'],
38
        'has_pdf': invoice['EXISTEPDF'],
39
        'vendor': {'caluire-axel': vendor},
40
    }
41
    if historical:
42
        data.update(
43
            {
44
                'amount': 0,
45
                'pay_limit_date': '',
46
                'online_payment': False,
47
            }
48
        )
49
    else:
50
        data.update(
51
            {
52
                'amount': max(0, invoice['MONTANT'] - invoice['ENCAISSE']),
53
                'amount_paid': invoice['ENCAISSE'],
54
                'created': invoice['EMISSION'],
55
                'pay_limit_date': invoice['ECHEANCE'],
56
                'online_payment': bool(invoice['MONTANT'] - invoice['ENCAISSE'] > 0),
57
            }
58
        )
59
    return data
passerelle/contrib/caluire_axel/xsd/Q_GetFacturesaPayer.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="PORTAILType">
7
		<xsd:sequence>
8
			<xsd:element ref="GETFACTURESAPAYER" minOccurs="0" maxOccurs="1"/>
9
		</xsd:sequence>  
10
	</xsd:complexType>
11
	
12
	<xsd:complexType name="GETFACTURESAPAYERType">
13
		<xsd:sequence>
14
			<xsd:element ref="IDENTFAMILLE"/>
15
			<xsd:element ref="IDENTREGIEFACT"/>
16
		</xsd:sequence>  
17
	</xsd:complexType>
18
	
19
	<xsd:element name="IDENTFAMILLE" type="all:IDENTREQUIREDType"/>
20
	<xsd:element name="IDENTREGIEFACT" type="all:IDREQUIREDType"/>
21
	
22
	<xsd:element name="GETFACTURESAPAYER" type="GETFACTURESAPAYERType"/>
23
	
24
	<xsd:element name="PORTAIL" type="PORTAILType"/>	
25
		
26
</xsd:schema>
passerelle/contrib/caluire_axel/xsd/R_GetFacturesaPayer.xsd
1
<?xml version="1.0" encoding="utf-8" ?>
2
<xsd:schema	xmlns:all="urn:AllAxelTypes" xmlns:ind="urn:Individu"  xmlns:xsd="http://www.w3.org/2001/XMLSchema" >
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="GetFacturesaPayer" />
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="GETFACTURESAPAYER" minOccurs="0" maxOccurs="1"/>
18
					</xsd:sequence>
19
				</xsd:extension>
20
			 </xsd:complexContent>
21
		</xsd:complexType>
22
	</xsd:redefine>	
23
	
24
	<xsd:complexType name="FACTUREType">
25
		<xsd:sequence>
26
			<xsd:element ref="IDFACTURE" />
27
			<xsd:element ref="MONTANT"/>
28
			<xsd:element ref="ENCAISSE"/>
29
			<xsd:element ref="FACTURATION"/>
30
			<xsd:element ref="EMISSION"/><!-- CUSTOM -->
31
			<xsd:element ref="ECHEANCE"/><!-- CUSTOM -->
32
			<xsd:element ref="EXISTEPDF"/>
33
		</xsd:sequence> 
34
	</xsd:complexType>
35
	
36
	<xsd:complexType name="GETFACTURESAPAYERType">
37
		<xsd:sequence>
38
			<xsd:element ref="CODE" />
39
			<xsd:element ref="FACTURE" minOccurs="0" maxOccurs="unbounded" />
40
		</xsd:sequence>
41
	</xsd:complexType>
42
	
43
	<xsd:element name="CODE" type="xsd:integer"/>	
44
	<xsd:element name="IDFACTURE" type="xsd:positiveInteger"/>
45
	<xsd:element name="MONTANT" type="all:MONTANTType"/>
46
	<xsd:element name="ENCAISSE" type="all:MONTANTType"/>
47
	<xsd:element name="FACTURATION" type="all:NOMType"/>
48
	<xsd:element name="EMISSION" type="all:DATEType"/><!-- CUSTOM -->
49
	<xsd:element name="ECHEANCE" type="all:DATEType"/><!-- CUSTOM -->
50
	<xsd:element name="EXISTEPDF" type="all:ONEmptyType"/>
51
	<xsd:element name="FACTURE" type="FACTUREType"/>
52
	
53
	<xsd:element name="GETFACTURESAPAYER" type="GETFACTURESAPAYERType"/>
54
	
55
</xsd:schema>
tests/data/caluire_axel/invoices.xml
1
<CODE>2</CODE>
2
<FACTURE>
3
  <IDFACTURE>42</IDFACTURE>
4
  <MONTANT>44.94</MONTANT>
5
  <ENCAISSE>40.00</ENCAISSE>
6
  <FACTURATION>PRESTATIONS PERISCOLAIRES SEPTEMBRE-OCTOBRE 2019</FACTURATION>
7
  <EMISSION>12/11/2019</EMISSION>
8
  <ECHEANCE>04/12/2019</ECHEANCE>
9
  <EXISTEPDF>o</EXISTEPDF>
10
</FACTURE>
11
<FACTURE>
12
  <IDFACTURE>43</IDFACTURE>
13
  <MONTANT>44.94</MONTANT>
14
  <ENCAISSE>0.00</ENCAISSE>
15
  <FACTURATION>PRESTATIONS PERISCOLAIRES NOVEMBRE 2019</FACTURATION>
16
  <EMISSION>12/12/2019</EMISSION>
17
  <ECHEANCE>04/01/2020</ECHEANCE>
18
  <EXISTEPDF>n</EXISTEPDF>
19
</FACTURE>
tests/test_caluire_axel.py
1164 1164
            'passerelle.contrib.caluire_axel.models.CaluireAxel.get_family_data',
1165 1165
            return_value=family_data,
1166 1166
        ):
1167 1167
            resp = app.get(
1168 1168
                '/caluire-axel/test/get_agenda?NameID=yyy&idpersonne=50632&activity_id=FOOBAR&start_date=2020-09-01&end_date=2021-08-31'
1169 1169
            )
1170 1170
    assert resp.json['err_desc'] == "Wrong agenda status"
1171 1171
    assert resp.json['err'] == 'agenda-code-error-%s' % code
1172

  
1173

  
1174
def test_invoices_endpoint_axel_error(app, resource):
1175
    Link.objects.create(resource=resource, name_id='yyy', family_id='XXX', person_id='42')
1176
    with mock.patch('passerelle.contrib.caluire_axel.schemas.get_factures_a_payer') as operation:
1177
        operation.side_effect = AxelError('FooBar')
1178
        resp = app.get('/caluire-axel/test/regie/MAREGIE/invoices?NameID=yyy')
1179
    assert resp.json['err_desc'] == "Axel error: FooBar"
1180
    assert resp.json['err'] == 'error'
1181

  
1182
    content = '''<PORTAIL>
1183
    <GETFACTURESAPAYER>
1184
        <CODE>-3</CODE>
1185
    </GETFACTURESAPAYER>
1186
</PORTAIL>'''
1187
    with mock_data(content, 'GetFacturesaPayer'):
1188
        resp = app.get('/caluire-axel/test/regie/MAREGIE/invoices?NameID=yyy')
1189
    assert resp.json['err_desc'] == "Wrong get-invoices status"
1190
    assert resp.json['err'] == 'get-invoices-code-error--3'
1191

  
1192

  
1193
def test_invoices_endpoint_no_result(app, resource):
1194
    resp = app.get('/caluire-axel/test/regie/MAREGIE/invoices?NameID=yyy')
1195
    assert resp.json['err_desc'] == "Person not found"
1196
    assert resp.json['err'] == 'not-found'
1197

  
1198

  
1199
def test_invoices_endpoint_no_invoice(app, resource):
1200
    Link.objects.create(resource=resource, name_id='yyy', family_id='XXX', person_id='42')
1201
    content = '''<PORTAIL>
1202
    <GETFACTURESAPAYER>
1203
        <CODE>0</CODE>
1204
    </GETFACTURESAPAYER>
1205
</PORTAIL>'''
1206
    with mock_data(content, 'GetFacturesaPayer'):
1207
        resp = app.get('/caluire-axel/test/regie/MAREGIE/invoices?NameID=yyy')
1208
    assert resp.json['err'] == 0
1209
    assert resp.json['data'] == []
1210

  
1211

  
1212
def test_invoices_endpoint(app, resource):
1213
    Link.objects.create(resource=resource, name_id='yyy', family_id='XXX', person_id='42')
1214
    filepath = os.path.join(os.path.dirname(__file__), 'data/caluire_axel/invoices.xml')
1215
    with open(filepath) as xml:
1216
        content = (
1217
            '''
1218
<PORTAIL>
1219
  <GETFACTURESAPAYER>
1220
    %s
1221
  </GETFACTURESAPAYER>
1222
</PORTAIL>'''
1223
            % xml.read()
1224
        )
1225
    with mock_data(content, 'GetFacturesaPayer'):
1226
        resp = app.get('/caluire-axel/test/regie/MAREGIE/invoices?NameID=yyy')
1227
    assert resp.json['err'] == 0
1228
    assert resp.json['data'] == [
1229
        {
1230
            'id': 'XXX-42',
1231
            'display_id': '42',
1232
            'label': 'PRESTATIONS PERISCOLAIRES SEPTEMBRE-OCTOBRE 2019',
1233
            'amount': '4.94',
1234
            'total_amount': '44.94',
1235
            'amount_paid': '40.00',
1236
            'online_payment': True,
1237
            'created': '2019-11-12',
1238
            'pay_limit_date': '2019-12-04',
1239
            'has_pdf': True,
1240
            'paid': False,
1241
            'vendor': {
1242
                'caluire-axel': {
1243
                    'IDFACTURE': 42,
1244
                    'MONTANT': '44.94',
1245
                    'ENCAISSE': '40.00',
1246
                    'FACTURATION': 'PRESTATIONS PERISCOLAIRES SEPTEMBRE-OCTOBRE 2019',
1247
                    'ECHEANCE': '2019-12-04',
1248
                    'EMISSION': '2019-11-12',
1249
                    'EXISTEPDF': True,
1250
                }
1251
            },
1252
        },
1253
        {
1254
            'id': 'XXX-43',
1255
            'display_id': '43',
1256
            'label': 'PRESTATIONS PERISCOLAIRES NOVEMBRE 2019',
1257
            'amount': '44.94',
1258
            'total_amount': '44.94',
1259
            'amount_paid': '0.00',
1260
            'online_payment': True,
1261
            'created': '2019-12-12',
1262
            'pay_limit_date': '2020-01-04',
1263
            'has_pdf': False,
1264
            'paid': False,
1265
            'vendor': {
1266
                'caluire-axel': {
1267
                    'IDFACTURE': 43,
1268
                    'FACTURATION': 'PRESTATIONS PERISCOLAIRES NOVEMBRE 2019',
1269
                    'MONTANT': '44.94',
1270
                    'ENCAISSE': '0.00',
1271
                    'ECHEANCE': '2020-01-04',
1272
                    'EMISSION': '2019-12-12',
1273
                    'EXISTEPDF': False,
1274
                }
1275
            },
1276
        },
1277
    ]
1172
-