0001-caluire-axel-add-invoices-endpoint-53884.patch
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 |
- |