0001-caluire-axel-add-invoices-endpoint-53884.patch
passerelle/contrib/caluire_axel/models.py | ||
---|---|---|
30 | 30 | |
31 | 31 |
class CaluireAxel(BaseResource): |
32 | 32 | |
33 | 33 |
wsdl_url = models.CharField( |
34 | 34 |
max_length=128, blank=False, verbose_name=_('WSDL URL'), help_text=_('Caluire Axel WSDL URL') |
35 | 35 |
) |
36 | 36 | |
37 | 37 |
category = _('Business Process Connectors') |
38 |
_category_ordering = [_('Family account'), _('Schooling')] |
|
38 |
_category_ordering = [_('Family account'), _('Schooling'), _('Invoices')]
|
|
39 | 39 | |
40 | 40 |
class Meta: |
41 | 41 |
verbose_name = _('Caluire Axel') |
42 | 42 | |
43 | 43 |
def check_status(self): |
44 | 44 |
response = self.requests.get(self.wsdl_url) |
45 | 45 |
response.raise_for_status() |
46 | 46 | |
... | ... | |
295 | 295 |
err_code='error', |
296 | 296 |
data={'xml_request': e.xml_request, 'xml_response': e.xml_response}, |
297 | 297 |
) |
298 | 298 | |
299 | 299 |
schooling_data = result.json_response['DATA']['PORTAIL']['GETINDIVIDU'] |
300 | 300 | |
301 | 301 |
return {'data': schooling_data} |
302 | 302 | |
303 |
def get_invoices(self, regie_id, name_id): |
|
304 |
link = self.get_link(name_id) |
|
305 |
try: |
|
306 |
result = schemas.get_factures_a_payer( |
|
307 |
self, |
|
308 |
{ |
|
309 |
'PORTAIL': { |
|
310 |
'GETFACTURESAPAYER': { |
|
311 |
'IDENTFAMILLE': link.family_id, |
|
312 |
'IDENTREGIEFACT': regie_id, |
|
313 |
} |
|
314 |
} |
|
315 |
}, |
|
316 |
) |
|
317 |
except axel.AxelError as e: |
|
318 |
raise APIError( |
|
319 |
'Axel error: %s' % e, |
|
320 |
err_code='error', |
|
321 |
data={'xml_request': e.xml_request, 'xml_response': e.xml_response}, |
|
322 |
) |
|
323 | ||
324 |
data = result.json_response['DATA']['PORTAIL']['GETFACTURESAPAYER'] |
|
325 |
result = [] |
|
326 | ||
327 |
for facture in data.get('FACTURE', []): |
|
328 |
result.append(utils.normalize_invoice(facture, link.family_id)) |
|
329 |
return result |
|
330 | ||
331 |
@endpoint( |
|
332 |
display_category=_('Invoices'), |
|
333 |
display_order=1, |
|
334 |
name='regie', |
|
335 |
perm='can_access', |
|
336 |
pattern=r'^(?P<regie_id>[\w-]+)/invoices/?$', |
|
337 |
example_pattern='{regie_id}/invoices', |
|
338 |
description=_("Get invoices to pay"), |
|
339 |
parameters={ |
|
340 |
'NameID': {'description': _('Publik ID')}, |
|
341 |
'regie_id': {'description': _('Regie identifier'), 'example_value': 'ENF'}, |
|
342 |
}, |
|
343 |
) |
|
344 |
def invoices(self, request, regie_id, NameID): |
|
345 |
invoices_data = self.get_invoices(regie_id=regie_id, name_id=NameID) |
|
346 |
return {'data': invoices_data} |
|
347 | ||
303 | 348 | |
304 | 349 |
class Link(models.Model): |
305 | 350 |
resource = models.ForeignKey(CaluireAxel, on_delete=models.CASCADE) |
306 | 351 |
name_id = models.CharField(blank=False, max_length=256) |
307 | 352 |
family_id = models.CharField(blank=False, max_length=128) |
308 | 353 |
person_id = models.CharField(blank=False, max_length=128) |
309 | 354 | |
310 | 355 |
class Meta: |
passerelle/contrib/caluire_axel/schemas.py | ||
---|---|---|
71 | 71 |
base_xsd_path = BASE_XSD_PATH |
72 | 72 |
axel_schema = CaluireAxelSchema |
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 |
get_factures_a_payer = Operation('GetFacturesaPayer') |
|
79 | 80 | |
80 | 81 | |
81 | 82 |
LINK_SCHEMA = copy.deepcopy( |
82 | 83 |
find_individus.request_schema['properties']['PORTAIL']['properties']['FINDINDIVIDU'] |
83 | 84 |
) |
84 | 85 |
for key in ['NAISSANCE', 'CODEPOSTAL', 'VILLE', 'TEL', 'MAIL']: |
85 | 86 |
LINK_SCHEMA['properties'].pop(key) |
86 | 87 |
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['DATEEMISSION'], |
|
55 |
'pay_limit_date': invoice['DATEECHEANCE'], |
|
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="DATEEMISSION"/> |
|
31 |
<xsd:element ref="DATEECHEANCE"/> |
|
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="DATEEMISSION" type="all:DATEType"/> |
|
49 |
<xsd:element name="DATEECHEANCE" type="all:DATEType"/> |
|
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 |
<DATEEMISSION>12/11/2019</DATEEMISSION> |
|
8 |
<DATEECHEANCE>04/12/2019</DATEECHEANCE> |
|
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 |
<DATEEMISSION>12/12/2019</DATEEMISSION> |
|
17 |
<DATEECHEANCE>04/01/2020</DATEECHEANCE> |
|
18 |
<EXISTEPDF>n</EXISTEPDF> |
|
19 |
</FACTURE> |
tests/test_caluire_axel.py | ||
---|---|---|
691 | 691 |
'passerelle.contrib.caluire_axel.models.CaluireAxel.get_family_data', |
692 | 692 |
return_value=family_data, |
693 | 693 |
): |
694 | 694 |
resp = app.get( |
695 | 695 |
'/caluire-axel/test/child_schooling_info?NameID=yyy&idpersonne=50632&schooling_date=2021-05-10' |
696 | 696 |
) |
697 | 697 |
assert resp.json['err'] == 0 |
698 | 698 |
assert set(resp.json['data'].keys()) == set(['CODE', 'INDIVIDU', 'SCOLAIRE']) |
699 | ||
700 | ||
701 |
def test_invoices_endpoint_axel_error(app, resource): |
|
702 |
Link.objects.create(resource=resource, name_id='yyy', family_id='XXX', person_id='42') |
|
703 |
with mock.patch('passerelle.contrib.caluire_axel.schemas.get_factures_a_payer') as operation: |
|
704 |
operation.side_effect = AxelError('FooBar') |
|
705 |
resp = app.get('/caluire-axel/test/regie/MAREGIE/invoices?NameID=yyy') |
|
706 |
assert resp.json['err_desc'] == "Axel error: FooBar" |
|
707 |
assert resp.json['err'] == 'error' |
|
708 | ||
709 | ||
710 |
def test_invoices_endpoint_no_result(app, resource): |
|
711 |
resp = app.get('/caluire-axel/test/regie/MAREGIE/invoices?NameID=yyy') |
|
712 |
assert resp.json['err_desc'] == "Person not found" |
|
713 |
assert resp.json['err'] == 'not-found' |
|
714 | ||
715 | ||
716 |
def test_invoices_endpoint_no_invoice(app, resource): |
|
717 |
Link.objects.create(resource=resource, name_id='yyy', family_id='XXX', person_id='42') |
|
718 |
content = '''<PORTAIL> |
|
719 |
<GETFACTURESAPAYER> |
|
720 |
<CODE>0</CODE> |
|
721 |
</GETFACTURESAPAYER> |
|
722 |
</PORTAIL>''' |
|
723 |
with mock_getdata(content, 'GetFacturesaPayer'): |
|
724 |
resp = app.get('/caluire-axel/test/regie/MAREGIE/invoices?NameID=yyy') |
|
725 |
assert resp.json['err'] == 0 |
|
726 |
assert resp.json['data'] == [] |
|
727 | ||
728 | ||
729 |
def test_invoices_endpoint(app, resource): |
|
730 |
Link.objects.create(resource=resource, name_id='yyy', family_id='XXX', person_id='42') |
|
731 |
filepath = os.path.join(os.path.dirname(__file__), 'data/caluire_axel/invoices.xml') |
|
732 |
with open(filepath) as xml: |
|
733 |
content = ( |
|
734 |
''' |
|
735 |
<PORTAIL> |
|
736 |
<GETFACTURESAPAYER> |
|
737 |
%s |
|
738 |
</GETFACTURESAPAYER> |
|
739 |
</PORTAIL>''' |
|
740 |
% xml.read() |
|
741 |
) |
|
742 |
with mock_getdata(content, 'GetFacturesaPayer'): |
|
743 |
resp = app.get('/caluire-axel/test/regie/MAREGIE/invoices?NameID=yyy') |
|
744 |
assert resp.json['err'] == 0 |
|
745 |
assert resp.json['data'] == [ |
|
746 |
{ |
|
747 |
'id': 'XXX-42', |
|
748 |
'display_id': '42', |
|
749 |
'label': 'PRESTATIONS PERISCOLAIRES SEPTEMBRE-OCTOBRE 2019', |
|
750 |
'amount': '4.94', |
|
751 |
'total_amount': '44.94', |
|
752 |
'amount_paid': '40.00', |
|
753 |
'online_payment': True, |
|
754 |
'created': '2019-11-12', |
|
755 |
'pay_limit_date': '2019-12-04', |
|
756 |
'has_pdf': True, |
|
757 |
'paid': False, |
|
758 |
'vendor': { |
|
759 |
'caluire-axel': { |
|
760 |
'IDFACTURE': 42, |
|
761 |
'MONTANT': '44.94', |
|
762 |
'ENCAISSE': '40.00', |
|
763 |
'FACTURATION': 'PRESTATIONS PERISCOLAIRES SEPTEMBRE-OCTOBRE 2019', |
|
764 |
'DATEECHEANCE': '2019-12-04', |
|
765 |
'DATEEMISSION': '2019-11-12', |
|
766 |
'EXISTEPDF': True, |
|
767 |
} |
|
768 |
}, |
|
769 |
}, |
|
770 |
{ |
|
771 |
'id': 'XXX-43', |
|
772 |
'display_id': '43', |
|
773 |
'label': 'PRESTATIONS PERISCOLAIRES NOVEMBRE 2019', |
|
774 |
'amount': '44.94', |
|
775 |
'total_amount': '44.94', |
|
776 |
'amount_paid': '0.00', |
|
777 |
'online_payment': True, |
|
778 |
'created': '2019-12-12', |
|
779 |
'pay_limit_date': '2020-01-04', |
|
780 |
'has_pdf': False, |
|
781 |
'paid': False, |
|
782 |
'vendor': { |
|
783 |
'caluire-axel': { |
|
784 |
'IDFACTURE': 43, |
|
785 |
'FACTURATION': 'PRESTATIONS PERISCOLAIRES NOVEMBRE 2019', |
|
786 |
'MONTANT': '44.94', |
|
787 |
'ENCAISSE': '0.00', |
|
788 |
'DATEECHEANCE': '2020-01-04', |
|
789 |
'DATEEMISSION': '2019-12-12', |
|
790 |
'EXISTEPDF': False, |
|
791 |
} |
|
792 |
}, |
|
793 |
}, |
|
794 |
] |
|
699 |
- |