Projet

Général

Profil

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

Nicolas Roche, 02 juin 2021 17:31

Télécharger (13,2 ko)

Voir les différences:

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

 passerelle/contrib/caluire_axel/models.py     |  60 ++++++++++
 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, 218 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 import dateformat
22 24
from django.utils.timezone import now
23 25
from django.utils.translation import ugettext_lazy as _
24 26

  
25 27
from passerelle.base.models import BaseResource
26 28
from passerelle.contrib.utils import axel
27 29
from passerelle.utils.api import endpoint
28 30
from passerelle.utils.jsonresponse import APIError
......
682 684
            historical=historical,
683 685
            nb_mounts_limit=nb_mounts_limit,
684 686
        )
685 687
        if invoice is None:
686 688
            raise APIError('Invoice not found', err_code='not-found')
687 689

  
688 690
        return {'data': invoice}
689 691

  
692
    @endpoint(
693
        display_category=_('Invoices'),
694
        display_order=4,
695
        name='regie',
696
        perm='can_access',
697
        pattern=r'^(?P<regie_id>[\w-]+)/invoice/(?P<invoice_id>(historical-)?\w+-\d+)/pdf/?$',
698
        example_pattern='{regie_id}/invoice/{invoice_id}/pdf',
699
        description=_('Get invoice as a PDF file'),
700
        parameters={
701
            'NameID': {'description': _('Publik ID')},
702
            'regie_id': {'description': _('Regie identifier'), 'example_value': 'ENF'},
703
            'invoice_id': {'description': _('Invoice identifier'), 'example_value': 'IDFAM-42'},
704
            'nb_mounts_limit': {'description': _('Number of months of history'), 'example_value': '12'},
705
        },
706
    )
707
    def invoice_pdf(self, request, regie_id, invoice_id, NameID, nb_mounts_limit='12'):
708
        # check that invoice is related to current user
709
        real_invoice_id = invoice_id.split('-')[-1]
710
        historical = invoice_id.startswith('historical-')
711
        try:
712
            link = self.get_link(NameID)
713
            invoice = self.get_invoice(
714
                regie_id=regie_id,
715
                invoice_id=real_invoice_id,
716
                family_id=link.family_id,
717
                historical=historical,
718
                nb_mounts_limit=nb_mounts_limit,
719
            )
720
        except APIError as e:
721
            e.http_status = 404
722
            raise
723
        if invoice is None:
724
            raise APIError('Invoice not found', err_code='not-found', http_status=404)
725
        # check that PDF is available
726
        if not invoice['has_pdf']:
727
            raise APIError('PDF not available', err_code='not-available', http_status=404)
728

  
729
        try:
730
            result = schemas.get_pdf_facture(
731
                self, {'PORTAIL': {'GETPDFFACTURE': {'IDFACTURE': int(invoice['display_id'])}}}
732
            )
733
        except axel.AxelError as e:
734
            raise APIError(
735
                'Axel error: %s' % e,
736
                err_code='error',
737
                http_status=404,
738
                data={'xml_request': e.xml_request, 'xml_response': e.xml_response},
739
            )
740
        b64content = base64.b64decode(
741
            result.json_response['DATA']['PORTAIL']['GETPDFFACTURE']['FACTUREPDF'] or ''
742
        )
743
        if not b64content:
744
            raise APIError('PDF error', err_code='error', http_status=404)
745
        response = HttpResponse(content_type='application/pdf')
746
        response['Content-Disposition'] = 'attachment; filename="%s.pdf"' % invoice_id
747
        response.write(b64content)
748
        return response
749

  
690 750

  
691 751
class Link(models.Model):
692 752
    resource = models.ForeignKey(CaluireAxel, on_delete=models.CASCADE)
693 753
    name_id = models.CharField(blank=False, max_length=256)
694 754
    family_id = models.CharField(blank=False, max_length=128)
695 755
    person_id = models.CharField(blank=False, max_length=128)
696 756

  
697 757
    class Meta:
passerelle/contrib/caluire_axel/schemas.py
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 82
get_factures_a_payer = Operation('GetFacturesaPayer')
83 83
get_list_factures = Operation('GetListFactures')
84
get_pdf_facture = Operation('GetPdfFacture')
84 85

  
85 86

  
86 87
LINK_SCHEMA = copy.deepcopy(
87 88
    find_individus.request_schema['properties']['PORTAIL']['properties']['FINDINDIVIDU']
88 89
)
89 90
for key in ['NAISSANCE', 'CODEPOSTAL', 'VILLE', 'TEL', 'MAIL']:
90 91
    LINK_SCHEMA['properties'].pop(key)
91 92
    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
1533 1533
                'ENCAISSE': '40.00',
1534 1534
                'FACTURATION': 'PRESTATIONS PERISCOLAIRES SEPTEMBRE-OCTOBRE 2019',
1535 1535
                'ECHEANCE': '2019-12-04',
1536 1536
                'EMISSION': '2019-11-12',
1537 1537
                'EXISTEPDF': True,
1538 1538
            },
1539 1539
        },
1540 1540
    }
1541

  
1542

  
1543
def test_invoice_pdf_endpoint_axel_error(app, resource):
1544
    Link.objects.create(resource=resource, name_id='yyy', family_id='XXX', person_id='42')
1545
    with mock.patch('passerelle.contrib.caluire_axel.schemas.get_factures_a_payer') as operation:
1546
        operation.side_effect = AxelError('FooBar')
1547
        resp = app.get('/caluire-axel/test/regie/MAREGIE/invoice/XXX-42/pdf?NameID=yyy', status=404)
1548
    assert resp.json['err_desc'] == "Axel error: FooBar"
1549
    assert resp.json['err'] == 'error'
1550

  
1551
    filepath = os.path.join(os.path.dirname(__file__), 'data/caluire_axel/invoices.xml')
1552
    with open(filepath) as xml:
1553
        content = (
1554
            '''<PORTAIL>
1555
  <GETFACTURESAPAYER>
1556
    %s
1557
  </GETFACTURESAPAYER>
1558
</PORTAIL>'''
1559
            % xml.read()
1560
        )
1561
    with mock_data(content, 'GetFacturesaPayer'):
1562
        with mock.patch('passerelle.contrib.caluire_axel.schemas.get_pdf_facture') as operation:
1563
            operation.side_effect = AxelError('FooBar')
1564
            resp = app.get('/caluire-axel/test/regie/MAREGIE/invoice/XXX-42/pdf?NameID=yyy', status=404)
1565
    assert resp.json['err_desc'] == "Axel error: FooBar"
1566
    assert resp.json['err'] == 'error'
1567

  
1568

  
1569
def test_invoice_pdf_endpoint_bad_request(app, resource):
1570
    Link.objects.create(resource=resource, name_id='yyy', family_id='XXX', person_id='42')
1571
    with mock_data(None, 'GetListFactures'):
1572
        resp = app.get(
1573
            '/caluire-axel/test/regie/MAREGIE/invoice/historical-XXX-42/pdf?NameID=yyy&nb_mounts_limit=not_a_number',
1574
            status=404,
1575
        )
1576
    assert resp.json['err_desc'] == "nb_mounts_limit must be an integer"
1577
    assert resp.json['err'] == 'bad-request'
1578

  
1579

  
1580
def test_invoice_pdf_endpoint_no_result(app, resource):
1581
    resp = app.get('/caluire-axel/test/regie/MAREGIE/invoice/XXX-42/pdf?NameID=yyy', status=404)
1582
    assert resp.json['err_desc'] == "Person not found"
1583
    assert resp.json['err'] == 'not-found'
1584

  
1585
    Link.objects.create(resource=resource, name_id='yyy', family_id='XXX', person_id='42')
1586
    filepath = os.path.join(os.path.dirname(__file__), 'data/caluire_axel/invoices.xml')
1587
    with open(filepath) as xml:
1588
        content = (
1589
            '''<PORTAIL>
1590
  <GETFACTURESAPAYER>
1591
    %s
1592
  </GETFACTURESAPAYER>
1593
</PORTAIL>'''
1594
            % xml.read()
1595
        )
1596
    with mock_data(content, 'GetFacturesaPayer'):
1597
        resp = app.get('/caluire-axel/test/regie/MAREGIE/invoice/XXX-35/pdf?NameID=yyy', status=404)
1598
    assert resp.json['err_desc'] == "Invoice not found"
1599
    assert resp.json['err'] == 'not-found'
1600

  
1601
    with mock_data(content, 'GetFacturesaPayer'):
1602
        resp = app.get('/caluire-axel/test/regie/MAREGIE/invoice/XXX-44/pdf?NameID=yyy', status=404)
1603
    assert resp.json['err_desc'] == "Invoice not found"
1604
    assert resp.json['err'] == 'not-found'
1605

  
1606
    with mock_data(content, 'GetFacturesaPayer'):
1607
        resp = app.get('/caluire-axel/test/regie/MAREGIE/invoice/XXX-43/pdf?NameID=yyy', status=404)
1608
    assert resp.json['err_desc'] == "PDF not available"
1609
    assert resp.json['err'] == 'not-available'
1610

  
1611
    pdf_content = '''<PORTAIL>
1612
    <GETPDFFACTURE>
1613
        <CODE>1</CODE>
1614
        <FACTUREPDF></FACTUREPDF>
1615
    </GETPDFFACTURE>
1616
</PORTAIL>'''
1617
    with mock.patch('passerelle.contrib.caluire_axel.models.CaluireAxel.get_invoice') as invoice:
1618
        invoice.return_value = {'has_pdf': True, 'display_id': '42'}
1619
        with mock_data(pdf_content, 'GetPdfFacture'):
1620
            resp = app.get('/caluire-axel/test/regie/MAREGIE/invoice/XXX-42/pdf?NameID=yyy', status=404)
1621
    assert resp.json['err_desc'] == "PDF error"
1622
    assert resp.json['err'] == 'error'
1623

  
1624

  
1625
def test_invoice_pdf_endpoint(app, resource):
1626
    Link.objects.create(resource=resource, name_id='yyy', family_id='XXX', person_id='42')
1627
    pdf_content = '''<PORTAIL>
1628
    <GETPDFFACTURE>
1629
        <CODE>1</CODE>
1630
        <FACTUREPDF>aGVsbG8gd29ybGQ=</FACTUREPDF>
1631
    </GETPDFFACTURE>
1632
</PORTAIL>'''
1633
    with mock.patch('passerelle.contrib.caluire_axel.models.CaluireAxel.get_invoice') as invoice:
1634
        invoice.return_value = {'has_pdf': True, 'display_id': '42'}
1635
        with mock_data(pdf_content, 'GetPdfFacture'):
1636
            app.get('/caluire-axel/test/regie/MAREGIE/invoice/XXX-42/pdf?NameID=yyy')
1637
    assert invoice.call_args_list[0][1]['historical'] is False
1638

  
1639
    with mock.patch('passerelle.contrib.caluire_axel.models.CaluireAxel.get_invoice') as invoice:
1640
        invoice.return_value = {'has_pdf': True, 'display_id': '42'}
1641
        with mock_data(pdf_content, 'GetPdfFacture'):
1642
            app.get('/caluire-axel/test/regie/MAREGIE/invoice/historical-XXX-42/pdf?NameID=yyy')
1643
    assert invoice.call_args_list[0][1]['historical'] is True
1541
-