1
|
# -*- coding: utf-8 -*-
|
2
|
from decimal import Decimal
|
3
|
import time
|
4
|
import datetime
|
5
|
from xml.etree import ElementTree as etree
|
6
|
import logging
|
7
|
|
8
|
from suds.client import Client
|
9
|
from suds.bindings.binding import Binding
|
10
|
|
11
|
logger = logging.getLogger(__name__)
|
12
|
# Webdev is bugged and using an HTML generator to produce XML content,
|
13
|
Binding.replyfilter = lambda self, x: x.replace(' ', ' ')
|
14
|
|
15
|
# cleaning and parsing functions
|
16
|
|
17
|
LINE_SEPARATOR = '\n'
|
18
|
COLUMN_SEPARATOR = '\t'
|
19
|
|
20
|
def unicode_and_strip(x):
|
21
|
return unicode(x).strip()
|
22
|
|
23
|
def strip_and_int(x):
|
24
|
try:
|
25
|
return int(x.strip())
|
26
|
except ValueError:
|
27
|
return None
|
28
|
|
29
|
def strip_and_date(x):
|
30
|
try:
|
31
|
return datetime.datetime.strptime(x.strip(), '%Y%m%d').date()
|
32
|
except ValueError:
|
33
|
return None
|
34
|
|
35
|
def parse_date(date_string):
|
36
|
if date_string:
|
37
|
return datetime.datetime.strptime(date_string, "%Y%m%d")
|
38
|
else:
|
39
|
None
|
40
|
|
41
|
class DominoException(Exception):
|
42
|
pass
|
43
|
|
44
|
def object_cached(function):
|
45
|
'''Decorate an object method so that its results is cached on the object
|
46
|
instance after the first call.
|
47
|
'''
|
48
|
def decorated_function(self, *args, **kwargs):
|
49
|
cache_name = '__%s_cache' % function.__name__
|
50
|
if not hasattr(self, cache_name):
|
51
|
setattr(self, cache_name, (time.time(), {}))
|
52
|
t, d = getattr(self, cache_name)
|
53
|
if time.time() - t > 30:
|
54
|
setattr(self, cache_name, (time.time(), {}))
|
55
|
t, d = getattr(self, cache_name)
|
56
|
k = tuple(*args) + tuple(sorted(kwargs.items()))
|
57
|
if not k in d:
|
58
|
d[k] = function(self, *args, **kwargs)
|
59
|
return d[k]
|
60
|
return decorated_function
|
61
|
|
62
|
# Data model
|
63
|
class SimpleObject(object):
|
64
|
'''Base class for object returned by the web service'''
|
65
|
|
66
|
'''Describe basic columns'''
|
67
|
COLUMNS = ()
|
68
|
'''Describe extended object columns'''
|
69
|
MORE_COLUMNS = ()
|
70
|
|
71
|
def __init__(self, **kwargs):
|
72
|
self.__dict__.update(kwargs)
|
73
|
|
74
|
def __repr__(self):
|
75
|
c = {}
|
76
|
for remote_name, name, converter, desc in self.COLUMNS:
|
77
|
if hasattr(self, name):
|
78
|
c[name] = getattr(self, name)
|
79
|
return str(c)
|
80
|
|
81
|
def serialize(self):
|
82
|
l = []
|
83
|
for remote_name, local_name, converter, desc in self.COLUMNS + self.MORE_COLUMNS:
|
84
|
if local_name == 'id':
|
85
|
continue
|
86
|
v = getattr(self, local_name, None)
|
87
|
if v is None:
|
88
|
continue
|
89
|
l.append(u'{0}: "{1}"'.format(remote_name, v))
|
90
|
return u','.join(l)
|
91
|
|
92
|
def debug(self):
|
93
|
'''Output a debugging view of this object'''
|
94
|
res = ''
|
95
|
for remote_name, name, converter, desc in self.MORE_COLUMNS or self.COLUMNS:
|
96
|
if hasattr(self, name):
|
97
|
res += name + ':' + repr(getattr(self, name)) + '\n'
|
98
|
return res
|
99
|
|
100
|
def __int__(self):
|
101
|
'''Return the object id'''
|
102
|
return self.id
|
103
|
|
104
|
class UrgentContact(SimpleObject):
|
105
|
COLUMNS = (
|
106
|
('IDENFANTS', 'id_enfant', strip_and_int, 'IDENFANTS'),
|
107
|
('IDCONTACT_AUTORISE', 'id', strip_and_int, 'IDCONTACT_AUTORISE'),
|
108
|
('LIENFAMILLE_CH', 'lien_de_famille', unicode_and_strip, 'LIENFAMILLE_CH'),
|
109
|
('PERE_MERE_CH', 'lien_pere_ou_pere', unicode_and_strip, 'PERE_MERE_CH'),
|
110
|
('IDFAMILLES', 'id_famille', unicode_and_strip, 'IDFAMILLES'),
|
111
|
('TYPE_CH', 'type', unicode_and_strip, 'TYPE_CH'),
|
112
|
('NOM_CH', 'nom', unicode_and_strip, 'NOM_CH'),
|
113
|
('PRENOM_CH', 'prenom', unicode_and_strip, 'PRENOM_CH'),
|
114
|
('RUE_CH', 'rue', unicode_and_strip, 'RUE_CH'),
|
115
|
('RUE2_CH', 'rue2', unicode_and_strip, 'RUE2_CH'),
|
116
|
('RUE3_CH', 'rue3', unicode_and_strip, 'RUE3_CH'),
|
117
|
('CODEPOSTAL_CH', 'code_postal', unicode_and_strip, 'CODEPOSTAL_CH'),
|
118
|
('VILLE_CH', 'ville', unicode_and_strip, 'VILLE_CH'),
|
119
|
('TELEPHONE_CH', 'telephone', unicode_and_strip, 'TELEPHONE_CH'),
|
120
|
('TELEPHONE2_CH', 'telephone2', unicode_and_strip, 'TELEPHONE2_CH'),
|
121
|
('ADRESSEINT_CH', 'adresse_internet', unicode_and_strip, 'ADRESSEINT_CH'),
|
122
|
)
|
123
|
|
124
|
class Child(SimpleObject):
|
125
|
COLUMNS = (
|
126
|
('IDENFANTS', 'id', strip_and_int, 'Identifiant de ENFANTS'),
|
127
|
('NOM_CH', 'nom', unicode_and_strip, 'Nom'),
|
128
|
('PRENOM_CH', 'prenom', unicode_and_strip, 'Prénom'),
|
129
|
('NAISSANCE_DA', 'date_naissance', strip_and_date, 'Date de Naissance'),
|
130
|
('COMMENTAIRE_ME', 'commentaire', unicode_and_strip, 'Commentaires / Notes'),
|
131
|
('IDFAMILLES', 'id_famille', unicode_and_strip, 'IDFAMILLES'),
|
132
|
('CODEPOSTAL_CH', 'code_postal', unicode_and_strip, 'Code Postal'),
|
133
|
('VILLE_CH', 'ville', unicode_and_strip, 'Ville'),
|
134
|
('CODEINTERNE_CH', 'code_interne', unicode_and_strip, 'Code Interne'),
|
135
|
('LIEUNAISSANCE_CH', 'lieu_naissance', unicode_and_strip, 'Lieu de Naissance'),
|
136
|
('DEPNAISSANCE_CH', 'departement_naissance', unicode_and_strip, 'Département Naissance'),
|
137
|
('NUMSECU_CH', 'num_securite_sociale', unicode_and_strip, 'N° de SECU'),
|
138
|
('NATIONALITE_CH', 'nationalite', unicode_and_strip, 'Nationalité'),
|
139
|
('PRENOM2_CH', 'prenom2', unicode_and_strip, 'Prénom 2'),
|
140
|
('SEXE_CH', 'sexe', unicode_and_strip, 'Sexe'),
|
141
|
('IDTABLELIBRE1', 'IDTABLELIBRE1', unicode_and_strip, 'IDTABLELIBRE1'),
|
142
|
('IDTABLELIBRE2', 'IDTABLELIBRE2', unicode_and_strip, 'IDTABLELIBRE2'),
|
143
|
('IDTABLELIBRE3', 'IDTABLELIBRE3', unicode_and_strip, 'IDTABLELIBRE3'),
|
144
|
('IDTABLELIBRE4', 'IDTABLELIBRE4', unicode_and_strip, 'IDTABLELIBRE4'),
|
145
|
('CHAMPLIBRE1_CH', 'CHAMPLIBRE1_CH', unicode_and_strip, 'Valeur Champ Libre 1'),
|
146
|
('CHAMPLIBRE2_CH', 'CHAMPLIBRE2_CH', unicode_and_strip, 'Valeur Champ Libre 2'),
|
147
|
('CHAMPCALCULE1_CH', 'CHAMPCALCULE1_CH', unicode_and_strip, 'Valeur Champ Calculé 1'),
|
148
|
('CHAMPCALCULE2_CH', 'CHAMPCALCULE2_CH', unicode_and_strip, 'Valeur Champ Calculé 2'),
|
149
|
('SOMMEIL_ME', 'sommeil', unicode_and_strip, 'Sommeil'),
|
150
|
('ACTIVITE_ME', 'activite', unicode_and_strip, 'Activités'),
|
151
|
('HABITUDE_ME', 'habitude', unicode_and_strip, 'Habitudes'),
|
152
|
('PHOTO_CH', 'photographie', unicode_and_strip, 'Photographie'),
|
153
|
('NUMCOMPTE_CH', 'numcompte', unicode_and_strip, 'N° Compte Comptable'),
|
154
|
('TELEPHONE_CH', 'telephone', unicode_and_strip, 'Téléphone'),
|
155
|
('IDFAMILLES2', 'id_famille2', unicode_and_strip, 'Identifiant famille 2'),
|
156
|
('PERE_CH', 'pere', unicode_and_strip, 'Nom du père'),
|
157
|
('MERE_CH', 'mere', unicode_and_strip, 'Nom de la mère'),
|
158
|
('AUTOPARENTALEMERE_IN', 'autorisation_parentale_mere', unicode_and_strip, 'Autorisation Parentale Mère'),
|
159
|
('AUTOPARENTALEPERE_IN', 'autorisation_parentale_pere', unicode_and_strip, 'Autorisation Parentale de Père'),
|
160
|
('IDPORTAIL_ENFANTS', 'id_portail_enfants', unicode_and_strip, 'Identifiant de PORTAIL_ENFANTS'),
|
161
|
('ADRESSEINT_CH', 'adresse_internet', unicode_and_strip, 'Adresse Internet'),
|
162
|
)
|
163
|
|
164
|
def save(self):
|
165
|
if hasattr(self, 'id'):
|
166
|
self.client.update_child(self)
|
167
|
else:
|
168
|
self.id = self.client.add_child(self)
|
169
|
self.client.clear_cache()
|
170
|
|
171
|
class Family(SimpleObject):
|
172
|
COLUMNS = (
|
173
|
('IDFAMILLES', 'id', strip_and_int, 'identifiant de famille'),
|
174
|
('NOMFAMILLE_CH', 'famille_nom', unicode_and_strip, 'nom de famille'),
|
175
|
('EMAILPERE_CH', 'email_pere', unicode_and_strip, 'email du père'),
|
176
|
('EMAILMERE_CH', 'email_mere', unicode_and_strip, 'email de la mère'),
|
177
|
('ADRESSEINT_CH', 'adresse_internet', unicode_and_strip, 'adresse internet'),
|
178
|
('CODEINTERNE_CH', 'code_interne', unicode_and_strip, 'code interne'),
|
179
|
)
|
180
|
|
181
|
MORE_COLUMNS = (
|
182
|
('IDFAMILLES', 'id', strip_and_int, 'identifiant de famille'),
|
183
|
('CODEINTERNE_CH', 'code_interne', unicode_and_strip, 'code interne'),
|
184
|
('CIVILITE_CH', 'civilite', unicode_and_strip, 'civilité'),
|
185
|
('NOMFAMILLE_CH', 'famille_nom', unicode_and_strip, 'nom de famille'),
|
186
|
('RUE_CH', 'rue', unicode_and_strip, 'rue'),
|
187
|
('RUE2_CH', 'rue2', unicode_and_strip, 'rue 2'),
|
188
|
('RUE3_CH', 'rue3', unicode_and_strip, 'rue 3'),
|
189
|
('CODEPOSTAL_CH', 'code_postal', unicode_and_strip, 'code postal'),
|
190
|
('VILLE_CH', 'ville', unicode_and_strip, 'ville'),
|
191
|
('TELEPHONE_CH', 'telephone', unicode_and_strip, 'téléphone'),
|
192
|
('TELEPHONE2_CH', 'telephone2', unicode_and_strip, 'téléphone 2'),
|
193
|
('TELECOPIE_CH', 'telecopie', unicode_and_strip, 'télécopie'),
|
194
|
('TELECOPIE2_CH', 'telecopie2', unicode_and_strip, 'télécopie 2'),
|
195
|
('ADRESSEINT_CH', 'adresse_internet', unicode_and_strip, 'adresse internet'),
|
196
|
('SITUATION_CH', 'situation', unicode_and_strip, 'situation familiale'),
|
197
|
('REVENUMENSUEL_MO', 'revenu_mensuel', unicode_and_strip, 'revenu mensuel de la famille'),
|
198
|
('REVENUANNUEL_MO', 'revenu_annuel', unicode_and_strip, 'revenu annuel de la famille'),
|
199
|
('QUOTIENTFAMILIAL_MO', 'quotient_familial', unicode_and_strip, 'quotient familial'),
|
200
|
('NBTOTALENFANTS_EN', 'nb_total_enfants', unicode_and_strip, 'nombre total d\'enfants'),
|
201
|
('NBENFANTSACHARGE_EN', 'nb_enfants_a_charge', unicode_and_strip, 'nombre d\'enfants à charge'),
|
202
|
('NOMPERE_CH', 'nom_pere', unicode_and_strip, 'monsieur'),
|
203
|
('PRENOMPERE_CH', 'prenom_pere', unicode_and_strip, 'prénom monsieur'),
|
204
|
('AUTOPARENTALEPERE_IN', 'autoparentale_pere', unicode_and_strip, 'autorisation parentale de père'),
|
205
|
('DATENAISPERE_DA', 'date_naissance_pere', strip_and_date, 'date de naisance du père'),
|
206
|
('DEPNAISPERE_EN', 'departement_naissance_pere', unicode_and_strip, 'département de naissance du père'),
|
207
|
('LIEUNAISPERE_CH', 'lieu_naissance_pere', unicode_and_strip, 'lieu de naissance du père'),
|
208
|
('RUEPERE_CH', 'rue_pere', unicode_and_strip, 'rue père'),
|
209
|
('RUE2PERE_CH', 'rue2_pere', unicode_and_strip, 'rue 2 père'),
|
210
|
('RUE3PERE_CH', 'rue3_pere', unicode_and_strip, 'rue 3 père'),
|
211
|
('CODEPOSTALPERE_CH', 'code_postal_pere', unicode_and_strip, 'code postal père'),
|
212
|
('VILLEPERE_CH', 'ville_pere', unicode_and_strip, 'ville père'),
|
213
|
('TELEPHONEPERE_CH', 'telephone_pere', unicode_and_strip, 'téléphone père'),
|
214
|
('TELEPHONE2PERE_CH', 'telephone2_pere', unicode_and_strip, 'téléphone 2 père'),
|
215
|
('TELPERE_LR_IN', 'tel_pere_liste_rouge', unicode_and_strip, 'téléphone liste rouge père'),
|
216
|
('TEL2PERE_LR_IN', 'tel2_pere_liste_rouge', unicode_and_strip, 'téléphone 2 liste rouge père'),
|
217
|
('TEL_LR_IN', 'tel_liste_rourge', unicode_and_strip, 'téléphone liste rouge'),
|
218
|
('TEL2_LR_IN', 'tel2_liste_rouge', unicode_and_strip, 'téléphone 2 liste rouge'),
|
219
|
('NOMMERE_CH', 'nom_mere', unicode_and_strip, 'madame'),
|
220
|
('PRENOMMERE_CH', 'prenom_mere', unicode_and_strip, 'prénom madame'),
|
221
|
('AUTOPARENTALEMERE_IN', 'autoparentale_mere', unicode_and_strip, 'autorisation parentale mère'),
|
222
|
('DATENAISMERE_DA', 'date_naissance_mere', strip_and_date, 'date de naissance de la mère'),
|
223
|
('DEPNAISMERE_EN', 'departement_naissance_mere', unicode_and_strip, 'département de naissance de la mère'),
|
224
|
('LIEUNAISMERE_CH', 'lieu_naissance_mere', unicode_and_strip, 'lieu de naissance de la mère'),
|
225
|
('RUEMERE_CH', 'rue_mere', unicode_and_strip, 'rue mère'),
|
226
|
('REVMENSUELPERE_MO', 'revenu_mensuel_pere', unicode_and_strip, 'revenu mensuel du père'),
|
227
|
('RUE2MERE_CH', 'rue2_mere', unicode_and_strip, 'rue 2 mère'),
|
228
|
('RUE3MERE_CH', 'rue3_mere', unicode_and_strip, 'rue 3 mère'),
|
229
|
('CODEPOSTALMERE_CH', 'code_postal_mere', unicode_and_strip, 'code postal de la mère'),
|
230
|
('VILLEMERE_CH', 'ville_mere', unicode_and_strip, 'ville de la mère'),
|
231
|
('REVMENSUELMERE_MO', 'revenu_mensuel_mere', unicode_and_strip, 'revenu mensuel mère'),
|
232
|
('REVANNUELPERE_MO', 'revenu_annuel_pere', unicode_and_strip, 'revenu annuel père'),
|
233
|
('REVANNUELMERE_MO', 'revenu_annuel_mere', unicode_and_strip, 'revenu annuel mère'),
|
234
|
('TELEPHONEMERE_CH', 'telephone_mere', unicode_and_strip, 'téléphone mère'),
|
235
|
('TELEPHONE2MERE_CH', 'telephone2_mere', unicode_and_strip, 'téléphone 2 mère'),
|
236
|
('TELMERE_LR_IN', 'telephone_mere_liste_rouge', unicode_and_strip, 'téléphone liste rouge mère'),
|
237
|
('TEL2MERE_LR_IN', 'telephone2_mere_liste_rouge', unicode_and_strip, 'téléphone 2 liste rouge mère'),
|
238
|
('TELECOPIEPERE_CH', 'telecopie_pere', unicode_and_strip, 'télécopie du père'),
|
239
|
('TELECOPIE2PERE_CH', 'telecopie2_pere', unicode_and_strip, 'télécopie 2 du père'),
|
240
|
('TELECOPIEMERE_CH', 'telecopie_mere', unicode_and_strip, 'télécopie de la mère'),
|
241
|
('TELECOPIE2MERE_CH', 'telecopie2_mere', unicode_and_strip, 'télécopie 2 de la mère'),
|
242
|
('PROFPERE_CH', 'profession_pere', unicode_and_strip, 'profession du père'),
|
243
|
('PROFMERE_CH', 'profession_mere', unicode_and_strip, 'profession de la mère'),
|
244
|
('LIEUTRAVPERE_CH', 'lieu_travail_pere', unicode_and_strip, 'lieu de travail du père'),
|
245
|
('LIEUTRAVMERE_CH', 'lieu_travail_mere', unicode_and_strip, 'lieu de travail de la mère'),
|
246
|
('RUETRAVPERE_CH', 'rue_travail_pere', unicode_and_strip, 'rue travail père'),
|
247
|
('RUE2TRAVPERE_CH', 'rue2_travail_pere', unicode_and_strip, 'rue 2 travail père'),
|
248
|
('RUE3TRAVPERE_CH', 'rue3_travail_pere', unicode_and_strip, 'rue 3 travail père'),
|
249
|
('CPTRAVPERE_CH', 'code_postal_travail_pere', unicode_and_strip, 'code postal travail père'),
|
250
|
('VILLETRAVPERE_CH', 'ville_travail_pere', unicode_and_strip, 'ville travail père'),
|
251
|
('RUETRAVMERE_CH', 'rue_travail_mere', unicode_and_strip, 'rue travail mère'),
|
252
|
('RUE2TRAVMERE_CH', 'rue2_travail_mere', unicode_and_strip, 'rue 2 travail mère'),
|
253
|
('RUE3TRAVMERE_CH', 'rue3_travail_mere', unicode_and_strip, 'rue 3 travail mère'),
|
254
|
('CPTRAVMERE_CH', 'code_postal_travail_mere', unicode_and_strip, 'code postal travail mère'),
|
255
|
('VILLETRAVMERE_CH', 'ville_travail_mere', unicode_and_strip, 'ville travail mère'),
|
256
|
('TELPROFPERE_CH', 'telephone_travail_pere', unicode_and_strip, 'téléphone professionnel père'),
|
257
|
('TEL2PROFPERE_CH', 'telephone2_travail_pere', unicode_and_strip, 'téléphone 2 professionnel père'),
|
258
|
('TELMOBILPERE_CH', 'telephone_mobile_pere', unicode_and_strip, 'téléphone mobile'),
|
259
|
('TELPROFMERE_CH', 'telephone_travail_mere', unicode_and_strip, 'téléphone travail mère'),
|
260
|
('TEL2PROFMERE_CH', 'telephone2_travail_mere', unicode_and_strip, 'téléphone 2 travail mère'),
|
261
|
('TELMOBILMERE_CH', 'telephone_mobile_mere', unicode_and_strip, 'téléphone mobile mère'),
|
262
|
('TOTALDU_MO', 'total_du', unicode_and_strip, 'total dû'),
|
263
|
('TOTALREGLE_MO', 'total_regle', unicode_and_strip, 'total réglé'),
|
264
|
('NUMCENTRESS_CH', 'num_centre_securite_sociale', unicode_and_strip, 'n° centre sécurité sociale'),
|
265
|
('NOMCENTRESS_CH', 'nom_centre_securite_sociale', unicode_and_strip, 'nom centre sécurité sociale'),
|
266
|
('NUMASSURANCE_CH', 'num_assurance', unicode_and_strip, 'n° assurance'),
|
267
|
('NOMASSURANCE_CH', 'nom_assurance', unicode_and_strip, 'nom assurance'),
|
268
|
('RIVOLI_EN', 'code_rivoli', unicode_and_strip, 'identifiant code rivoli'),
|
269
|
('NUMCOMPTE_CH', 'numero_compte_comptable', unicode_and_strip, 'n° compte comptable'),
|
270
|
('EMAILPERE_CH', 'email_pere', unicode_and_strip, 'email du père'),
|
271
|
('EMAILMERE_CH', 'email_mere', unicode_and_strip, 'email de la mère'),
|
272
|
('NUMALLOCATAIRE_CH', 'numero_allocataire', unicode_and_strip, 'n° allocataire'),
|
273
|
('COMMENTAIRE_ME', 'commentaire', unicode_and_strip, 'commentaires / notes'),
|
274
|
('IDCSPPERE', 'identifiant_csp_pere', unicode_and_strip, 'référence identifiant csp'),
|
275
|
('IDCSPMERE', 'identifiant_csp_mere', unicode_and_strip, 'référence identifiant csp'),
|
276
|
('IDSECTEURS', 'identifiant_secteurs', unicode_and_strip, 'référence identifiant secteurs'),
|
277
|
('IDZONES', 'identifiant_zones', unicode_and_strip, 'référence identifiant zones'),
|
278
|
('IDRUES', 'identifiant_rues', unicode_and_strip, 'référence identifiant rues'),
|
279
|
('IDVILLES', 'identifiant_villes', unicode_and_strip, 'référence identifiant villes'),
|
280
|
('IDREGIMES', 'identifiant_regimes', unicode_and_strip, 'référence identifiant regimes'),
|
281
|
('IDSITUATIONFAMILLE', 'identifiant_situation_famille', unicode_and_strip, 'référence identifiant situationfamille'),
|
282
|
('NUMSECUPERE_CH', 'num_securite_sociale_pere', unicode_and_strip, 'n° secu père'),
|
283
|
('NUMSECUMERE_CH', 'num_securite_sociale_mere', unicode_and_strip, 'n° secu mère'),
|
284
|
('NATIONPERE_CH', 'nation_pere', unicode_and_strip, 'nationalité père'),
|
285
|
('NATIONMERE_CH', 'nation_mere', unicode_and_strip, 'nationalité mère'),
|
286
|
('NOMJEUNEFILLE_CH', 'nom_jeune_fille', unicode_and_strip, 'nom jeune fille'),
|
287
|
('IDCAFS', 'idcafs', unicode_and_strip, 'référence identifiant cafs'),
|
288
|
('CHAMPLIBRE1_CH', 'champ_libre1', unicode_and_strip, 'valeur champ libre 1'),
|
289
|
('CHAMPLIBRE2_CH', 'champ_libre2', unicode_and_strip, 'valeur champ libre 2'),
|
290
|
('CHAMPCALCULE1_CH', 'champ_calcule1', unicode_and_strip, 'valeur champ calculé 1'),
|
291
|
('CHAMPCALCULE2_CH', 'champ_calcule2', unicode_and_strip, 'valeur champ calculé 2'),
|
292
|
('IDTABLELIBRE1', 'id_table_libre1', unicode_and_strip, 'idtablelibre1'),
|
293
|
('IDTABLELIBRE3', 'id_table_libre3', unicode_and_strip, 'idtablelibre3'),
|
294
|
('IDTABLELIBRE2', 'id_table_libre2', unicode_and_strip, 'idtablelibre2'),
|
295
|
('IDTABLELIBRE4', 'id_table_libre4', unicode_and_strip, 'idtablelibre4'),
|
296
|
('NOMURSSAF_CH', 'nom_urssaf', unicode_and_strip, 'nom urssaf'),
|
297
|
('NUMURSSAF_CH', 'num_urssaf', unicode_and_strip, 'n° urssaf'),
|
298
|
('IDPROFPERE', 'identifiant_profession_pere', unicode_and_strip, 'référence identifiant profession'),
|
299
|
('IDPROFMERE', 'identifiant_profession_mere', unicode_and_strip, 'référence identifiant profession'),
|
300
|
('ALLOCATAIRE_CH', 'allocataire', unicode_and_strip, 'allocataire père ou mère (p,m)'),
|
301
|
# ('PHOTOPERE_CH', 'photo_pere', unicode_and_strip, 'photographie père'),
|
302
|
# ('PHOTOMERE_CH', 'photo_mere', unicode_and_strip, 'photographie mère'),
|
303
|
('NUMRUE_CH', 'numero_rue', unicode_and_strip, 'numéro de rue'),
|
304
|
('NUMRUEPERE_CH', 'numero_rue_pere', unicode_and_strip, 'numéro de rue père'),
|
305
|
('NUMRUEMERE_CH', 'numero_rue_mere', unicode_and_strip, 'numéro de rue mère'),
|
306
|
('IDPORTAIL_FAMILLES', 'identifiant_portail_familles', unicode_and_strip, 'identifiant de portail_familles'),
|
307
|
('ECHEANCEASSURANCE_DA', 'echeance_assurance', unicode_and_strip, 'date echéance assurance'),
|
308
|
('RM_MIKADO_MO', 'rm_mikado', unicode_and_strip, 'revenus mensuels mikado'),
|
309
|
('RA_MIKADO_MO', 'ra_mikado', unicode_and_strip, 'revenus annuels mikado'),
|
310
|
('QF_MIKADO_MO', 'qf_mikado', unicode_and_strip, 'quotient familial mikado'),
|
311
|
('RM_DIABOLO_MO', 'rm_diabolo', unicode_and_strip, 'revenus mensuels diabolo'),
|
312
|
('RA_DIABOLO_MO', 'ra_diabolo', unicode_and_strip, 'revenus annuels diabolo'),
|
313
|
('QF_DIABOLO_MO', 'qf_diabolo', unicode_and_strip, 'quotient familial diabolo'),
|
314
|
('RM_OLIGO_MO', 'rm_oligo', unicode_and_strip, 'revenus mensuels oligo'),
|
315
|
('RA_OLIGO_MO', 'ra_oligo', unicode_and_strip, 'revenus annuels oligo'),
|
316
|
('QF_OLIGO_MO', 'qf_oligo', unicode_and_strip, 'quotient familial oligo'),
|
317
|
('APPLICATION_REV_MIKADO_DA', 'application_rev_mikado', unicode_and_strip, 'date d\'application des revenus de mikado'),
|
318
|
('APPLICATION_REV_DIABOLO_DA', 'application_rev_diabolo', unicode_and_strip, 'date d\'application des revenus de diabolo'),
|
319
|
('APPLICATION_REV_OLIGO_DA', 'application_rev_oligo', unicode_and_strip, 'date d\'application des revenus de oligo'),
|
320
|
)
|
321
|
|
322
|
def __init__(self, *args, **kwargs):
|
323
|
self.children = []
|
324
|
super(Family, self).__init__(*args, **kwargs)
|
325
|
|
326
|
def complete(self):
|
327
|
k = [a for a,b,c,d in self.MORE_COLUMNS]
|
328
|
list(self.client('LISTER_FAMILLES', args=(','.join(k), self.id),
|
329
|
columns=self.MORE_COLUMNS, instances=(self,)))
|
330
|
l = self.client.get_children(self.id).values()
|
331
|
self.children = sorted(l, key=lambda c: c.id)
|
332
|
return self
|
333
|
|
334
|
@property
|
335
|
def invoices(self):
|
336
|
return [invoice for id, invoice in self.client.invoices.iteritems() if invoice.id_famille == self.id]
|
337
|
|
338
|
def add_child(self, child):
|
339
|
if hasattr(self, 'id'):
|
340
|
child.id_famille = self.id
|
341
|
child.client = self.client
|
342
|
self.children.append(child)
|
343
|
|
344
|
def save(self):
|
345
|
if hasattr(self, 'id'):
|
346
|
self.client.update_family(self)
|
347
|
else:
|
348
|
self.code_interne = self.client.new_code_interne()
|
349
|
self.id = self.client.add_family(self)
|
350
|
for child in self.children:
|
351
|
child.id_famille = self.id
|
352
|
child.save()
|
353
|
self.client.clear_cache()
|
354
|
|
355
|
class Invoice(SimpleObject):
|
356
|
COLUMNS = (
|
357
|
('', 'id_famille', int, ''),
|
358
|
('', 'id', int, ''),
|
359
|
('', 'numero', str, ''),
|
360
|
('', 'debut_periode', parse_date, ''),
|
361
|
('', 'fin_periode', parse_date, ''),
|
362
|
('', 'creation', parse_date, ''),
|
363
|
('', 'echeance', parse_date, ''),
|
364
|
('', 'montant', Decimal, ''),
|
365
|
('', 'reste_du', Decimal, ''),
|
366
|
)
|
367
|
_detail = None
|
368
|
|
369
|
def detail(self):
|
370
|
if not self._detail:
|
371
|
self.client.factures_detail([self])
|
372
|
return self._detail
|
373
|
|
374
|
@property
|
375
|
def family(self):
|
376
|
return self.client.families[self.id_famille]
|
377
|
|
378
|
def paid(self):
|
379
|
return self.reste_du == Decimal(0)
|
380
|
|
381
|
class DominoWs(object):
|
382
|
'''Interface to the WebService exposed by Abelium Domino.
|
383
|
|
384
|
It allows to retrieve family and invoices.
|
385
|
|
386
|
Beware that it does a lot of caching to limit call to the webservice, so
|
387
|
if you need fresh data, call clear_cache()
|
388
|
|
389
|
All family are in the families dictionnary and all invoices in the
|
390
|
invoices dictionnary.
|
391
|
'''
|
392
|
|
393
|
def __init__(self, url, domain, login, password, location=None):
|
394
|
if not Client:
|
395
|
raise ValueError('You need python suds')
|
396
|
logger.debug('creating DominoWs(%r, %r, %r, %r, location=%r)', url, domain, login, password, location)
|
397
|
self.url = url
|
398
|
self.domain = domain
|
399
|
self.login = login
|
400
|
self.password = password
|
401
|
self.client = Client(url, location=location, timeout=60)
|
402
|
self.client.options.cache.setduration(seconds=60)
|
403
|
|
404
|
def clear_cache(self):
|
405
|
'''Remove cached attributes from the instance.'''
|
406
|
|
407
|
for key, value in self.__dict__.items():
|
408
|
if key.startswith('__') and key.endswith('_cache'):
|
409
|
del self.__dict__[key]
|
410
|
|
411
|
def call(self, function_name, *args):
|
412
|
'''Call SOAP method named function_name passing args list as parameters.
|
413
|
|
414
|
Any error is converted into the DominoException class.'''
|
415
|
print 'call', function_name, args
|
416
|
|
417
|
try:
|
418
|
logger.debug('soap call to %s%r', function_name, args)
|
419
|
data = getattr(self.client.service, function_name)(self.domain, self.login, self.password, *args)
|
420
|
logger.debug('result: %s', data)
|
421
|
self.data = data
|
422
|
except IOError, e:
|
423
|
raise DominoException('Erreur IO', e)
|
424
|
if data is None:
|
425
|
data = ''
|
426
|
if data.startswith('ERREUR'):
|
427
|
raise DominoException(data[9:].encode('utf8'))
|
428
|
return data
|
429
|
|
430
|
def parse_tabular_data(self, data):
|
431
|
'''Row are separated by carriage-return, ASCII #13, characters and columns by tabs.
|
432
|
Empty lines (ignoring spaces) are ignored.
|
433
|
'''
|
434
|
|
435
|
rows = data.split(LINE_SEPARATOR)
|
436
|
rows = [[cell.strip() for cell in row.split(COLUMN_SEPARATOR)] for row in rows if row.strip() != '']
|
437
|
return rows
|
438
|
|
439
|
def __call__(self, function_name, cls=None, args=[], instances=None, columns=None):
|
440
|
'''Call SOAP method named function_name, splitlines, map tab separated
|
441
|
values to _map keys in a dictionnary, and use this dictionnary to
|
442
|
initialize an object of class cls.
|
443
|
|
444
|
- If instances is present, the given instances are updated with the
|
445
|
returned content, in order, row by row.
|
446
|
- If cls is not None and instances is None, a new instance of the class
|
447
|
cls is instancied for every row and initialized with the content of
|
448
|
the row.
|
449
|
- If cls and instances are None, the raw data returned by the SOAP call
|
450
|
is returned.
|
451
|
'''
|
452
|
|
453
|
data = self.call(function_name, *args)
|
454
|
if cls or instances:
|
455
|
rows = self.parse_tabular_data(data)
|
456
|
kwargs = {}
|
457
|
if instances:
|
458
|
rows = zip(rows, instances)
|
459
|
for row in rows:
|
460
|
if instances:
|
461
|
row, instance = row
|
462
|
if not row[0]:
|
463
|
continue
|
464
|
for a, b in zip(columns or cls.COLUMNS, row):
|
465
|
x, name, converter, desc = a
|
466
|
kwargs[name] = converter(b.strip())
|
467
|
if instances:
|
468
|
instance.__dict__.update(kwargs)
|
469
|
yield instance
|
470
|
else:
|
471
|
yield cls(client=self, **kwargs)
|
472
|
else:
|
473
|
yield data
|
474
|
|
475
|
def add_family(self, family):
|
476
|
result = self.call('AJOUTER_FAMILLE', family.serialize())
|
477
|
return int(result.strip())
|
478
|
|
479
|
def update_family(self, family):
|
480
|
if not hasattr(family, 'id'):
|
481
|
raise DominoException('Family lacks an "id" attribute, it usually means that it is new.')
|
482
|
result = self.call('MODIFIER_FAMILLE', unicode(family.id), family.serialize())
|
483
|
return result.strip() == 'OK'
|
484
|
|
485
|
def add_child(self, child):
|
486
|
result = self.call('AJOUTER_ENFANT', child.serialize())
|
487
|
return int(result.strip())
|
488
|
|
489
|
def update_child(self, child):
|
490
|
if not hasattr(child, 'id'):
|
491
|
raise DominoException('Family lacks an "id" attribute, it usually means that it is new.')
|
492
|
result = self.call('MODIFIER_ENFANT', unicode(child.id), child.serialize())
|
493
|
return result.strip() == 'OK'
|
494
|
|
495
|
@property
|
496
|
@object_cached
|
497
|
def families(self):
|
498
|
'''Dictionary of all families indexed by their id.
|
499
|
|
500
|
After the first use, the value is cached. Use clear_cache() to reset
|
501
|
it.
|
502
|
'''
|
503
|
|
504
|
return self.get_families()
|
505
|
|
506
|
def get_families(self, id_famille=0, full=False):
|
507
|
'''Get families informations.
|
508
|
There is no caching.
|
509
|
|
510
|
id_famille - if not 0, the family with this id is retrieved. If 0
|
511
|
all families are retrieved. Default to 0.
|
512
|
full - If True return all the columns of the family table. Default
|
513
|
to False.
|
514
|
'''
|
515
|
columns = Family.MORE_COLUMNS if full else Family.COLUMNS
|
516
|
families = self('LISTER_FAMILLES',
|
517
|
Family,
|
518
|
args=(','.join([x[0] for x in columns]), id_famille))
|
519
|
return dict([(int(x), x) for x in families])
|
520
|
|
521
|
def get_children(self, id_famille=0):
|
522
|
columns = Child.COLUMNS
|
523
|
if id_famille == 0:
|
524
|
children = self('LISTER_ENFANTS',
|
525
|
Child,
|
526
|
args=((','.join([x[0] for x in columns])),))
|
527
|
else:
|
528
|
children = self('LISTER_ENFANTS_FAMILLE',
|
529
|
Child,
|
530
|
args=(id_famille, (','.join([x[0] for x in columns]))))
|
531
|
return dict([(int(x), x) for x in children])
|
532
|
|
533
|
def get_urgent_contacts(self, id_enfant):
|
534
|
columns = UrgentContact.COLUMNS
|
535
|
urgent_contacts = self('LISTER_PERSONNES_URGENCE',
|
536
|
UrgentContact,
|
537
|
|
538
|
args=((id_enfant, ','.join([x[0] for x in columns]))))
|
539
|
return dict([(int(x), x) for x in urgent_contacts])
|
540
|
|
541
|
@property
|
542
|
@object_cached
|
543
|
def invoices(self):
|
544
|
'''Dictionnary of all invoices indexed by their id.
|
545
|
|
546
|
After the first use, the value is cached. Use clear_cache() to reset
|
547
|
it.
|
548
|
'''
|
549
|
invoices = self.get_invoices()
|
550
|
for invoice in invoices.values():
|
551
|
invoice.famille = self.families[invoice.id_famille]
|
552
|
return invoices
|
553
|
|
554
|
def new_code_interne(self):
|
555
|
max_ci = 0
|
556
|
for family in self.families.values():
|
557
|
try:
|
558
|
max_ci = max(max_ci, int(family.code_interne))
|
559
|
except:
|
560
|
pass
|
561
|
return '%05d' % (max_ci+1)
|
562
|
|
563
|
def get_invoices(self, id_famille=0, state='TOUTES'):
|
564
|
'''Get invoices informations.
|
565
|
|
566
|
id_famille - If value is not 0, only invoice for the family with
|
567
|
this id are retrieved. If value is 0, invoices for all families are
|
568
|
retrieved. Default to 0.
|
569
|
etat - state of the invoices to return, possible values are
|
570
|
'SOLDEES', 'NON_SOLDEES', 'TOUTES'.
|
571
|
'''
|
572
|
invoices = self('LISTER_FACTURES_FAMILLE', Invoice,
|
573
|
args=(id_famille, state))
|
574
|
invoices = list(invoices)
|
575
|
for invoice in invoices:
|
576
|
invoice.famille = self.families[invoice.id_famille]
|
577
|
return dict(((int(x), x) for x in invoices))
|
578
|
|
579
|
FACTURE_DETAIL_HEADERS = ['designation', 'quantite', 'prix', 'montant']
|
580
|
def factures_detail(self, invoices):
|
581
|
'''Retrieve details of some invoice'''
|
582
|
data = self.call('DETAILLER_FACTURES', (''.join(("%s;" % int(x) for x in invoices)),))
|
583
|
try:
|
584
|
tree = etree.fromstring(data.encode('utf8'))
|
585
|
for invoice, facture_node in zip(invoices, tree.findall('facture')):
|
586
|
rows = []
|
587
|
for ligne in facture_node.findall('detail_facture/ligne'):
|
588
|
row = []
|
589
|
rows.append(row)
|
590
|
for header in self.FACTURE_DETAIL_HEADERS:
|
591
|
if header in ligne.attrib:
|
592
|
row.append((header, ligne.attrib[header]))
|
593
|
etablissement = facture_node.find('detail_etablissements/etablissement')
|
594
|
if etablissement is not None:
|
595
|
nom = etablissement.get('nom').strip()
|
596
|
else:
|
597
|
nom = ''
|
598
|
d = { 'etablissement': nom, 'lignes': rows }
|
599
|
invoice._detail = d
|
600
|
except Exception, e:
|
601
|
raise
|
602
|
raise DominoException('Exception when retrieving invoice details', e)
|
603
|
|
604
|
def get_family_by_mail(self, email):
|
605
|
'''Return the first whose one email attribute matches the given email'''
|
606
|
for famille in self.families.values():
|
607
|
if email in (famille.email_pere, famille.email_mere,
|
608
|
famille.adresse_internet):
|
609
|
return famille
|
610
|
return None
|
611
|
|
612
|
def get_family_by_code_interne(self, code_interne):
|
613
|
'''Return the first whose one email attribute matches the given email'''
|
614
|
for famille in self.families.values():
|
615
|
if getattr(famille, 'code_interne', None) == code_interne:
|
616
|
return famille
|
617
|
return None
|
618
|
|
619
|
def pay_invoice(self, id_invoices, amount, other_information, date=None):
|
620
|
'''Notify Domino of the payment of some invoices.
|
621
|
|
622
|
id_invoices - integer if of the invoice or Invoice instances
|
623
|
amount - amount as a Decimal object
|
624
|
other_information - free content to attach to the payment, like a
|
625
|
bank transaction number for correlation.
|
626
|
date - date of the payment, must be a datetime object. If None,
|
627
|
now() is used. Default to None.
|
628
|
'''
|
629
|
|
630
|
if not date:
|
631
|
date = datetime.datetime.now()
|
632
|
due = sum([self.invoices[int(id_invoice)].due for id_invoice in id_invoices])
|
633
|
if Decimal(amount) == Decimal(due):
|
634
|
return self('SOLDER_FACTURE', None, args=(str(amount),
|
635
|
''.join([ '%s;' % int(x) for x in id_invoices]),
|
636
|
date.strftime('%Y-%m-%d'), other_information))
|
637
|
else:
|
638
|
raise DominoException('Amount due and paid do not match', { 'due': due, 'paid': amount})
|