Projet

Général

Profil

0001-cartads_cs-cache-data-35722.patch

Frédéric Péters, 31 août 2019 14:25

Télécharger (23,6 ko)

Voir les différences:

Subject: [PATCH] cartads_cs: cache data (#35722)

 .../migrations/0002_cartadsdatacache.py       |  26 +++
 passerelle/apps/cartads_cs/models.py          | 194 ++++++++++++++----
 tests/test_cartads_cs.py                      | 178 ++++++++--------
 3 files changed, 273 insertions(+), 125 deletions(-)
 create mode 100644 passerelle/apps/cartads_cs/migrations/0002_cartadsdatacache.py
passerelle/apps/cartads_cs/migrations/0002_cartadsdatacache.py
1
# -*- coding: utf-8 -*-
2
# Generated by Django 1.11.12 on 2019-08-31 09:48
3
from __future__ import unicode_literals
4

  
5
from django.db import migrations, models
6
import jsonfield.fields
7

  
8

  
9
class Migration(migrations.Migration):
10

  
11
    dependencies = [
12
        ('cartads_cs', '0001_initial'),
13
    ]
14

  
15
    operations = [
16
        migrations.CreateModel(
17
            name='CartaDSDataCache',
18
            fields=[
19
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
20
                ('data_type', models.CharField(max_length=50)),
21
                ('data_parameters', jsonfield.fields.JSONField(default={})),
22
                ('data_values', jsonfield.fields.JSONField(default={})),
23
                ('last_update_datetime', models.DateTimeField(auto_now=True)),
24
            ],
25
        ),
26
    ]
passerelle/apps/cartads_cs/models.py
25 25

  
26 26
from Crypto.Cipher import AES
27 27

  
28
from django.conf import settings
28 29
from django.core.files.storage import default_storage
29 30
from django.core.signing import Signer
30 31
from django.core.urlresolvers import reverse
......
33 34
from django.utils.translation import ugettext_lazy as _
34 35
from django.utils.six.moves.urllib import parse as urlparse
35 36

  
37
from jsonfield import JSONField
38

  
36 39
from passerelle.base.models import BaseResource
37 40
from passerelle.utils.api import endpoint
38 41

  
......
41 44
    return 'cartads_cs/%s/%s' % (instance.tracking_code, filename)
42 45

  
43 46

  
47
class CartaDSDataCache(models.Model):
48
    data_type = models.CharField(max_length=50)
49
    data_parameters = JSONField(default={})
50
    data_values = JSONField(default={})
51
    last_update_datetime = models.DateTimeField(auto_now=True)
52

  
53

  
44 54
class CartaDSFile(models.Model):
45 55
    tracking_code = models.CharField(max_length=20)
46 56
    id_piece = models.CharField(max_length=20)
......
137 147
        'description': _('Token for upload file'),
138 148
    }
139 149

  
140
    @endpoint(description=_('Get list of collectivities'))
141
    def communes(self, request):
150
    def update_data_cache(self):
142 151
        client = self.soap_client()
152

  
153
        # communes
143 154
        resp = client.service.GetCommunes(self.get_token())
144
        return {'data': [{'id': str(x['Key']), 'text': x['Value']} for x in resp]}
155
        communes_cache, created = CartaDSDataCache.objects.get_or_create(data_type='communes')
156
        communes_cache.data_values = {'data': [{'id': str(x['Key']), 'text': x['Value']} for x in resp]}
157
        communes_cache.save()
158

  
159
        # types dossier
160
        types_dossier_ids = {}
161
        for commune in communes_cache.data_values['data']:
162
            resp = client.service.GetTypesDossier(self.get_token(), int(commune['id']))
163
            if resp is None:
164
                continue
165
            data_cache, created = CartaDSDataCache.objects.get_or_create(
166
                    data_type='types_dossier',
167
                    data_parameters={'commune_id': int(commune['id'])})
168
            data_cache.data_values = {'data': [{'id': str(x['Key']), 'text': x['Value']} for x in resp]}
169
            types_dossier_ids.update({x['id']: True for x in data_cache.data_values['data']})
170
            data_cache.save()
171

  
172
        # objets_demande
173
        objets_demande_ids = {}
174
        for type_dossier_id in types_dossier_ids.keys():
175
            resp = client.service.GetObjetsDemande(self.get_token(), type_dossier_id)
176
            if resp is None:
177
                continue
178
            data_cache, created = CartaDSDataCache.objects.get_or_create(
179
                    data_type='objets_demande',
180
                    data_parameters={'type_dossier_id': type_dossier_id})
181
            data_cache.data_values = {'data': [{'id': str(x['Key']), 'text': x['Value']} for x in resp]}
182
            objets_demande_ids.update({x['id']: True for x in data_cache.data_values['data']})
183
            data_cache.save()
184

  
185
        # liste_pdf
186
        pdfs_path = default_storage.path('public/cartads_cs/%s/documents' % self.slug)
187
        if not os.path.exists(pdfs_path):
188
            os.makedirs(pdfs_path)
189
        for type_compte in [1]:
190
            for type_dossier_id in types_dossier_ids.keys():
191
                resp = client.service.GetListePdf(self.get_token(), type_dossier_id,
192
                        {'TypeCompteUtilisateur': type_compte})
193
                if resp is None:
194
                    continue
195

  
196
                def format_cerfa_label(x):
197
                    try:
198
                        if x['Description']:
199
                            return u'%(Nom)s: %(Description)s' % x
200
                    except KeyError:
201
                        pass
202
                    return u'%(Nom)s' % x
203

  
204
                data_cache, created = CartaDSDataCache.objects.get_or_create(
205
                        data_type='liste_pdf',
206
                        data_parameters={
207
                            'type_dossier_id': type_dossier_id,
208
                            'type_compte': type_compte,
209
                            })
210
                data_cache.data_values = {'data': [
211
                    {'id': x['Identifiant'],
212
                     'text': format_cerfa_label(x),
213
                     'url': x['UrlTelechargement'],
214
                    } for x in resp or []]}
215

  
216
                for value in data_cache.data_values['data']:
217
                    filepath = os.path.join(default_storage.path(self.pdf_path(value)))
218
                    resp = self.requests.get(value['url'])
219
                    if resp.ok:
220
                        with open(filepath, 'wb') as fd:
221
                            fd.write(resp.content)
222

  
223
                data_cache.save()
224

  
225
        # pieces
226
        for type_dossier_id in types_dossier_ids.keys():
227
            for objet_demande_id in objets_demande_ids.keys():
228
                resp = client.service.GetPieces(self.get_token(), type_dossier_id, objet_demande_id)
229
                if resp is None:
230
                    continue
231
                data_cache, created = CartaDSDataCache.objects.get_or_create(
232
                        data_type='pieces',
233
                        data_parameters={
234
                            'type_dossier_id': type_dossier_id,
235
                            'objet_demande_id': str(objet_demande_id),
236
                            })
237
                if resp is not None:
238
                    data_cache.data_values = {'data': [
239
                        {'id': str(x['IdPiece']),
240
                            'text': x['Libelle'],
241
                            'description': x['Descriptif'],
242
                            'codePiece': x['CodePiece'],
243
                            'reglementaire': x['Reglementaire'],
244
                            'files': [],
245
                            'max_files': 6,
246
                            } for x in resp]}
247
                data_cache.save()
248

  
249
    def hourly(self):
250
        super(AbstractCartaDSCS, self).hourly()
251
        if CartaDSDataCache.objects.count() == 0:
252
            # don't wait for daily job to get initial data
253
            self.update_data_cache()
254

  
255
    def daily(self):
256
        super(AbstractCartaDSCS, self).daily()
257
        self.update_data_cache()
145 258

  
146
    @endpoint(description=_('Get lisf of file types'),
259
    @endpoint(description=_('Get list of collectivities'))
260
    def communes(self, request):
261
        cache = CartaDSDataCache.objects.get(data_type='communes')
262
        return cache.data_values
263

  
264
    @endpoint(description=_('Get list of file types'),
147 265
              parameters={
148 266
                  'commune_id': COMMUNE_ID_PARAM
149 267
              })
150 268
    def types_dossier(self, request, commune_id):
151
        client = self.soap_client()
152
        resp = client.service.GetTypesDossier(self.get_token(), int(commune_id))
153
        return {'data': [{'id': x['Key'], 'text': x['Value']} for x in resp]}
269
        cache = CartaDSDataCache.objects.get(
270
                    data_type='types_dossier',
271
                    data_parameters={'commune_id': int(commune_id)})
272
        return cache.data_values
154 273

  
155 274
    @endpoint(description=_('Get list of demand subjects'),
156 275
              parameters={'type_dossier_id': TYPE_DOSSIER_ID_PARAM},
157 276
              )
158 277
    def objets_demande(self, request, type_dossier_id):
159
        client = self.soap_client()
160
        resp = client.service.GetObjetsDemande(self.get_token(), type_dossier_id)
161
        return {'data': [{'id': str(x['Key']), 'text': x['Value']} for x in resp or []]}
278
        cache = CartaDSDataCache.objects.get(
279
                    data_type='objets_demande',
280
                    data_parameters={'type_dossier_id': type_dossier_id})
281
        return cache.data_values
162 282

  
163 283
    @endpoint(description=_('Get list of CERFA documents'),
164 284
              parameters={
......
166 286
                  'type_compte': {'description': _('Type of account')},
167 287
              })
168 288
    def liste_pdf(self, request, type_dossier_id, type_compte=1):
169
        client = self.soap_client()
170
        resp = client.service.GetListePdf(self.get_token(), type_dossier_id,
171
                {'TypeCompteUtilisateur': type_compte})
172
        def format_cerfa_label(x):
173
            try:
174
                if x['Description']:
175
                    return u'%(Nom)s: %(Description)s' % x
176
            except KeyError:
177
                pass
178
            return u'%(Nom)s' % x
179
        return {'data': [
180
            {'id': x['Identifiant'],
181
             'text': format_cerfa_label(x),
182
             'url': x['UrlTelechargement'],
183
            } for x in resp or []]}
289
        cache = CartaDSDataCache.objects.get(
290
                    data_type='liste_pdf',
291
                    data_parameters={
292
                        'type_dossier_id': type_dossier_id,
293
                        'type_compte': type_compte,
294
                        })
295
        if request:  # point to local documents cache
296
            for pdf in cache.data_values['data']:
297
                pdf['url'] = request.build_absolute_uri(
298
                        os.path.join(settings.MEDIA_URL, self.pdf_path(pdf)))
299
        return cache.data_values
300

  
301
    def pdf_path(self, pdf):
302
        if '*' in pdf['id']:  # cerfa
303
            filename = 'cerfa_%s.pdf' % pdf['id'].replace('*', '-')
304
        else:
305
            filename = '%s.pdf' % pdf['id']
306
        return os.path.join('public/cartads_cs', self.slug, 'documents', filename)
307

  
184 308

  
185 309
    @endpoint(perm='can_access',
186 310
              description=_('Get list of file items'),
......
190 314
                  'tracking_code': TRACKING_CODE_PARAM,
191 315
              })
192 316
    def pieces(self, request, type_dossier_id, objet_demande_id, tracking_code):
193
        client = self.soap_client()
194
        resp = client.service.GetPieces(self.get_token(), type_dossier_id,
195
                objet_demande_id)
317
        cache, created = CartaDSDataCache.objects.get_or_create(
318
                        data_type='pieces',
319
                        data_parameters={
320
                            'type_dossier_id': type_dossier_id,
321
                            'objet_demande_id': objet_demande_id,
322
                            })
323

  
196 324
        signer = Signer(salt='cart@ds_cs')
197 325
        upload_token = signer.sign(tracking_code)
198 326
        cerfa_pieces = [
......
214 342
             'max_files': 6,
215 343
            }
216 344
        ]
217
        pieces = [
218
            {'id': str(x['IdPiece']),
219
             'text': x['Libelle'],
220
             'description': x['Descriptif'],
221
             'codePiece': x['CodePiece'],
222
             'reglementaire': x['Reglementaire'],
223
             'files': [],
224
             'max_files': 6,
225
            } for x in resp]
345
        pieces = cache.data_values['data']
226 346
        required_pieces = [x for x in pieces if x['reglementaire']]
227 347
        if required_pieces:
228 348
            required_pieces[0]['section_start'] = 'Pièces réglementaires'
tests/test_cartads_cs.py
3 3
import datetime
4 4

  
5 5
import mock
6
from httmock import HTTMock
6 7
import pytest
7 8

  
8 9
from django.core.files.storage import default_storage
......
76 77
            }]
77 78

  
78 79

  
79
def test_communes(connector, app):
80
    with mock.patch('passerelle.apps.cartads_cs.models.CartaDSCS.soap_client') as client:
81
        client.return_value = mock.Mock(service=FakeService())
82
        resp = app.get('/cartads-cs/test/communes')
83
        assert resp.json == {'data': [{'text': 'AIGREFEUILLE SUR MAINE', 'id': '2'}], 'err': 0}
80
def pdf_mock(url, request):
81
    return {'content': '%PDF...', 'status_code': 200}
84 82

  
85
def test_types_dossier(connector, app):
86
    with mock.patch('passerelle.apps.cartads_cs.models.CartaDSCS.soap_client') as client:
87
        client.return_value = mock.Mock(service=FakeService())
88
        resp = app.get('/cartads-cs/test/types_dossier', status=400)
89
        resp = app.get('/cartads-cs/test/types_dossier?commune_id=1')
90
        assert resp.json == {'data': [{'id': 'CU', 'text': "Certificat d'urbanisme"}], 'err': 0}
91 83

  
92
def test_objets_demande(connector, app):
93
    with mock.patch('passerelle.apps.cartads_cs.models.CartaDSCS.soap_client') as client:
94
        client.return_value = mock.Mock(service=FakeService())
95
        resp = app.get('/cartads-cs/test/objets_demande?type_dossier_id=CU')
96
        assert resp.json == {'data': [{'id': '1', 'text': "CU d'information"}], 'err': 0}
97

  
98
def test_liste_pdf(connector, app):
99
    with mock.patch('passerelle.apps.cartads_cs.models.CartaDSCS.soap_client') as client:
100
        client.return_value = mock.Mock(service=FakeService())
101
        resp = app.get('/cartads-cs/test/liste_pdf?type_dossier_id=CU')
102
        assert resp.json == {'data': [{'id': '13410*04',
103
                                       'text': "Cerfa 13410-04: Demande de Certificat d'urbanisme",
104
                                       'url': 'https://invalid/adscs/webservices/ServicePDF.ashx?pdf=13410*04'}],
105
                             'err': 0}
106

  
107
def test_pieces_management(connector, app):
84
@pytest.fixture
85
def cached_data(connector, app):
108 86
    with mock.patch('passerelle.apps.cartads_cs.models.CartaDSCS.soap_client') as client:
109 87
        client.return_value = mock.Mock(service=FakeService())
110
        resp = app.get('/cartads-cs/test/pieces?type_dossier_id=CU&objet_demande_id=1&tracking_code=BBBBBBBB')
111
        data = resp.json['data']
112
        assert len(data) == 4
113
        assert data[0]['text'] == 'Cerfa rempli'
114
        assert data[0]['max_files'] == 1
115
        assert data[1]['text'] == u'Cerfa demandeurs complémentaires'
116
        assert data[1]['max_files'] == 6
117
        assert data[2]['text'] == 'Plan de situation du terrain'
118
        assert data[2]['max_files'] == 6
119
        assert data[3]['text'] == 'DECLARATION PREALABLE INCOMPLETE'
120
        assert data[3]['max_files'] == 6
121
        for piece in data:
122
            assert len(piece['files']) == 1
123
            assert piece['files'][0].keys() == ['url']
124

  
125
        resp = app.get('/cartads-cs/test/check_pieces?type_dossier_id=CU&objet_demande_id=1&tracking_code=BBBBBBBB')
126
        assert resp.json == {'result': False, 'err': 0}
127

  
128
        resp = app.post(data[0]['files'][0]['url'],
129
                        upload_files=[('files[]', 'test.pdf', '%PDF...')])
130
        cerfa_token = resp.json[0]['token']
131

  
132
        resp = app.get('/cartads-cs/test/pieces?type_dossier_id=CU&objet_demande_id=1&tracking_code=BBBBBBBB')
133
        data = resp.json['data']
134
        assert data[0]['files'][0]['name']
135

  
136
        resp = app.post(data[0]['files'][0]['url'] + '%s/delete/' % cerfa_token)
137
        assert resp.json == {'err': 0}
138

  
139
        resp = app.get('/cartads-cs/test/pieces?type_dossier_id=CU&objet_demande_id=1&tracking_code=BBBBBBBB')
140
        data = resp.json['data']
141
        assert 'name' not in data[0]['files'][0]
142

  
143
        resp = app.post(data[0]['files'][0]['url'],
144
                        upload_files=[('files[]', 'test.pdf', '%PDF...')])
145

  
146
        resp = app.post(data[1]['files'][0]['url'],
147
                        upload_files=[('files[]', 'test.pdf', '%PDF...')])
148
        resp = app.post(data[1]['files'][0]['url'],
149
                        upload_files=[('files[]', 'test.pdf', '%PDF...')])
150
        resp = app.get('/cartads-cs/test/pieces?type_dossier_id=CU&objet_demande_id=1&tracking_code=BBBBBBBB')
151
        data = resp.json['data']
152
        assert len(data[1]['files']) == 3
153

  
154
        resp = app.get('/cartads-cs/test/check_pieces?type_dossier_id=CU&objet_demande_id=1&tracking_code=BBBBBBBB')
155
        assert resp.json == {'result': False, 'err': 0}
156

  
157
        resp = app.post(data[2]['files'][0]['url'],
158
                        upload_files=[('files[]', 'test.pdf', '%PDF...')])
159

  
160
        resp = app.get('/cartads-cs/test/check_pieces?type_dossier_id=CU&objet_demande_id=1&tracking_code=BBBBBBBB')
161
        assert resp.json == {'result': True, 'err': 0}
162

  
163
def test_send(connector, app):
88
        with HTTMock(pdf_mock):
89
            connector.hourly()
90

  
91
def test_communes(connector, app, cached_data):
92
    resp = app.get('/cartads-cs/test/communes')
93
    assert resp.json == {'data': [{'text': 'AIGREFEUILLE SUR MAINE', 'id': '2'}], 'err': 0}
94

  
95
def test_types_dossier(connector, app, cached_data):
96
    resp = app.get('/cartads-cs/test/types_dossier', status=400)
97
    resp = app.get('/cartads-cs/test/types_dossier?commune_id=2')
98
    assert resp.json == {'data': [{'id': 'CU', 'text': "Certificat d'urbanisme"}], 'err': 0}
99

  
100
def test_objets_demande(connector, app, cached_data):
101
    resp = app.get('/cartads-cs/test/objets_demande?type_dossier_id=CU')
102
    assert resp.json == {'data': [{'id': '1', 'text': "CU d'information"}], 'err': 0}
103

  
104
def test_liste_pdf(connector, app, cached_data):
105
    resp = app.get('/cartads-cs/test/liste_pdf?type_dossier_id=CU')
106
    assert resp.json == {'data': [{'id': '13410*04',
107
                                   'text': "Cerfa 13410-04: Demande de Certificat d'urbanisme",
108
                                   'url': 'http://testserver/media/public/cartads_cs/test/documents/cerfa_13410-04.pdf'}],
109
                         'err': 0}
110

  
111
def test_pieces_management(connector, app, cached_data):
112
    resp = app.get('/cartads-cs/test/pieces?type_dossier_id=CU&objet_demande_id=1&tracking_code=BBBBBBBB')
113
    data = resp.json['data']
114
    assert len(data) == 4
115
    assert data[0]['text'] == 'Cerfa rempli'
116
    assert data[0]['max_files'] == 1
117
    assert data[1]['text'] == u'Cerfa demandeurs complémentaires'
118
    assert data[1]['max_files'] == 6
119
    assert data[2]['text'] == 'Plan de situation du terrain'
120
    assert data[2]['max_files'] == 6
121
    assert data[3]['text'] == 'DECLARATION PREALABLE INCOMPLETE'
122
    assert data[3]['max_files'] == 6
123
    for piece in data:
124
        assert len(piece['files']) == 1
125
        assert piece['files'][0].keys() == ['url']
126

  
127
    resp = app.get('/cartads-cs/test/check_pieces?type_dossier_id=CU&objet_demande_id=1&tracking_code=BBBBBBBB')
128
    assert resp.json == {'result': False, 'err': 0}
129

  
130
    resp = app.post(data[0]['files'][0]['url'],
131
                    upload_files=[('files[]', 'test.pdf', '%PDF...')])
132
    cerfa_token = resp.json[0]['token']
133

  
134
    resp = app.get('/cartads-cs/test/pieces?type_dossier_id=CU&objet_demande_id=1&tracking_code=BBBBBBBB')
135
    data = resp.json['data']
136
    assert data[0]['files'][0]['name']
137

  
138
    resp = app.post(data[0]['files'][0]['url'] + '%s/delete/' % cerfa_token)
139
    assert resp.json == {'err': 0}
140

  
141
    resp = app.get('/cartads-cs/test/pieces?type_dossier_id=CU&objet_demande_id=1&tracking_code=BBBBBBBB')
142
    data = resp.json['data']
143
    assert 'name' not in data[0]['files'][0]
144

  
145
    resp = app.post(data[0]['files'][0]['url'],
146
                    upload_files=[('files[]', 'test.pdf', '%PDF...')])
147

  
148
    resp = app.post(data[1]['files'][0]['url'],
149
                    upload_files=[('files[]', 'test.pdf', '%PDF...')])
150
    resp = app.post(data[1]['files'][0]['url'],
151
                    upload_files=[('files[]', 'test.pdf', '%PDF...')])
152
    resp = app.get('/cartads-cs/test/pieces?type_dossier_id=CU&objet_demande_id=1&tracking_code=BBBBBBBB')
153
    data = resp.json['data']
154
    assert len(data[1]['files']) == 3
155

  
156
    resp = app.get('/cartads-cs/test/check_pieces?type_dossier_id=CU&objet_demande_id=1&tracking_code=BBBBBBBB')
157
    assert resp.json == {'result': False, 'err': 0}
158

  
159
    resp = app.post(data[2]['files'][0]['url'],
160
                    upload_files=[('files[]', 'test.pdf', '%PDF...')])
161

  
162
    resp = app.get('/cartads-cs/test/check_pieces?type_dossier_id=CU&objet_demande_id=1&tracking_code=BBBBBBBB')
163
    assert resp.json == {'result': True, 'err': 0}
164

  
165
def test_send(connector, app, cached_data):
164 166
    CartaDSFile.objects.all().delete()
165 167
    Job.objects.all().delete()
166
    test_pieces_management(connector, app)
168
    test_pieces_management(connector, app, cached_data)
167 169
    resp = app.get('/cartads-cs/test/send?commune_id=1&type_dossier_id=CU&objet_demande_id=1&tracking_code=BBBBBBBB&email=test@invalid')
168 170
    assert CartaDSDossier.objects.all().count() == 1
169 171
    dossier = CartaDSDossier.objects.all().first()
......
199 201
    assert dossier.cartads_id_dossier == '135792'
200 202
    assert dossier.cartads_numero_dossier == 'CU 044 043 19 A0006'
201 203

  
202
def test_send_notification_error(connector, app):
204
def test_send_notification_error(connector, app, cached_data):
203 205
    CartaDSFile.objects.all().delete()
204 206
    Job.objects.all().delete()
205
    test_pieces_management(connector, app)
207
    test_pieces_management(connector, app, cached_data)
206 208
    resp = app.get('/cartads-cs/test/send?commune_id=1&type_dossier_id=CU&objet_demande_id=1&tracking_code=BBBBBBBB&email=test@invalid')
207 209
    assert CartaDSDossier.objects.all().count() == 1
208 210
    dossier = CartaDSDossier.objects.all().first()
......
240 242
    assert dossier.notification_message is not None
241 243

  
242 244

  
243
def test_status(connector, app):
245
def test_status(connector, app, cached_data):
244 246
    CartaDSDossier.objects.all().delete()
245
    test_send(connector, app)
247
    test_send(connector, app, cached_data)
246 248
    dossier = CartaDSDossier.objects.all()[0]
247 249

  
248 250
    with mock.patch('passerelle.apps.cartads_cs.models.CartaDSCS.soap_client') as client:
......
251 253
        assert resp.json['status_label'] == 'En cours de saisie'
252 254

  
253 255

  
254
def test_status_error(connector, app):
256
def test_status_error(connector, app, cached_data):
255 257
    CartaDSDossier.objects.all().delete()
256
    test_send_notification_error(connector, app)
258
    test_send_notification_error(connector, app, cached_data)
257 259
    dossier = CartaDSDossier.objects.all()[0]
258 260

  
259 261
    resp = app.get('/cartads-cs/test/status?dossier_id=%s' % dossier.id)
260
-