1f881eab1dfb8fe74bf82efe977eba826f717df2
[auf_rh_dae.git] / project / dae / views.py
1 # -*- encoding: utf-8 -*-
2
3 import os
4 import datetime
5 import StringIO
6 from collections import defaultdict
7 from datetime import date
8 from simplejson import dumps
9 import warnings
10
11 from django.core.urlresolvers import reverse
12 from django.http import Http404, HttpResponse, HttpResponseGone
13 from django.shortcuts import redirect, render_to_response, get_object_or_404
14 from django.template import Context, RequestContext
15 from django.template.loader import get_template
16 from django.contrib import messages
17 from django.conf import settings
18
19 from reversion.models import Version
20 import ho.pisa as pisa
21
22 from project.dae import models as dae
23 from project.rh_v1 import models as rh
24
25 from decorators import dae_groupe_requis, \
26 poste_dans_ma_region_ou_service, \
27 dossier_dans_ma_region_ou_service, \
28 employe_dans_ma_region_ou_service
29 from forms import *
30
31 pisa.showLogging()
32
33 def reponse_pdf(template_src, context_dict):
34 """
35 Générer une réponse HTTP avec un PDF
36 """
37 css = ""
38 for f in ('css/pdf.css', 'css/dae.css'):
39 css_file = os.path.join(settings.MEDIA_ROOT, f)
40 css += open(css_file, 'r').read()
41 context_dict['css'] = css
42
43 template = get_template(template_src)
44 context = Context(context_dict)
45 html = template.render(context)
46 result = StringIO.StringIO()
47 pdf = pisa.pisaDocument(html, result, encoding='UTF-8')
48 if not pdf.err:
49 return HttpResponse(result.getvalue(), mimetype='application/pdf')
50 return HttpResponse("impossible de générer le pdf! %s" % html)
51
52
53 @dae_groupe_requis
54 def index(request):
55 return render_to_response('dae/index.html', {}, RequestContext(request))
56
57 @dae_groupe_requis
58 @poste_dans_ma_region_ou_service
59 def poste_consulter(request, key):
60 source, id = key.split('-')
61 poste = get_object_or_404(dae.Poste, pk=id)
62
63 if request.POST:
64 validationForm = PosteWorkflowForm(request.POST, instance=poste, request=request)
65 if validationForm.is_valid():
66 validationForm.save()
67 messages.add_message(request, messages.SUCCESS, "La validation a été enregistrée.")
68 return redirect('poste_consulter', key=key)
69 else:
70 validationForm = PosteWorkflowForm(instance=poste, request=request)
71
72 vars = {'poste' : poste, 'validationForm' : validationForm, }
73
74
75 mode = request.GET.get('mode', None)
76 if mode is None:
77 return render_to_response('dae/poste_consulter.html', vars, RequestContext(request))
78 if mode == 'pdf':
79 return reponse_pdf('dae/poste_pdf.html', vars)
80 if mode == 'vpdf':
81 return render_to_response('dae/poste_pdf.html', vars, RequestContext(request))
82
83
84 @dae_groupe_requis
85 @poste_dans_ma_region_ou_service
86 def poste(request, key=None):
87 """ Formulaire pour un poste.
88
89 Permet de créer ou modifier un poste. Si le poste n'existe que dans rh_v1
90 il est automatiquement copié dans dae.
91
92 """
93 poste, data, vars = None, dict(), dict()
94
95 if key:
96 # Poste existant
97 data['poste'] = key
98 source, id = key.split('-')
99
100 if source == 'dae':
101 poste = get_object_or_404(dae.Poste, pk=id)
102 elif source == 'rh':
103 p = get_object_or_404(rh.Poste, pk=id)
104 # Initialisation avec les valeurs du poste de rh_v1
105 poste = dae.Poste(id_rh=p, nom=p.type_poste.nom)
106 for field in ('implantation', 'type_poste', 'actif'):
107 setattr(poste, field, getattr(p, field))
108 else:
109 # Nouveau poste
110 vars['new'] = True
111
112 if request.POST:
113 data.update(dict(request.POST.items()))
114 form = PosteForm(data, instance=poste, request=request)
115 financementForm = FinancementForm(request.POST, instance=poste)
116 piecesForm = PostePieceForm(request.POST, request.FILES, instance=poste)
117 if form.is_valid() and piecesForm.is_valid() and financementForm.is_valid():
118 poste = form.save()
119 piecesForm.instance = poste
120 piecesForm.save()
121 financementForm.instance = poste
122 financementForm.save()
123 messages.add_message(request, messages.SUCCESS, "Le poste %s a été sauvegardé." % poste)
124 if request.POST.has_key('save'):
125 return redirect('poste_consulter', key='dae-%s' % poste.id)
126 else:
127 return redirect('poste', key='dae-%s' % poste.id)
128
129 else:
130 messages.add_message(request, messages.ERROR, 'Il y a des erreurs dans le formulaire.')
131
132 else:
133 # 'initial' évite la validation prémature lors d'une copie de poste de
134 # rh_v1 vers dae.
135 form = PosteForm(initial=data, instance=poste, request=request)
136 piecesForm = PostePieceForm(instance=poste)
137 financementForm = FinancementForm(instance=poste)
138
139 vars.update(dict(form=form, poste=poste, poste_key=key, piecesForm=piecesForm, financementForm=financementForm))
140
141 return render_to_response('dae/poste.html', vars, RequestContext(request))
142
143 @dae_groupe_requis
144 def postes_liste(request):
145 """ Liste des postes. """
146 vars = dict()
147 vars['postes'] = dae.Poste.objects.ma_region_ou_service(request.user).order_by('-date_creation')
148 return render_to_response('dae/postes_liste.html', vars, RequestContext(request))
149
150 def filtered_type_remun():
151 # Exclusion de "Indemnité de fonction" des types de rémun utilisés
152 return rh.TypeRemuneration.objects.exclude(pk=7)
153
154 @dae_groupe_requis
155 @dossier_dans_ma_region_ou_service
156 def embauche_consulter(request, dossier_id):
157 dossier = get_object_or_404(dae.Dossier, pk=dossier_id)
158
159 if request.POST:
160 validationForm = DossierWorkflowForm(request.POST, instance=dossier, request=request)
161 if validationForm.is_valid():
162 validationForm.save()
163 messages.add_message(request, messages.SUCCESS, "La validation a été enregistrée.")
164 return redirect('embauche_consulter', dossier_id=dossier.id)
165 else:
166 validationForm = DossierWorkflowForm(instance=dossier, request=request)
167
168 vars = {
169 'dossier' : dossier,
170 'validationForm' : validationForm,
171 }
172 return render_to_response('dae/embauche_consulter.html', vars, RequestContext(request))
173
174 @dae_groupe_requis
175 @dossier_dans_ma_region_ou_service
176 def embauche(request, key=None, dossier_id=None):
177 """ Formulaire d'autorisation d'embauche. """
178 if not key:
179 vars = dict(step='poste', form=ChoosePosteForm(request=request))
180 else:
181 type_remun = filtered_type_remun()
182 vars = dict(type_remun=type_remun)
183 source, id = key.split('-')
184 if source != 'dae':
185 return Http404
186 poste = get_object_or_404(dae.Poste, pk=id)
187 if not dossier_id:
188 vars['new'] = True
189
190 if request.POST:
191 if request.POST['employe'] == '':
192 # Nouvel employé
193 employe = dae.Employe()
194 else:
195 employe_source, id = request.POST['employe'].split('-')
196 if employe_source == 'dae':
197 # Employé DAE
198 employe = get_object_or_404(dae.Employe, pk=id)
199 elif employe_source == 'rh':
200 # Employé RH, on le copie dans DAE
201 e = get_object_or_404(rh.Employe, pk=id)
202 employe = dae.Employe(id_rh=e, prenom=e.prenom, nom=e.nom,
203 genre=e.genre)
204 else:
205 raise Http404
206
207 employe_form = EmployeForm(request.POST, instance=employe, request=request)
208
209 if request.POST:
210 if employe_form.is_valid():
211 data = dict(request.POST.items())
212 #with warnings.catch_warnings():
213 # warnings.simplefilter('ignore')
214 employe = employe_form.save()
215 data['employe'] = 'dae-%s' % employe.id
216 employe_form = EmployeForm(data, instance=employe, request=request)
217
218 if not dossier_id:
219 dossier = dae.Dossier(poste=poste, employe=employe)
220 else:
221 dossier = get_object_or_404(dae.Dossier, pk=dossier_id)
222 dossier_form = DossierForm(request.POST, instance=dossier)
223 piecesForm = DossierPieceForm(request.POST, request.FILES, instance=dossier)
224 justificationsNouveauForm = JustificationNouvelEmployeForm(request.POST, instance=dossier)
225 justificationsAutreForm = JustificationAutreEmployeForm(request.POST, instance=dossier)
226 dossiersComparaisonsForm = DossierComparaisonForm(request.POST, instance=dossier)
227
228 if dossier_form.is_valid() and piecesForm.is_valid() and justificationsNouveauForm.is_valid() and justificationsAutreForm.is_valid() and dossiersComparaisonsForm.is_valid():
229 dossier = dossier_form.save()
230 piecesForm.instance = dossier
231 piecesForm.save()
232 justificationsNouveauForm.instance = dossier
233 justificationsNouveauForm.save()
234 justificationsAutreForm.instance = dossier
235 justificationsAutreForm.save()
236 dossiersComparaisonsForm.instance = dossier
237 dossiersComparaisonsForm.save()
238
239 if not dossier.remuneration_set.all():
240 # Pré-peuplement des entrées de la section "coût
241 # global", à l'exclusion de "Indemnité de fonction"
242 for type in type_remun.all():
243 dae.Remuneration(dossier=dossier, type=type,
244 devise=dossier.devise).save()
245
246 else:
247 # Sauvegarde du coût global
248 cg_lines = defaultdict(dict)
249 for k, v in request.POST.items():
250 if k.startswith('cg-'):
251 prefix, field_name, cg_id = k.split('-')
252 cg_lines[int(cg_id)][unicode(field_name)] = v
253
254 for r in dossier.remuneration_set.all():
255 print 'trying %r' % r
256 if r.id in cg_lines:
257 if cg_lines[r.id]['montant'] == '':
258 r.delete()
259 else:
260 for k, v in cg_lines[r.id].items():
261 setattr(r, k, v)
262 r.save()
263
264 messages.add_message(request, messages.SUCCESS, "Le dossier %s a été sauvegardé." % dossier)
265 if request.POST.has_key('save'):
266 return redirect('embauche_consulter', dossier_id=dossier.id)
267 else:
268 return redirect('embauche', key=dossier.poste.key, dossier_id=dossier.id)
269
270 else:
271 messages.add_message(request, messages.ERROR, 'Il y a des erreurs dans le formulaire.')
272
273 else:
274 dossier_form = DossierForm(instance=dossier)
275 piecesForm = DossierPieceForm(instance=dossier)
276 justificationsNouveauForm = JustificationNouvelEmployeForm(instance=dossier)
277 justificationsAutreForm = JustificationAutreEmployeForm(instance=dossier)
278 dossiersComparaisonsForm = DossierComparaisonForm(instance=dossier)
279 else:
280 # Initialisation d'un formulaire vide
281 dossier_rh = rh.Dossier()
282 poste_rh = poste.id_rh
283 if dossier_id:
284 dossier = get_object_or_404(dae.Dossier, pk=dossier_id)
285 employe = dossier.employe
286 data = dict(employe='dae-%s' % employe.id)
287 employe_form = EmployeForm(initial=data, instance=employe, request=request)
288 else:
289 dossier = pre_filled_dossier(dossier_rh, 'new', poste_rh)
290 employe_form = EmployeForm(request=request)
291
292 dossier_form = DossierForm(instance=dossier)
293 piecesForm = DossierPieceForm(instance=dossier)
294 justificationsNouveauForm = JustificationNouvelEmployeForm(instance=dossier)
295 justificationsAutreForm = JustificationAutreEmployeForm(instance=dossier)
296 dossiersComparaisonsForm = DossierComparaisonForm(instance=dossier)
297
298 vars = dict(step='employe',
299 type_remun=type_remun,
300 poste=poste,
301 dossier=dossier,
302 piecesForm=piecesForm,
303 justificationsNouveauForm=justificationsNouveauForm,
304 justificationsAutreForm=justificationsAutreForm,
305 dossiersComparaisonsForm=dossiersComparaisonsForm,
306 forms=dict(employe=employe_form, dossier=dossier_form, )
307 )
308
309
310 return render_to_response('dae/embauche.html', vars,
311 RequestContext(request))
312 @dae_groupe_requis
313 @dossier_dans_ma_region_ou_service
314 def embauches_liste(request):
315 """ Liste des embauches. """
316 vars = dict()
317 vars['embauches'] = dae.Dossier.objects.ma_region_ou_service(request.user).order_by('-date_creation')
318 return render_to_response('dae/embauches_liste.html', vars, RequestContext(request))
319
320 def employe(request, key):
321 """ Récupération AJAX de l'employé pour la page d'embauche. """
322 data = dict(employe=key)
323
324 if key == '':
325 # Nouvel employé
326 employe = dae.Employe()
327 else:
328 # Employé existant
329 source, id = key.split('-')
330
331 if source == 'dae':
332 employe = get_object_or_404(dae.Employe, pk=id)
333 elif source == 'rh':
334 e = get_object_or_404(rh.Employe, id=id)
335 # Initialisation avec les valeurs de l'employé de rh_v1
336 employe = dae.Employe(id_rh=e)
337 for field in ('prenom', 'nom', 'genre'):
338 setattr(employe, field, getattr(e, field))
339
340 return HttpResponse(EmployeForm(initial=data, instance=employe, request=request).as_table())
341
342 ################################################################################
343 # AJAX SECURISE
344 ################################################################################
345 @dae_groupe_requis
346 @employe_dans_ma_region_ou_service
347 def dossier(request, poste_key, employe_key):
348 """ Récupération AJAX du dossier pour la page d'embauche. """
349 data = dict()
350
351 poste_source, poste_id = poste_key.split('-')
352 poste = get_object_or_404(dae.Poste, pk=poste_id)
353
354 # Récupérer la devise de l'implantation lié au poste
355 implantation_devise = poste.get_default_devise()
356 data.update({'devise' : implantation_devise.id})
357
358 if poste.id_rh_id is not None:
359 poste_rh = get_object_or_404(rh.Poste, pk=poste.id_rh_id)
360 else:
361 poste_rh = None
362
363 ##########################################################################################
364 # NOUVEL EMPLOYE
365 ##########################################################################################
366 if employe_key == '':
367 employe_source = 'new'
368 employe = None
369 dossier_rh = rh.Dossier()
370 dossier = pre_filled_dossier(dossier_rh, employe_source, poste_rh)
371
372 ##########################################################################################
373 # EMPLOYE DAE
374 ##########################################################################################
375 if employe_key.startswith('dae'):
376 employe_source, employe_id = employe_key.split('-')
377 employe_dae = get_object_or_404(dae.Employe, pk=employe_id)
378
379 # récupération de l'ancien dossier rh v1 pour l'employe DAE
380 try:
381 dossier_rh = rh.Dossier.objects.get(employe=employe_dae.id_rh_id, mandat_date_fin=None)
382 except (rh.Dossier.DoesNotExist):
383 dossier_rh = rh.Dossier()
384
385 # on tente de récupérer le dossier DAE, au pire on le contruit en le
386 # prépoluant avec son dossier rh v1.
387 try:
388 dossier = dae.Dossier.objects.get(employe=employe_dae, poste=poste)
389 except (dae.Dossier.DoesNotExist):
390 dossier = pre_filled_dossier(dossier_rh, employe_source, poste_rh)
391 employe = employe_dae.id_rh
392 ##########################################################################################
393 # EMPLOYE RH v1
394 ##########################################################################################
395 if employe_key.startswith('rh'):
396 employe_source, employe_id = employe_key.split('-')
397 employe_rh = get_object_or_404(rh.Employe, pk=employe_id)
398
399 # récupération de l'ancien dossier rh v1 pour l'employe rh v1, s'il n'en a pas,
400 # on en fournit un nouveau qui servira uniquement un créer un nouveau dossier DAE.
401 try:
402 dossier_rh = rh.Dossier.objects.get(employe=employe_rh, mandat_date_fin=None)
403 except (rh.Dossier.DoesNotExist):
404 dossier_rh = rh.Dossier()
405 dossier = pre_filled_dossier(dossier_rh, employe_source, poste_rh)
406 employe = employe_rh
407
408 dossier_form = DossierForm(initial=data, instance=dossier)
409 vars = dict(form=dossier_form, poste=poste, employe=employe)
410 return render_to_response('dae/embauche-dossier.html', vars,
411 RequestContext(request))
412
413 # @Cette fonction est appelée à partir de fonctions déjà sécurisée
414 def pre_filled_dossier(dossier_rh, employe_source, poste_rh):
415 dossier = dae.Dossier()
416
417 if employe_source != 'new' and dossier_rh.id:
418 dossier.statut_anterieur = dossier_rh.statut
419
420 # Certains dossiers ont un classement à zéro
421 if dossier_rh.classement_id > 0:
422 dossier.classement_anterieur = dossier_rh.classement
423
424 # Récupération du salaire de base
425 remun = dossier_rh.remuneration_set.filter(type=1)
426 if remun:
427 dossier.salaire_anterieur = remun[0].montant
428
429 # Récupération du titulaire précédent
430 try:
431 dossiers = rh.Dossier.objects.order_by('-mandat_date_fin')
432 dossiers = dossiers.filter(poste1=poste_rh) | dossiers.filter(poste2=poste_rh)
433 if len(dossiers):
434 # Ce bloc ignore toutes les erreurs, car les données de rh
435 # manquantes peuvent en générer
436 d = dossiers[0]
437 try:
438 titulaire = d.employe
439 dossier.employe_anterieur = titulaire
440 dossier.classement_titulaire_anterieur = d.classement
441 dossier.statut_titulaire_anterieur = d.statut
442 dossier.salaire_titulaire_anterieur = \
443 d.remuneration_set.all()[0].montant
444 except:
445 pass
446 # TODO: afficher l'info, les champs ne sont pas dans le
447 # modèle dae.Dossier: nom, prenom, classement, salaire
448 pass
449
450 except (rh.Dossier.DoesNotExist):
451 dossier_rh = rh.Dossier()
452
453 return dossier
454
455 @dae_groupe_requis
456 @dossier_dans_ma_region_ou_service
457 def dossier_resume(request, dossier_id=None):
458 """ Appel AJAX :
459 input : valeur_point
460 output : devise, devise_code, taux_euro
461 """
462 try:
463 dossier = rh.Dossier.objects.get(id=dossier_id)
464 except:
465 return HttpResponseGone("Ce dossier n'est pas accessible")
466
467 data = {}
468 data['personne'] = unicode(dossier.employe)
469 data['implantation'] = dossier.implantation1.id
470 data['poste'] = u"%s %s" % (dossier.poste1.type_poste.nom, dossier.complement1)
471 data['montant'] = dossier.get_salaire()
472 salaire = dossier.get_dernier_salaire_remun()
473 if salaire is not None:
474 data['devise'] = dossier.get_dernier_salaire_remun().devise.id
475 data['montant_euros'] = dossier.get_dernier_salaire_remun().en_euros()
476 else:
477 data['devise'] = None
478 data['montant_euros'] = 0
479 return HttpResponse(dumps(data))
480
481 def liste_postes(request):
482 """ Appel AJAX :
483 input : implantation_id
484 output : JSON liste de valeur point
485 """
486 method = request.method
487 params = getattr(request, method, [])
488 data = []
489
490 # Voir le code de _poste_choices dans forms.py
491 dae_ = dae.Poste.objects.filter(actif=True, id_rh__isnull=True)
492 copies = dae.Poste.objects.exclude(id_rh__isnull=True)
493 rh_postes_actifs = rh.Poste.objects.filter(actif=True)
494
495 if 'implantation_id' in params and params.get('implantation_id') is not u"":
496 implantation_id = params.get('implantation_id')
497 dae_ = dae_.filter(implantation__id=implantation_id)
498 copies = copies.filter(implantation__id=implantation_id)
499 rh_postes_actifs = rh_postes_actifs.filter(implantation__id=implantation_id)
500
501 id_copies = [p.id_rh_id for p in copies.all()]
502 rhv1 = rh_postes_actifs.exclude(id__in=id_copies)
503 rhv1 = rhv1.select_related(depth=1)
504
505 data = [('', 'Nouveau poste')] + sorted([('dae-%s' % p.id, label_poste_display(p)) for p in dae_ | copies] + [('rh-%s' % p.id, label_poste_display(p)) for p in rhv1], key=lambda t: t[1])
506 return HttpResponse(dumps(data))
507
508
509 ################################################################################
510 # AJAX SECURITE non nécessaire
511 ################################################################################
512 def coefficient(request):
513 """ Appel AJAX :
514 input : classement
515 output : coefficient
516 """
517 method = request.method
518 params = getattr(request, method, [])
519 data = dict()
520 if 'classement' in params and params.get('classement') is not u"":
521 classement = params.get('classement')
522 classement = rh.Classement.objects.get(pk=classement)
523 data['coefficient'] = classement.coefficient
524 else:
525 data['coefficient'] = 0
526 return HttpResponse(dumps(data))
527
528
529 def devise(request):
530 """ Appel AJAX :
531 input : valeur_point
532 output : devise, devise_code, taux_euro
533 """
534 method = request.method
535 params = getattr(request, method, [])
536 data = dict()
537 if 'valeur_point' in params and params.get('valeur_point') is not u"":
538 valeur_point = params.get('valeur_point')
539 valeur_point = rh.ValeurPoint.objects.get(pk=valeur_point)
540 annee = valeur_point.annee
541 implantation = valeur_point.implantation
542 taux = rh.TauxChange.objects.get(annee=annee,
543 implantation=implantation)
544 data['devise'] = taux.devise.id
545 data['valeur'] = valeur_point.valeur
546 data['devise_code'] = taux.devise.code
547 data['taux_euro'] = taux.taux
548 else:
549 return HttpResponseGone("Vous devez choisir une valeur de point")
550 return HttpResponse(dumps(data))
551
552 def devise_code(request):
553 """ Appel AJAX :
554 input : devise
555 output : devise_code, taux_euro
556 """
557 method = request.method
558 params = getattr(request, method, [])
559 data = dict()
560 if 'devise' in params:
561 devise = params.get('devise')
562 devise = rh.Devise.objects.get(pk=devise)
563 annee = date.today().year
564 taux = rh.TauxChange.objects.filter(annee=annee, devise=devise)
565 if len(taux) == 0:
566 return HttpResponseGone("Le taux n'est pas disponible")
567 data['devise_code'] = devise.code
568 data['taux_euro'] = taux[0].taux
569 return HttpResponse(dumps(data))
570
571 def add_remun(request, dossier, type_remun):
572 dossier = get_object_or_404(dae.Dossier, pk=dossier)
573 type_remun = get_object_or_404(rh.TypeRemuneration, pk=type_remun)
574 dae.Remuneration(dossier=dossier, devise=dossier.devise,
575 type=type_remun).save()
576
577 return render_to_response('dae/embauche-remun.html', dict(dossier=dossier),
578 RequestContext(request))
579
580 def salaire(request, implantation, devise, classement):
581 if not devise or not classement:
582 raise Http404
583
584 taux_impl = rh.TauxChange.objects.filter(implantation=implantation) \
585 .order_by('-annee')
586 taux = rh.TauxChange.objects.filter(devise=devise).order_by('-annee')
587 vp = rh.ValeurPoint.objects.filter(implantation=implantation) \
588 .order_by('-annee')
589 if vp.count() * taux.count() * taux_impl.count() == 0:
590 raise Http404
591
592 classement = get_object_or_404(rh.Classement, pk=classement)
593 taux, taux_impl, vp = taux[0].taux, taux_impl[0].taux, vp[0].valeur
594
595 salaire_euro = round(vp * classement.coefficient * taux_impl, 2)
596 data = dict(salaire_euro=salaire_euro, taux=taux,
597 salaire_devise=round(salaire_euro / taux, 2))
598
599 return HttpResponse(dumps(data))
600
601 def liste_valeurs_point(request):
602 """ Appel AJAX :
603 input : implantation_id
604 output : JSON liste de valeur point
605 """
606 method = request.method
607 params = getattr(request, method, [])
608 data = []
609 annee_courante = datetime.datetime.now().year
610 if 'implantation_id' in params and params.get('implantation_id') is not u"":
611 implantation_id = params.get('implantation_id')
612 objects = rh.ValeurPoint.objects.filter(implantation=implantation_id, annee__in=(annee_courante-1, annee_courante)).order_by("-annee")
613 else:
614 objects = rh.ValeurPoint.objects.filter(annee__in=(annee_courante-1, annee_courante)).order_by("-annee")
615 for o in objects:
616 data.append({'id' : o.id, 'label' : o.__unicode__(), })
617 return HttpResponse(dumps(data))
618