Projet

Général

Profil

0001-add-avis-imposition.patch

Benjamin Dauvergne, 29 mai 2020 16:53

Télécharger (28,4 ko)

Voir les différences:

Subject: [PATCH] add avis imposition

 passerelle/apps/avis_imposition/__init__.py   |   0
 .../migrations/0001_initial.py                |  30 +++
 passerelle/apps/avis_imposition/models.py     | 193 ++++++++++++++++++
 passerelle/settings.py                        |   1 +
 setup.py                                      |   1 +
 tests/data/avis-imposition.html               |  85 ++++++++
 tests/data/avis-imposition.json               |  52 +++++
 tests/test_avis_imposition.py                 | 116 +++++++++++
 8 files changed, 478 insertions(+)
 create mode 100644 passerelle/apps/avis_imposition/__init__.py
 create mode 100644 passerelle/apps/avis_imposition/migrations/0001_initial.py
 create mode 100644 passerelle/apps/avis_imposition/models.py
 create mode 100644 tests/data/avis-imposition.html
 create mode 100644 tests/data/avis-imposition.json
 create mode 100644 tests/test_avis_imposition.py
passerelle/apps/avis_imposition/migrations/0001_initial.py
1
# -*- coding: utf-8 -*-
2
# Generated by Django 1.11.20 on 2020-05-28 19:49
3
from __future__ import unicode_literals
4

  
5
from django.db import migrations, models
6

  
7

  
8
class Migration(migrations.Migration):
9

  
10
    initial = True
11

  
12
    dependencies = [
13
        ('base', '0020_auto_20200515_1923'),
14
    ]
15

  
16
    operations = [
17
        migrations.CreateModel(
18
            name='AvisImposition',
19
            fields=[
20
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
21
                ('title', models.CharField(max_length=50, verbose_name='Title')),
22
                ('slug', models.SlugField(unique=True, verbose_name='Identifier')),
23
                ('description', models.TextField(verbose_name='Description')),
24
                ('users', models.ManyToManyField(blank=True, related_name='_avisimposition_users_+', related_query_name='+', to='base.ApiUser')),
25
            ],
26
            options={
27
                'verbose_name': 'API Particulier',
28
            },
29
        ),
30
    ]
passerelle/apps/avis_imposition/models.py
1
# coding: utf-8
2
# passerelle - uniform access to multiple data sources and services
3
# Copyright (C) 2017  Entr'ouvert
4
#
5
# This program is free software: you can redistribute it and/or modify it
6
# under the terms of the GNU Affero General Public License as published
7
# by the Free Software Foundation, either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# This program is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
# GNU Affero General Public License for more details.
14
#
15
# You should have received a copy of the GNU Affero General Public License
16
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
17

  
18
'''Interface with "Service de vérification des avis" of impots.gouv.fr
19
'''
20

  
21
from __future__ import unicode_literals
22

  
23
import decimal
24
import logging
25
import re
26

  
27
import lxml.html
28
import requests
29

  
30
from django.utils.translation import ugettext_lazy as _
31
from django.utils.six.moves.urllib_parse import urljoin
32

  
33
from passerelle.base.models import BaseResource
34
from passerelle.utils.api import endpoint
35
from passerelle.utils.jsonresponse import APIError
36
from passerelle.utils.xml import text_content
37

  
38
AVIS_IMPOSITION_GOUV_FR_URL = 'https://cfsmsp.impots.gouv.fr/secavis/'
39

  
40

  
41
def remove_spaces(value):
42
    return re.sub(r'\s', '', value)
43

  
44

  
45
FIELD_INDEXES = {
46
    'nom_declarant_1': 4,
47
    'nom_declarant_2': 5,
48
    'nom_de_naissance_declarant_1': 7,
49
    'nom_de_naissance_declarant_2': 8,
50
    'prenom_declarant_1': 10,
51
    'prenom_declarant_2': 11,
52
    'date_de_naissance_declarant_1': 13,
53
    'date_de_naissance_declarant_2': 14,
54
    'adresse_ligne_1': 16,
55
    'adresse_ligne_2': 19,
56
    'adresse_ligne_3': 21,
57
    'nombre_de_parts': 28,
58
    'situation_familiale': 30,
59
    'revenu_brut_global': 34,
60
    'revenu_imposable': 36,
61
    'impot_avant_correction': 38,
62
    'revenu_fiscal_de_reference': 42,
63
}
64

  
65

  
66
def nombre_de_parts(value):
67
    try:
68
        return decimal.Decimal(value)
69
    except (ValueError, TypeError):
70
        return None
71

  
72

  
73
def euros(value):
74
    if not value:
75
        return None
76
    value = remove_spaces(value)
77
    value = value.rstrip('€')
78
    return int(value)
79

  
80

  
81
def situation_familiale(value):
82
    if value == 'Pacs\xe9(e)s':
83
        return 'pacs'
84
    if value == 'Mari\xe9(e)s':
85
        return 'mariage'
86
    if value == 'Divorc\xe9(e)':
87
        return 'divorce'
88
    if value == 'C\xe9libataire':
89
        return 'celibataire'
90
    raise NotImplementedError(value)
91

  
92

  
93
ADAPTERS = {
94
    'nombre_de_parts': nombre_de_parts,
95
    'revenu_brut_global': euros,
96
    'revenu_imposable': euros,
97
    'impot_avant_correction': euros,
98
    'revenu_fiscal_de_reference': euros,
99
    'situation_familiale': situation_familiale,
100
}
101

  
102

  
103
def get_form(logger=None):
104
    logger = logger or logging
105
    try:
106
        response = requests.get(AVIS_IMPOSITION_GOUV_FR_URL)
107
        response.raise_for_status()
108
    except requests.RequestException as e:
109
        raise APIError('service-is-down', data=str(e))
110

  
111
    if 'Saisissez les identifiants' not in response.text:
112
        raise RuntimeError('service has changed')
113

  
114
    html = lxml.html.fromstring(response.content)
115
    data = {}
116
    for form_elt in html.xpath('//form'):
117
        if 'action' not in form_elt.attrib:
118
            continue
119
        action_url = form_elt.attrib['action']
120
        break
121
    else:
122
        raise RuntimeError('service has changed')
123
    logger.debug('using found action_url %s', action_url)
124

  
125
    for input_elt in html.xpath('//input'):
126
        if 'value' in input_elt.attrib:
127
            data[input_elt.attrib['name']] = input_elt.attrib['value']
128

  
129
    return action_url, data
130

  
131

  
132
def get_avis_imposition(numero_fiscal, reference_avis, logger=None):
133
    logger = logger or logging
134

  
135
    action_url, data = get_form()
136
    data['j_id_7:spi'] = numero_fiscal
137
    data['j_id_7:num_facture'] = reference_avis
138

  
139
    logger.debug('sending data %s', data)
140

  
141
    try:
142
        response = requests.post(urljoin(AVIS_IMPOSITION_GOUV_FR_URL, action_url), params=data)
143
        response.raise_for_status()
144
    except requests.RequestException as e:
145
        raise APIError('service-is-down', data=str(e))
146

  
147
    if 'Saisissez les identifiants' in response.text:
148
        raise APIError('not-found')
149

  
150
    response_html = lxml.html.fromstring(response.content)
151
    td_contents = [text_content(td).strip() for td in response_html.xpath('//td')]
152

  
153
    logger.debug('got td_contents %s', td_contents)
154

  
155
    data = {}
156
    for field, index in FIELD_INDEXES.items():
157
        try:
158
            data[field] = td_contents[index] or ''
159
        except IndexError:
160
            raise RuntimeError('service has changed')
161
        if field in ADAPTERS:
162
            data[field] = ADAPTERS[field](data[field])
163
    for situation_partielle_elt in response_html.xpath('//*[@id="situationPartielle"]'):
164
        data['situation_partielle'] = text_content(situation_partielle_elt).strip()
165
        break
166
    return data
167

  
168

  
169
class AvisImposition(BaseResource):
170
    @endpoint(perm='can_access',
171
              description=_('Get citizen\'s fiscal informations'),
172
              parameters={
173
                  'numero_fiscal': {
174
                      'description': _('fiscal identifier'),
175
                      'example_value': '1562456789521',
176
                  },
177
                  'reference_avis': {
178
                      'description': _('tax notice number'),
179
                      'example_value': '1512456789521',
180
                  },
181
              })
182
    def verify(self, request, numero_fiscal, reference_avis):
183
        numero_fiscal = remove_spaces(numero_fiscal)
184
        reference_avis = remove_spaces(reference_avis)
185
        return {'data': get_avis_imposition(numero_fiscal, reference_avis)}
186

  
187
    def check_status(self):
188
        get_form()
189

  
190
    category = _('Business Process Connectors')
191

  
192
    class Meta:
193
        verbose_name = _('API Particulier')
passerelle/settings.py
129 129
    'passerelle.apps.astregs',
130 130
    'passerelle.apps.atal',
131 131
    'passerelle.apps.atos_genesys',
132
    'passerelle.apps.avis_imposition',
132 133
    'passerelle.apps.base_adresse',
133 134
    'passerelle.apps.bdp',
134 135
    'passerelle.apps.cartads_cs',
setup.py
110 110
            'httplib2',
111 111
            'xmlschema',
112 112
            'pytz',
113
            'twill>=2',
113 114
        ],
114 115
        cmdclass={
115 116
            'build': build,
tests/data/avis-imposition.html
1
<!DOCTYPE html>
2
<html xmlns="http://www.w3.org/1999/xhtml"><head>
3
<meta http-equiv="Content-type" content="text/html; charset=UTF-8">
4

  
5
<title>Impots.gouv.fr - Service de vérification en ligne des avis</title>
6

  
7
<link href="coin_fichiers/style.css" rel="styleSheet" type="text/css">
8

  
9
<script type="text/javascript" src="coin_fichiers/fonctions.js"></script>
10

  
11
</head>
12
<body>
13
<div id="conteneur">
14
<div id="barre_haut">
15
			<div style="float: left;"><img src="coin_fichiers/bo_seule-2.gif" alt=""></div>
16
			<div style="float: right;"><img src="coin_fichiers/bo_seule-3.gif" alt=""></div>
17
</div>
18
<div id="principal">
19
<div id="nav_pro">
20
<b>Bienvenue sur le service de vérification des avis</b>
21
</div>
22

  
23
<div id="infoService"><p>Le service permet de vérifier l'authenticité des avis (Impôt sur le revenu) présentés par un usager</p></div>
24
<br>
25
<div class="titre"><span>Accès au service de vérification</span></div>
26
<br>
27
<div class="titre2">Saisissez les identifiants</div><form id="j_id_7" name="j_id_7" method="post" action="/secavis/faces/commun/index.jsf" enctype="application/x-www-form-urlencoded">
28
	<table>
29
		<tbody>
30
			<tr>
31
				<td width="290"> 
32
					<br><br>
33
				</td>
34
			</tr>		
35
			<tr>
36
				<td> 
37
					<label>Numéro fiscal *</label><a href="#" onclick="win = ouvrePopup('/secavis/faces/commun/aideSpi.jsf', 523, 375); win.focus();" tabindex="4"><img src="coin_fichiers/pic_aide_pro.gif" alt="aideSPI" style="vertical-align:middle"></a>					
38
				</td>
39
			</tr>
40
			<tr>
41
				<td><input id="j_id_7:spi" name="j_id_7:spi" type="text" maxlength="13" size="15" tabindex="1" autocomplete="off">
42
				</td>
43
				
44
			</tr>
45
			<tr>
46
				<td></td>
47
			</tr>
48
			<tr>
49
				<td>
50
				 <label>Référence de l'avis *</label><a href="#" onclick="win = ouvrePopup('/secavis/faces/commun/aideNumFacture.jsf', 520, 375); win.focus();" tabindex="5"><img src="coin_fichiers/pic_aide_pro.gif" alt="aideReferenceAvis" style="vertical-align:middle"></a>	
51
				</td>
52
			</tr>
53
			<tr>
54
				<td><input id="j_id_7:num_facture" name="j_id_7:num_facture" type="text" maxlength="13" size="15" tabindex="2" autocomplete="off">
55
				</td>
56
				
57
			</tr>
58
			
59

  
60
			<tr>
61
				<td></td>
62
			</tr>
63
			<tr>
64
				<td></td>
65
				<td></td>
66

  
67
				<td>
68
				<div align="right">
69
					
70
					<div class="bloc_boutons"><input id="j_id_7:j_id_l" name="j_id_7:j_id_l" type="submit" value="Valider" title="Vérifier les informations d'avis" class="valider" tabindex="3">
71
					</div>
72

  
73
			 	</div>
74
				</td>
75
			</tr>
76

  
77
		</tbody>
78
	</table><input type="hidden" name="j_id_7_SUBMIT" value="1"><input type="hidden" name="javax.faces.ViewState" id="j_id__v_0:javax.faces.ViewState:1" value="RxJe/1JKTJSr3aiM3H9DqZq0DrwqEXsY7Rw4eLRgEBsCF1IALJGqVgWTaQkiKbbdcGDWW774BWUCa/+j2CDznhw1/3bxJteY6ZCui66yNevhkej4xuyrFMte5KQnKORt9JZrOQ=="></form>
79
<br>
80
<div id="donneesObligatoires">* données obligatoires</div>
81
</div>
82
<div id="bas_page">© Ministère de l'Économie et des Finances</div><img src="coin_fichiers/hit.gif" alt="" width="1" height="1">
83
</div>
84

  
85
<div id="grammalecte_menu_main_button_shadow_host" style="width: 0px; height: 0px;"></div></body><script src="coin_fichiers/api.js"></script></html>
tests/data/avis-imposition.json
1
[
2
  {
3
    "numero_fiscal": "1234",
4
    "reference_avis": "abcd",
5
    "response": "<!DOCTYPE html>\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n\t\n<head>\n<meta http-equiv=\"Content-type\" content=\"text/html; charset=UTF-8\" />\n\n<title>Impots.gouv.fr - Service de v\u00e9rification en ligne des avis</title>\n<link href=\"/secavis/css/style.css\" rel=\"styleSheet\" type=\"text/css\" />\n</head>\n\n<body>\n\n<div id=\"conteneur\">\n<div id=\"barre_haut\">\n\t\t\t<div style=\"float: left;\"><img src=\"/secavis/img/bo_seule-2.gif\" alt=\"\" /></div>\n\t\t\t<div style=\"float: right;\"><img src=\"/secavis/img/bo_seule-3.gif\" alt=\"\" /></div>\n</div>\n<div id=\"principal\">\n<div id=\"nav_pro\">\n<b>L'administration fiscale certifie l'authenticit\u00e9 du document pr\u00e9sent\u00e9 pour les donn\u00e9es suivantes :</b>\n</div>\n\n<div class=\"titre_affiche_avis\">\n<span>Imp\u00f4t   2019 sur les revenus de l'ann\u00e9e  2018  \n</span>\n</div>\t\t\n\t\t\n\n\t\t\t<table>\n\t\t\t\t<tbody>\n\t\t\t\t<tr>\n\t\t\t\t\t<td class=\"label\">\n\t\t\t\t\t</td>\n\t\t\t\t\t<td class=\"label\">D\u00e9clarant 1\n\t\t\t\t\t</td>\n\t\t\t\t\t<td class=\"label\">D\u00e9clarant 2\n\t\t\t\t\t</td>\n\t\t\t\t</tr>\n\t\t\t\t<tr>\n\t\t\t\t\t<td class=\"labelPair\">Nom\n\t\t\t\t\t</td>\n\t\t\t\t\t<td class=\"labelPair\">DOE\n\t\t\t\t\t</td>\n\t\t\t\t\t<td class=\"labelPair\">DOE\n\t\t\t\t\t</td>\n\t\t\t\t</tr>\n\t\t\t\t<tr>\n\t\t\t\t\t<td class=\"labelImpair\">Nom de naissance\n\t\t\t\t\t</td>\n\t\t\t\t\t<td class=\"labelImpair\">DOE\n\t\t\t\t\t</td>\n\t\t\t\t\t<td class=\"labelImpair\">DOE\n\t\t\t\t\t</td>\n\t\t\t\t</tr>\n\t\t\t\t<tr>\n\t\t\t\t\t<td class=\"labelPair\">Pr\u00e9nom(s)\n\t\t\t\t\t</td>\n\t\t\t\t\t<td class=\"labelPair\">JOHN\n\t\t\t\t\t</td>\n\t\t\t\t\t<td class=\"labelPair\">JANE\n\t\t\t\t\t</td>\n\t\t\t\t</tr>\n\t\t\t\t<tr>\n\t\t\t\t\t<td class=\"labelImpair\">Date de naissance\n\t\t\t\t\t</td>\n\t\t\t\t\t<td class=\"labelImpair\">01/01/1970\n\t\t\t\t\t</td>\n\t\t\t\t\t<td class=\"labelImpair\">\n\t\t\t\t\t</td>\n\t\t\t\t</tr>\n\t\t\t\t<tr>\n\t\t\t\t\t<td class=\"labelPair\">\n\t\t\t\t\t\tAdresse d\u00e9clar\u00e9e au 1<sup>er</sup> janvier  2019\n\t\t\t\t\t</td>\n\t\t\t\t\t<td class=\"labelPair\">RÉSIDENCE DU CALVAIRE\n\t\t\t\t\t</td>\n\t\t\t\t\t<td class=\"labelPair\">\n\t\t\t\t\t</td>\n\t\t\t\t</tr>\n\t\t\t\t<tr>\n\t\t\t\t\t<td class=\"labelPair\">\n\t\t\t\t\t</td>\n\t\t\t\t\t<td colspan=\"2\" class=\"labelPair\">RUE VICTOR HUGO\n\t\t\t\t\t</td>\n\t\t\t\t</tr>\n\t\t\t\t\t<tr>\n\t\t\t\t\t<td class=\"labelPair\">\n\t\t\t\t\t</td>\n\t\t\t\t\t<td colspan=\"2\" class=\"labelPair\">75014 PARIS\n\t\t\t\t\t</td>\n\t\t\t\t\t</tr>\t\t\t\n\t\t\t\t<tr>\n\t\t\t\t\t<td class=\"espace\"></td>\n\t\t\t\t</tr>\n\t\t\t\t<tr>\n\t\t\t\t\t<td class=\"labelPair\" colspan=\"2\">Date de mise en recouvrement de l'avis d'imp\u00f4t\n\t\t\t\t\t</td>\n\t\t\t\t\t<td class=\"textPair\">31/12/2019\n\t\t\t\t\t</td>\n\t\t\t\t</tr>\t\t\n\t\t\t\t<tr>\n\t\t\t\t\t<td class=\"labelImpair\" colspan=\"2\">Date d'\u00e9tablissement\n\t\t\t\t\t</td>\n\t\t\t\t\t<td class=\"textImpair\">09/12/2019\n\t\t\t\t\t</td>\n\t\t\t\t</tr>\n\t\t\t\t<tr>\n\t\t\t\t\t<td class=\"labelPair\" colspan=\"2\">Nombre de part(s)\n\t\t\t\t\t</td>\n\t\t\t\t\t<td class=\"textPair\">4.00\n\t\t\t\t\t</td>\n\t\t\t\t</tr>\n\t\t\t\t<tr>\n\t\t\t\t\t<td class=\"labelImpair\" colspan=\"2\">Situation de famille\n\t\t\t\t\t</td>\n\t\t\t\t\t<td class=\"textImpair\">Pacs\u00e9(e)s\n\t\t\t\t\t</td>\n\t\t\t\t</tr>\n\t\t\t\t<tr>\n\t\t\t\t\t<td class=\"labelPair\" colspan=\"2\">Nombre de personne(s) \u00e0 charge\n\t\t\t\t\t</td>\n\t\t\t\t\t<td class=\"textPair\">4\n\t\t\t\t\t</td>\n\t\t\t\t</tr>\n\t\t\t\t<tr>\n\t\t\t\t\t<td class=\"labelImpair\" colspan=\"2\">Revenu brut global\n\t\t\t\t\t</td>\n\t\t\t\t\t<td class=\"textImpair\">48\u00a0473 \u20ac\n\t\t\t\t\t</td>\n\t\t\t\t</tr>\n\t\t\t\t<tr>\n\t\t\t\t\t<td class=\"labelPair\" colspan=\"2\">Revenu imposable\n\t\t\t\t\t</td>\n\t\t\t\t\t<td class=\"textPair\">48\u00a0473 \u20ac\n\t\t\t\t\t</td>\n\t\t\t\t</tr>\n\t\t\t\t<tr>\n\t\t\t\t\t<td class=\"labelImpair\" colspan=\"2\">Imp\u00f4t sur le revenu net avant corrections\n\t\t\t\t\t</td>\n\t\t\t\t\t<td class=\"textImpair\">112 \u20ac\n\t\t\t\t\t</td>\n\t\t\t\t</tr>\n\t\t\t\t<tr>\n\t\t\t\t\t<td class=\"labelPair\" colspan=\"2\">Montant de l'imp\u00f4t\n\t\t\t\t\t</td>\n\t\t\t\t\t<td class=\"textPair\">Non imposable\n\t\t\t\t\t</td>\n\t\t\t\t</tr>\t\t\t\t\t\t\t\t\n\t\t\t\t<tr>\n\t\t\t\t\t<td class=\"labelImpair\" colspan=\"2\">Revenu fiscal de r\u00e9f\u00e9rence\n\t\t\t\t\t</td>\n\t\t\t\t\t<td class=\"textImpair\">48\u00a0473 \u20ac\n\t\t\t\t\t</td>\n\t\t\t\t</tr>\t\t\t\t\t\t\t\n\n\t\t\t</tbody></table>\n\t\t\t\n\t\t\t<div id=\"situationPartielle\">\n\t\t\t</div>\n\t\t\t\t\n\t\t\t<div id=\"boutonsAvis\">\t\t\n\t\t\t\t<a class=\"nouvelle_recherche\" href=\"/secavis/\">\t\t\t\n\t\t\t\t\tNouvelle recherche\t\t\t\t\n\t\t\t\t</a>\n\t\t\t </div>\n\n\t\t</div>\n\t\t<div id=\"bas_page\">\u00a9 Minist\u00e8re de l'\u00c9conomie et des Finances</div><img src=\"https://logs1279.xiti.com/hit.xiti?s=532158&amp;s2=3&amp;p=AFFICHAGE::Avis_Sans_Correctif_erreur_pas_javascript&amp;\" alt=\"\" height=\"1\" width=\"1\" />\n\t</div>\n</body>\n</html>",
6
    "result": {
7
      "adresse_ligne_1": "RÉSIDENCE DU CALVAIRE",
8
      "adresse_ligne_2": "RUE VICTOR HUGO",
9
      "adresse_ligne_3": "75014 PARIS",
10
      "date_de_naissance_declarant_1": "01/01/1970",
11
      "date_de_naissance_declarant_2": "",
12
      "impot_avant_correction": 112,
13
      "nom_de_naissance_declarant_1": "DOE",
14
      "nom_de_naissance_declarant_2": "DOE",
15
      "nom_declarant_1": "DOE",
16
      "nom_declarant_2": "DOE",
17
      "nombre_de_parts": "4.00",
18
      "prenom_declarant_1": "JOHN",
19
      "prenom_declarant_2": "JANE",
20
      "revenu_brut_global": 48473,
21
      "revenu_fiscal_de_reference": 48473,
22
      "revenu_imposable": 48473,
23
      "situation_familiale": "pacs",
24
      "situation_partielle": ""
25
    }
26
  },
27
  {
28
    "numero_fiscal": "1234",
29
    "reference_avis": "abcd",
30
    "response": "<!DOCTYPE html>\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n\t\n<head>\n<meta http-equiv=\"Content-type\" content=\"text/html; charset=UTF-8\" />\n\n<title>Impots.gouv.fr - Service de v\u00e9rification en ligne des avis</title>\n<link href=\"/secavis/css/style.css\" rel=\"styleSheet\" type=\"text/css\" />\n</head>\n\n<body>\n\n<div id=\"conteneur\">\n<div id=\"barre_haut\">\n\t\t\t<div style=\"float: left;\"><img src=\"/secavis/img/bo_seule-2.gif\" alt=\"\" /></div>\n\t\t\t<div style=\"float: right;\"><img src=\"/secavis/img/bo_seule-3.gif\" alt=\"\" /></div>\n</div>\n<div id=\"principal\">\n<div id=\"nav_pro\">\n<b>L'administration fiscale certifie l'authenticit\u00e9 du document pr\u00e9sent\u00e9 pour les donn\u00e9es suivantes :</b>\n</div>\n\n<div class=\"titre_affiche_avis\">\n<span>Imp\u00f4t   2019 sur les revenus de l'ann\u00e9e  2018  \n</span>\n</div>\t\t\n\t\t\n\n\t\t\t<table>\n\t\t\t\t<tbody>\n\t\t\t\t<tr>\n\t\t\t\t\t<td class=\"label\">\n\t\t\t\t\t</td>\n\t\t\t\t\t<td class=\"label\">D\u00e9clarant 1\n\t\t\t\t\t</td>\n\t\t\t\t\t<td class=\"label\">D\u00e9clarant 2\n\t\t\t\t\t</td>\n\t\t\t\t</tr>\n\t\t\t\t<tr>\n\t\t\t\t\t<td class=\"labelPair\">Nom\n\t\t\t\t\t</td>\n\t\t\t\t\t<td class=\"labelPair\">DOE\n\t\t\t\t\t</td>\n\t\t\t\t\t<td class=\"labelPair\">\n\t\t\t\t\t</td>\n\t\t\t\t</tr>\n\t\t\t\t<tr>\n\t\t\t\t\t<td class=\"labelImpair\">Nom de naissance\n\t\t\t\t\t</td>\n\t\t\t\t\t<td class=\"labelImpair\">DOE\n\t\t\t\t\t</td>\n\t\t\t\t\t<td class=\"labelImpair\">\n\t\t\t\t\t</td>\n\t\t\t\t</tr>\n\t\t\t\t<tr>\n\t\t\t\t\t<td class=\"labelPair\">Pr\u00e9nom(s)\n\t\t\t\t\t</td>\n\t\t\t\t\t<td class=\"labelPair\">JOHN\n\t\t\t\t\t</td>\n\t\t\t\t\t<td class=\"labelPair\">\n\t\t\t\t\t</td>\n\t\t\t\t</tr>\n\t\t\t\t<tr>\n\t\t\t\t\t<td class=\"labelImpair\">Date de naissance\n\t\t\t\t\t</td>\n\t\t\t\t\t<td class=\"labelImpair\">01/01/1970\n\t\t\t\t\t</td>\n\t\t\t\t\t<td class=\"labelImpair\">\n\t\t\t\t\t</td>\n\t\t\t\t</tr>\n\t\t\t\t<tr>\n\t\t\t\t\t<td class=\"labelPair\">\n\t\t\t\t\t\tAdresse d\u00e9clar\u00e9e au 1<sup>er</sup> janvier  2019\n\t\t\t\t\t</td>\n\t\t\t\t\t<td class=\"labelPair\">RÉSIDENCE DU CALVAIRE\n\t\t\t\t\t</td>\n\t\t\t\t\t<td class=\"labelPair\">\n\t\t\t\t\t</td>\n\t\t\t\t</tr>\n\t\t\t\t<tr>\n\t\t\t\t\t<td class=\"labelPair\">\n\t\t\t\t\t</td>\n\t\t\t\t\t<td colspan=\"2\" class=\"labelPair\">RUE VICTOR HUGO\n\t\t\t\t\t</td>\n\t\t\t\t</tr>\n\t\t\t\t\t<tr>\n\t\t\t\t\t<td class=\"labelPair\">\n\t\t\t\t\t</td>\n\t\t\t\t\t<td colspan=\"2\" class=\"labelPair\">75014 PARIS\n\t\t\t\t\t</td>\n\t\t\t\t\t</tr>\t\t\t\n\t\t\t\t<tr>\n\t\t\t\t\t<td class=\"espace\"></td>\n\t\t\t\t</tr>\n\t\t\t\t<tr>\n\t\t\t\t\t<td class=\"labelPair\" colspan=\"2\">Date de mise en recouvrement de l'avis d'imp\u00f4t\n\t\t\t\t\t</td>\n\t\t\t\t\t<td class=\"textPair\">31/07/2019\n\t\t\t\t\t</td>\n\t\t\t\t</tr>\t\t\n\t\t\t\t<tr>\n\t\t\t\t\t<td class=\"labelImpair\" colspan=\"2\">Date d'\u00e9tablissement\n\t\t\t\t\t</td>\n\t\t\t\t\t<td class=\"textImpair\">09/07/2019\n\t\t\t\t\t</td>\n\t\t\t\t</tr>\n\t\t\t\t<tr>\n\t\t\t\t\t<td class=\"labelPair\" colspan=\"2\">Nombre de part(s)\n\t\t\t\t\t</td>\n\t\t\t\t\t<td class=\"textPair\">2.00\n\t\t\t\t\t</td>\n\t\t\t\t</tr>\n\t\t\t\t<tr>\n\t\t\t\t\t<td class=\"labelImpair\" colspan=\"2\">Situation de famille\n\t\t\t\t\t</td>\n\t\t\t\t\t<td class=\"textImpair\">Divorc\u00e9(e)\n\t\t\t\t\t</td>\n\t\t\t\t</tr>\n\t\t\t\t<tr>\n\t\t\t\t\t<td class=\"labelPair\" colspan=\"2\">Nombre de personne(s) \u00e0 charge\n\t\t\t\t\t</td>\n\t\t\t\t\t<td class=\"textPair\">2\n\t\t\t\t\t</td>\n\t\t\t\t</tr>\n\t\t\t\t<tr>\n\t\t\t\t\t<td class=\"labelImpair\" colspan=\"2\">Revenu brut global\n\t\t\t\t\t</td>\n\t\t\t\t\t<td class=\"textImpair\">48\u00a0473 \u20ac\n\t\t\t\t\t</td>\n\t\t\t\t</tr>\n\t\t\t\t<tr>\n\t\t\t\t\t<td class=\"labelPair\" colspan=\"2\">Revenu imposable\n\t\t\t\t\t</td>\n\t\t\t\t\t<td class=\"textPair\">48\u00a0473 \u20ac\n\t\t\t\t\t</td>\n\t\t\t\t</tr>\n\t\t\t\t<tr>\n\t\t\t\t\t<td class=\"labelImpair\" colspan=\"2\">Imp\u00f4t sur le revenu net avant corrections\n\t\t\t\t\t</td>\n\t\t\t\t\t<td class=\"textImpair\">5\u00a0084 \u20ac\n\t\t\t\t\t</td>\n\t\t\t\t</tr>\n\t\t\t\t<tr>\n\t\t\t\t\t<td class=\"labelPair\" colspan=\"2\">Montant de l'imp\u00f4t\n\t\t\t\t\t</td>\n\t\t\t\t\t<td class=\"textPair\">Non imposable\n\t\t\t\t\t</td>\n\t\t\t\t</tr>\t\t\t\t\t\t\t\t\n\t\t\t\t<tr>\n\t\t\t\t\t<td class=\"labelImpair\" colspan=\"2\">Revenu fiscal de r\u00e9f\u00e9rence\n\t\t\t\t\t</td>\n\t\t\t\t\t<td class=\"textImpair\">48\u00a0473 \u20ac\n\t\t\t\t\t</td>\n\t\t\t\t</tr>\t\t\t\t\t\t\t\n\n\t\t\t</tbody></table>\n\t\t\t\n\t\t\t<div id=\"situationPartielle\">\n\t\t\t</div>\n\t\t\t\t\n\t\t\t<div id=\"boutonsAvis\">\t\t\n\t\t\t\t<a class=\"nouvelle_recherche\" href=\"/secavis/\">\t\t\t\n\t\t\t\t\tNouvelle recherche\t\t\t\t\n\t\t\t\t</a>\n\t\t\t </div>\n\n\t\t</div>\n\t\t<div id=\"bas_page\">\u00a9 Minist\u00e8re de l'\u00c9conomie et des Finances</div><img src=\"https://logs1279.xiti.com/hit.xiti?s=532158&amp;s2=3&amp;p=AFFICHAGE::Avis_Sans_Correctif_erreur_pas_javascript&amp;\" alt=\"\" height=\"1\" width=\"1\" />\n\t</div>\n</body>\n</html>",
31
    "result": {
32
      "adresse_ligne_1": "RÉSIDENCE DU CALVAIRE",
33
      "adresse_ligne_2": "RUE VICTOR HUGO",
34
      "adresse_ligne_3": "75014 PARIS",
35
      "date_de_naissance_declarant_1": "01/01/1970",
36
      "date_de_naissance_declarant_2": "",
37
      "impot_avant_correction": 5084,
38
      "nom_de_naissance_declarant_1": "DOE",
39
      "nom_de_naissance_declarant_2": "",
40
      "nom_declarant_1": "DOE",
41
      "nom_declarant_2": "",
42
      "nombre_de_parts": "2.00",
43
      "prenom_declarant_1": "JOHN",
44
      "prenom_declarant_2": "",
45
      "revenu_brut_global": 48473,
46
      "revenu_fiscal_de_reference": 48473,
47
      "revenu_imposable": 48473,
48
      "situation_familiale": "divorce",
49
      "situation_partielle": ""
50
    }
51
  }
52
]
tests/test_avis_imposition.py
1
# passerelle - uniform access to multiple data sources and services
2
# Copyright (C) 2016 Entr'ouvert
3
#
4
# This program is free software: you can redistribute it and/or modify it
5
# under the terms of the GNU Affero General Public License as published
6
# by the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU Affero General Public License for more details.
13
#
14
# You should have received a copy of the GNU Affero General Public License
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

  
17
from __future__ import unicode_literals
18

  
19
import json
20

  
21
import requests
22

  
23
import httmock
24

  
25
import pytest
26

  
27
from passerelle.apps.avis_imposition.models import AvisImposition
28

  
29
import utils
30

  
31

  
32
# Content from the form page
33
with open('tests/data/avis-imposition.html') as fd:
34
    html = fd.read()
35

  
36
# Contents from the submit result page
37
with open('tests/data/avis-imposition.json') as fd:
38
    data = json.load(fd)
39

  
40

  
41
@pytest.fixture
42
def avis_imposition(db):
43
    return utils.make_resource(AvisImposition, slug='test')
44

  
45

  
46
@pytest.mark.parametrize('data', data)
47
def test_ok(avis_imposition, data, app):
48
    @httmock.urlmatch()
49
    def http_response(url, request):
50
        if request.method == 'GET':
51
            return html
52
        else:
53
            return data['response']
54

  
55
    with httmock.HTTMock(http_response):
56
        response = utils.endpoint_get(
57
            expected_url='/avis-imposition/test/verify',
58
            app=app,
59
            resource=avis_imposition,
60
            endpoint='verify',
61
            params={
62
                'numero_fiscal': data['numero_fiscal'],
63
                'reference_avis': data['reference_avis'],
64
            })
65
    assert {key: value for key, value in response.json['data'].items()} == data['result']
66

  
67

  
68
def test_not_found(avis_imposition, app):
69
    @httmock.urlmatch()
70
    def http_response(url, request):
71
        return html
72

  
73
    with httmock.HTTMock(http_response):
74
        response = utils.endpoint_get(
75
            expected_url='/avis-imposition/test/verify',
76
            app=app,
77
            resource=avis_imposition,
78
            endpoint='verify',
79
            params={
80
                'numero_fiscal': '1234',
81
                'reference_avis': 'abcd',
82
            })
83
    assert response.json['err_desc'] == 'not-found'
84

  
85

  
86
@pytest.mark.parametrize('error', [
87
    'exception-on-get',
88
    '500-on-get',
89
    'exception-on-post',
90
    '500-on-post',
91
])
92
def test_request_error(avis_imposition, app, error):
93
    @httmock.urlmatch()
94
    def http_response(url, request):
95
        if error == 'exception-on-get':
96
            raise requests.RequestException('boom')
97
        if error == '500-on-get':
98
            return {'status_code': 500}
99
        if request.method == 'GET':
100
            return html
101
        if error == 'exception-on-post':
102
            raise requests.RequestException('boom')
103
        if error == '500-on-post':
104
            return {'status_code': 500}
105

  
106
    with httmock.HTTMock(http_response):
107
        response = utils.endpoint_get(
108
            expected_url='/avis-imposition/test/verify',
109
            app=app,
110
            resource=avis_imposition,
111
            endpoint='verify',
112
            params={
113
                'numero_fiscal': '1234',
114
                'reference_avis': 'abcd',
115
            })
116
    assert response.json['err_desc'] == 'service-is-down'
0
-