]> SALOME platform Git repositories - tools/eficas.git/blob - Ihm/I_MACRO_ETAPE.py
Salome HOME
Memorisation des unites uniquement pour reevaluation
[tools/eficas.git] / Ihm / I_MACRO_ETAPE.py
1 #            CONFIGURATION MANAGEMENT OF EDF VERSION
2 # ======================================================================
3 # COPYRIGHT (C) 1991 - 2002  EDF R&D                  WWW.CODE-ASTER.ORG
4 # THIS PROGRAM IS FREE SOFTWARE; YOU CAN REDISTRIBUTE IT AND/OR MODIFY
5 # IT UNDER THE TERMS OF THE GNU GENERAL PUBLIC LICENSE AS PUBLISHED BY
6 # THE FREE SOFTWARE FOUNDATION; EITHER VERSION 2 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, BUT
10 # WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF
11 # MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. SEE THE GNU
12 # GENERAL PUBLIC LICENSE FOR MORE DETAILS.
13 #
14 # YOU SHOULD HAVE RECEIVED A COPY OF THE GNU GENERAL PUBLIC LICENSE
15 # ALONG WITH THIS PROGRAM; IF NOT, WRITE TO EDF R&D CODE_ASTER,
16 #    1 AVENUE DU GENERAL DE GAULLE, 92141 CLAMART CEDEX, FRANCE.
17 #
18 #
19 # ======================================================================
20 """
21 """
22 # Modules Python
23 import sys
24 import traceback,types,string
25
26 # Modules Eficas
27 import I_ETAPE
28 from Noyau.N_ASSD import ASSD
29
30 # import rajoutés suite à l'ajout de Build_sd --> à résorber
31 import Noyau, Validation.V_MACRO_ETAPE
32 from Noyau import N_Exception
33 from Noyau.N_Exception import AsException
34 # fin import à résorber
35
36 class MACRO_ETAPE(I_ETAPE.ETAPE):
37
38   def __init__(self):
39       self.typret=None
40       self.recorded_units={}
41
42   def get_sdprods(self,nom_sd):
43     """ 
44          Fonction : retourne le concept produit par l etape de nom nom_sd
45                     s il existe sinon None
46     """
47     if self.sd and self.sd.nom == nom_sd :return self.sd
48     for co in self.sdprods:
49       if co.nom == nom_sd:return co
50     if type(self.definition.op_init) == types.FunctionType:
51       d={}
52       apply(self.definition.op_init,(self,d))
53       return d.get(nom_sd,None)
54     return None
55
56   def get_contexte_jdc(self,fichier,text):
57     """ 
58          Interprète text comme un texte de jdc et retourne le 
59          contexte final
60          cad le dictionnaire des sd disponibles à la dernière étape
61          Si text n'est pas un texte de jdc valide, retourne None
62          ou leve une exception
63          --> utilisée par ops.POURSUITE et INCLUDE
64     """
65     try:
66        # on essaie de créer un objet JDC auxiliaire avec un contexte initial
67        context_ini = self.parent.get_contexte_avant(self)
68
69        # Indispensable avant de creer un nouveau JDC
70        CONTEXT.unset_current_step()
71        args=self.jdc.args
72        prefix_include=None
73        if hasattr(self,'prefix'):
74           prefix_include=self.prefix
75        # ATTENTION : le dictionnaire recorded_units sert à memoriser les unites des 
76        # fichiers inclus. Il est preferable de garder le meme dictionnaire pendant
77        # tout le traitement et de ne pas le reinitialiser brutalement (utiliser clear plutot)
78        # si on ne veut pas perdre la memoire des unites.
79        # En principe si la memorisation est faite au bon moment il n'est pas necessaire
80        # de prendre cette precaution mais ce n'est pas vrai partout.
81        old_recorded_units=self.recorded_units.copy()
82        self.recorded_units.clear()
83
84        j=self.JdC_aux( procedure=text,cata=self.jdc.cata,
85                                 nom=fichier,
86                                 context_ini = context_ini,
87                                 appli=self.jdc.appli,
88                                 jdc_pere=self.jdc,etape_include=self,
89                                 prefix_include=prefix_include,
90                                 recorded_units=self.recorded_units,
91                                 old_recorded_units=old_recorded_units,**args)
92
93        j.analyse()
94        # On récupère les étapes internes (pour validation)
95        self.etapes=j.etapes
96     except:
97        traceback.print_exc()
98        # On force le contexte (etape courante) à self
99        CONTEXT.unset_current_step()
100        CONTEXT.set_current_step(self)
101        return None
102
103     if not j.cr.estvide():
104        # Erreurs dans l'INCLUDE. On garde la memoire du fichier mais on n'insere pas les concepts
105        # On force le contexte (etape courante) à self
106        CONTEXT.unset_current_step()
107        CONTEXT.set_current_step(self)
108        raise Exception("Impossible de relire le fichier\n"+str(j.cr))
109
110     cr=j.report()
111     if not cr.estvide():
112        # On force le contexte (etape courante) à self
113        CONTEXT.unset_current_step()
114        CONTEXT.set_current_step(self)
115        raise Exception("Le fichier include contient des erreurs\n"+str(j.cr))
116
117     # On recupere le contexte apres la derniere etape
118     j_context=j.get_contexte_avant(None)
119
120     # Cette verification n'est plus necessaire elle est integree dans le JDC_INCLUDE
121     self.verif_contexte(j_context)
122
123     # On remplit le dictionnaire des concepts produits inclus
124     # en retirant les concepts présents dans le  contexte initial
125     # On ajoute egalement le concept produit dans le sds_dict du parent
126     # sans verification car on est sur (verification integrée) que le nommage est possible
127     self.g_context.clear()
128     for k,v in j_context.items():
129        if not context_ini.has_key(k) or context_ini[k] != v:
130            self.g_context[k]=v
131            self.parent.sds_dict[k]=v
132
133
134     # On recupere le contexte courant
135     self.current_context=j.current_context
136     self.index_etape_courante=j.index_etape_courante
137
138     # XXX j.supprime() ???
139     # On rétablit le contexte (etape courante) à self
140     CONTEXT.unset_current_step()
141     CONTEXT.set_current_step(self)
142
143     return j_context
144
145   def verif_contexte(self,context):
146      """
147          On verifie que le contexte context peut etre inséré dans le jeu
148          de commandes à la position de self
149      """
150      for nom_sd,sd in context.items():
151         if not isinstance(sd,ASSD):continue
152         #if self.parent.get_sd_apres_etape(nom_sd,etape=self):
153         if self.parent.get_sd_apres_etape_avec_detruire(nom_sd,sd,etape=self):
154            # Il existe un concept produit par une etape apres self => impossible d'inserer
155            # On force le contexte (etape courante) à self
156            CONTEXT.unset_current_step()
157            CONTEXT.set_current_step(self)
158            raise Exception("Impossible d'inclure le fichier. Un concept de nom " + 
159                            "%s existe déjà dans le jeu de commandes." % nom_sd)
160
161   def reevalue_sd_jdc(self):
162      """
163          Avec la liste des SD qui ont été supprimées, propage la 
164          disparition de ces SD dans toutes les étapes et descendants
165      """
166      l_sd_supp,l_sd_repl = self.diff_contextes()
167      for sd in l_sd_supp:
168         self.parent.delete_concept_after_etape(self,sd)
169      for old_sd,sd in l_sd_repl:
170         self.parent.replace_concept_after_etape(self,old_sd,sd)
171
172   def diff_contextes(self):
173      """ 
174          Réalise la différence entre les 2 contextes 
175          old_contexte_fichier_init et contexte_fichier_init
176          cad retourne la liste des sd qui ont disparu ou ne derivent pas de la meme classe
177          et des sd qui ont ete remplacees
178      """
179      if not hasattr(self,'old_contexte_fichier_init'):return [],[]
180      l_sd_suppressed = []
181      l_sd_replaced = []
182      for old_key in self.old_contexte_fichier_init.keys():
183        if not self.contexte_fichier_init.has_key(old_key):
184          if isinstance(self.old_contexte_fichier_init[old_key],ASSD):
185            l_sd_suppressed.append(self.old_contexte_fichier_init[old_key])
186        else:
187          if isinstance(self.old_contexte_fichier_init[old_key],ASSD):
188             # Un concept de meme nom existe
189             old_class=self.old_contexte_fichier_init[old_key].__class__
190             if not isinstance(self.contexte_fichier_init[old_key],old_class):
191                # S'il n'est pas d'une classe derivee, on le supprime
192                l_sd_suppressed.append(self.old_contexte_fichier_init[old_key])
193             else:
194                l_sd_replaced.append((self.old_contexte_fichier_init[old_key],self.contexte_fichier_init[old_key]))
195      return l_sd_suppressed,l_sd_replaced
196       
197   def control_sdprods(self,d):
198       """
199           Cette methode doit updater le contexte fournit par
200           l'appelant en argument (d) en fonction de sa definition
201           tout en verifiant que ses concepts produits ne sont pas
202           deja definis dans le contexte
203       """
204       if hasattr(self,"fichier_unite"):
205          self.update_fichier_init(self.fichier_unite)
206          self.init_modif()
207
208       if type(self.definition.op_init) == types.FunctionType:
209         apply(self.definition.op_init,(self,d))
210       if self.sd:
211         if d.has_key(self.sd.nom):
212            # Le concept est deja defini
213            if self.reuse and self.reuse is d[self.sd.nom]:
214               # Le concept est reutilise : situation normale
215               pass
216            else:
217               # Redefinition du concept, on l'annule
218               #XXX on pourrait simplement annuler son nom pour conserver les objets
219               # l'utilisateur n'aurait alors qu'a renommer le concept (faisable??)
220               self.sd=self.reuse=self.sdnom=None
221               self.init_modif()
222         else:
223            # Le concept n'est pas defini, on peut updater d
224            d[self.sd.nom]=self.sd
225       # On verifie les concepts a droite du signe =
226       for co in self.sdprods:
227         if d.has_key(co.nom) and co is not d[co.nom] :
228            self.delete_concept(co)
229         else:
230            d[co.nom]=co
231        
232
233   def supprime_sdprods(self):
234       """
235           Fonction:
236             Lors d'une destruction d'etape, detruit tous les concepts produits
237             Un opérateur n a qu un concept produit
238             Une procedure n'en a aucun
239             Une macro en a en général plus d'un
240       """
241       if not self.is_reentrant() :
242          # l'étape n'est pas réentrante
243          # le concept retourné par l'étape est à supprimer car il était
244          # créé par l'étape
245          if self.sd != None :
246             self.parent.del_sdprod(self.sd)
247             self.parent.delete_concept(self.sd)
248       # On détruit les concepts à droite du signe =
249       for co in self.sdprods:
250          self.parent.del_sdprod(co)
251          self.parent.delete_concept(co)
252       # Si la macro a des etapes et des concepts inclus, on les detruit
253       for nom_sd,co in self.g_context.items():
254          if not isinstance(co,ASSD):continue
255          self.parent.del_sdprod(co)
256          self.parent.delete_concept(co)
257       # On met g_context à blanc
258       self.g_context={}
259          
260 #ATTENTION : cette methode surcharge celle de Noyau (a garder en synchro)
261   def Build_sd(self,nom):
262      """
263         Construit le concept produit de l'opérateur. Deux cas 
264         peuvent se présenter :
265
266         - le parent n'est pas défini. Dans ce cas, l'étape prend en charge 
267           la création et le nommage du concept.
268
269         - le parent est défini. Dans ce cas, l'étape demande au parent la 
270           création et le nommage du concept.
271
272      """
273      if not self.isactif():return
274      # CCAR : meme modification que dans I_ETAPE
275      if not self.isvalid(sd='non') : return
276      self.sdnom=nom
277      try:
278         # On positionne la macro self en tant que current_step pour que les 
279         # étapes créées lors de l'appel à sd_prod et à op_init aient la macro
280         #  comme parent 
281         self.set_current_step()
282         if self.parent:
283            sd= self.parent.create_sdprod(self,nom)
284            if type(self.definition.op_init) == types.FunctionType: 
285               apply(self.definition.op_init,(self,self.parent.g_context))
286         else:
287            sd=self.get_sd_prod()
288            if sd != None and self.reuse == None:
289               # On ne nomme le concept que dans le cas de non reutilisation 
290               # d un concept
291               sd.nom=nom
292         self.reset_current_step()
293         # Si on est arrive ici, l'etape est valide
294         self.state="unchanged"
295         self.valid=1
296         if self.jdc and self.jdc.par_lot == "NON" :
297            self.Execute()
298         return sd
299      except AsException,e:
300         self.reset_current_step()
301         # Une erreur s'est produite lors de la construction du concept
302         # Comme on est dans EFICAS, on essaie de poursuivre quand meme
303         # Si on poursuit, on a le choix entre deux possibilités :
304         # 1. on annule la sd associée à self
305         # 2. on la conserve mais il faut qu'elle soit correcte et la retourner
306         # En plus il faut rendre coherents sdnom et sd.nom
307         # On choisit de retourner None et de mettre l'etape invalide 
308         self.sd=None
309         self.sdnom=None
310         self.state="unchanged"
311         self.valid=0
312         return self.sd
313         #raise AsException("Etape ",self.nom,'ligne : ',self.appel[0],
314         #                     'fichier : ',self.appel[1],e)
315      except EOFError:
316         raise
317      except :
318         self.reset_current_step()
319         l=traceback.format_exception(sys.exc_info()[0],sys.exc_info()[1],sys.exc_info()[2])
320         raise AsException("Etape ",self.nom,'ligne : ',self.appel[0],
321                           'fichier : ',self.appel[1]+'\n',
322                            string.join(l))
323
324   def make_contexte_include(self,fichier,text):
325     """
326         Cette méthode sert à créer un contexte en interprétant un texte source
327         Python
328     """
329     # on récupère le contexte d'un nouveau jdc dans lequel on interprete text
330     contexte = self.get_contexte_jdc(fichier,text)
331     if contexte == None :
332       raise Exception("Impossible de construire le jeu de commandes correspondant au fichier")
333     else:
334       # Pour les macros de type include : INCLUDE, INCLUDE_MATERIAU et POURSUITE
335       # l'attribut g_context est un dictionnaire qui contient les concepts produits par inclusion
336       # l'attribut contexte_fichier_init est un dictionnaire qui contient les concepts produits
337       # en sortie de macro. g_context est obtenu en retirant de contexte_fichier_init les concepts
338       # existants en debut de macro contenus dans context_ini (dans get_contexte_jdc)
339       # g_context est utilisé pour avoir les concepts produits par la macro
340       # contexte_fichier_init est utilisé pour avoir les concepts supprimés par la macro
341       self.contexte_fichier_init = contexte
342
343   def reevalue_fichier_init(self):
344       """Recalcule les concepts produits par le fichier enregistre"""
345       old_context=self.contexte_fichier_init
346       try:
347          self.make_contexte_include(self.fichier_ini ,self.fichier_text)
348       except:
349          l=traceback.format_exception_only("Fichier invalide",sys.exc_info()[1])
350          self.fichier_err = string.join(l)
351          #self.etapes=[]
352          self.g_context={}
353
354          self.old_contexte_fichier_init=old_context
355          self.contexte_fichier_init={}
356          self.reevalue_sd_jdc()
357          return
358
359       # L'evaluation s'est bien passee
360       self.fichier_err = None
361       self.old_contexte_fichier_init=old_context
362       self.reevalue_sd_jdc()
363
364   def update_fichier_init(self,unite):
365       """Reevalue le fichier init sans demander (dans la mesure du possible) a l'utilisateur 
366          les noms des fichiers
367          Ceci suppose que les relations entre unites et noms ont été memorisees préalablement
368       """
369       
370       if unite != self.fichier_unite:
371          # Changement d'unite
372          f,text=self.get_file(unite=unite,fic_origine=self.parent.nom)
373          units={}
374          self.fichier_ini = f
375          self.fichier_text=text
376          self.recorded_units=units
377       elif not self.parent.recorded_units.has_key(unite):
378          # Nouvelle unite
379          f,text=self.get_file(unite=unite,fic_origine=self.parent.nom)
380          units={}
381          self.fichier_ini = f
382          self.fichier_text=text
383          self.recorded_units=units
384       else:
385          # Meme unite existante
386          f,text,units=self.parent.recorded_units[unite]
387          self.fichier_ini = f
388          self.fichier_text=text
389          self.recorded_units=units
390
391       self.fichier_err=None
392       self.old_contexte_fichier_init=self.contexte_fichier_init
393
394       try:
395         self.make_contexte_include(f,text)
396         # Les 3 attributs fichier_ini fichier_text recorded_units doivent etre corrects
397         # avant d'appeler change_unit
398         self.parent.change_unit(unite,self,self.fichier_unite)
399       except:
400         # Erreurs lors de l'evaluation de text dans un JDC auxiliaire
401         l=traceback.format_exception_only("Fichier invalide",sys.exc_info()[1])
402         # On conserve la memoire du nouveau fichier
403         # mais on n'utilise pas les concepts crees par ce fichier
404         # on met l'etape en erreur : fichier_err=string.join(l)
405         self.fichier_err=string.join(l)
406         self.parent.change_unit(unite,self,self.fichier_unite)
407         self.g_context={}
408         self.contexte_fichier_init={}
409
410       # Le contexte du parent doit etre reinitialise car les concepts produits ont changé
411       self.parent.reset_context()
412       # Si des concepts ont disparu lors du changement de fichier, on demande leur suppression
413       self.reevalue_sd_jdc()
414
415   def record_unite(self):
416       if self.nom == "POURSUITE":
417          self.parent.record_unit(None,self)
418       else:
419          if hasattr(self,'fichier_unite') : 
420             self.parent.record_unit(self.fichier_unite,self)
421
422   def make_poursuite(self):
423       """ Cette methode est appelée par la fonction sd_prod de la macro POURSUITE
424       """
425       if not hasattr(self,'fichier_ini') :
426          # Si le fichier n'est pas defini on le demande
427          f,text=self.get_file_memo(fic_origine=self.parent.nom)
428          # On memorise le fichier retourne
429          self.fichier_ini = f
430          self.fichier_unite = None
431          self.fichier_text = text
432          self.fichier_err=None
433          import Extensions.jdc_include
434          self.JdC_aux=Extensions.jdc_include.JdC_poursuite
435          self.contexte_fichier_init={}
436
437          if f is None:
438              self.fichier_err="Le fichier POURSUITE n'est pas defini"
439              self.parent.record_unit(None,self)
440              raise Exception(self.fichier_err)
441
442          try:
443            self.make_contexte_include(self.fichier_ini,self.fichier_text)
444            self.parent.record_unit(None,self)
445          except:
446            l=traceback.format_exception_only("Fichier invalide",sys.exc_info()[1])
447            if self.jdc.appli:
448               self.jdc.appli.affiche_alerte("Erreur lors de l'evaluation du fichier poursuite",
449                                             message="Ce fichier ne sera pas pris en compte\n"+string.join(l)
450                                            )
451            self.parent.record_unit(None,self)
452            self.g_context={}
453            self.fichier_err = string.join(l)
454            self.contexte_fichier_init={}
455            raise
456
457       else:
458          # Si le fichier est deja defini on ne reevalue pas le fichier
459          # et on leve une exception si une erreur a été enregistrée
460          self.update_fichier_init(None)
461          if self.fichier_err is not None: raise Exception(self.fichier_err)
462
463   def get_file(self,unite=None,fic_origine=''):
464       """Retourne le nom du fichier et le source correspondant a l'unite unite
465          Initialise en plus recorded_units
466       """
467       units={}
468       if self.jdc :
469          f,text=self.jdc.get_file(unite=unite,fic_origine=fic_origine)
470       else:
471          f,text=None,None
472       self.recorded_units=units
473       return f,text
474
475   def get_file_memo(self,unite=None,fic_origine=''):
476       """Retourne le nom du fichier et le source correspondant a l'unite unite
477          Initialise en plus recorded_units
478       """
479       units={}
480       if self.parent.old_recorded_units.has_key(unite):
481          f,text,units=self.parent.old_recorded_units[unite]
482       elif self.jdc :
483          f,text=self.jdc.get_file(unite=unite,fic_origine=fic_origine)
484       else:
485          f,text=None,None
486       self.recorded_units=units
487       return f,text
488
489 #ATTENTION : cette methode surcharge celle de Noyau (a garder en synchro)
490   def make_include(self,unite=None):
491       """
492           Inclut un fichier dont l'unite logique est unite
493           Cette methode est appelee par la fonction sd_prod de la macro INCLUDE
494           Si l'INCLUDE est invalide, la methode doit produire une exception 
495           Sinon on retourne None. Les concepts produits par l'INCLUDE sont
496           pris en compte par le JDC parent lors du calcul du contexte (appel de ???)
497       """
498
499       # On supprime l'attribut unite qui bloque l'evaluation du source de l'INCLUDE
500       # car on ne s'appuie pas sur lui dans EFICAS mais sur l'attribut fichier_ini
501       del self.unite
502       # Si unite n'a pas de valeur, l'etape est forcement invalide. On peut retourner None
503       if not unite : return
504
505       if not hasattr(self,'fichier_ini') : 
506          # Si le fichier n'est pas defini on le demande
507          f,text=self.get_file_memo(unite=unite,fic_origine=self.parent.nom)
508          # On memorise le fichier retourne
509          self.fichier_ini  = f
510          self.fichier_text = text
511          self.contexte_fichier_init={}
512          self.fichier_unite=unite
513          self.fichier_err=None
514          import Extensions.jdc_include
515          self.JdC_aux=Extensions.jdc_include.JdC_include
516
517          if f is None:
518              self.fichier_err="Le fichier INCLUDE n est pas defini"
519              self.parent.record_unit(unite,self)
520              raise Exception(self.fichier_err)
521
522          try:
523            self.make_contexte_include(self.fichier_ini ,self.fichier_text)
524            self.parent.record_unit(unite,self)
525          except:
526            l=traceback.format_exception_only("Fichier invalide",sys.exc_info()[1])
527            if self.jdc.appli:
528               self.jdc.appli.affiche_alerte("Erreur lors de l'evaluation du fichier inclus",
529                                             message="Ce fichier ne sera pas pris en compte\n"+string.join(l)
530                                            )
531            self.parent.record_unit(unite,self)
532            self.g_context={}
533            self.fichier_err = string.join(l)
534            self.contexte_fichier_init={}
535            raise
536
537       else:
538          # Si le fichier est deja defini on ne reevalue pas le fichier
539          # et on leve une exception si une erreur a été enregistrée
540          self.update_fichier_init(unite)
541          self.fichier_unite=unite
542          if self.fichier_err is not None: raise Exception(self.fichier_err)
543         
544
545 #ATTENTION : cette methode surcharge celle de Noyau (a garder en synchro)
546   def make_contexte(self,fichier,text):
547     """
548         Cette méthode sert à créer un contexte pour INCLUDE_MATERIAU
549         en interprétant un texte source Python
550         Elle est appelee par la fonction sd_prd d'INCLUDE_MATERIAU
551     """
552     # On supprime l'attribut mat qui bloque l'evaluation du source de l'INCLUDE_MATERIAU
553     # car on ne s'appuie pas sur lui dans EFICAS mais sur l'attribut fichier_ini
554     if hasattr(self,'mat'):del self.mat
555     self.fichier_ini =fichier
556     self.fichier_unite =fichier
557     self.fichier_text=text
558     self.fichier_err=None 
559     self.contexte_fichier_init={}
560     # On specifie la classe a utiliser pour le JDC auxiliaire
561     import Extensions.jdc_include
562     self.JdC_aux=Extensions.jdc_include.JdC_include
563     try:
564        self.make_contexte_include(self.fichier_ini ,self.fichier_text)
565        self.parent.record_unit(self.fichier_unite,self)
566     except:
567        l=traceback.format_exception_only("Fichier invalide",sys.exc_info()[1])
568        self.fichier_err = string.join(l)
569        self.parent.record_unit(self.fichier_unite,self)
570        self.g_context={}
571        self.contexte_fichier_init={}
572        raise
573
574 #ATTENTION : cette methode surcharge celle de Noyau (a garder en synchro)
575   def update_sdprod(self,cr='non'):
576      # Cette methode peut etre appelee dans EFICAS avec des mots cles de 
577      # la commande modifies. Ceci peut conduire a la construction ou
578      # a la reconstruction d'etapes dans le cas d'INCLUDE ou d'INCLUDE_MATERIAU
579      # Il faut donc positionner le current_step avant l'appel
580      CONTEXT.unset_current_step()
581      CONTEXT.set_current_step(self)
582      valid=Validation.V_MACRO_ETAPE.MACRO_ETAPE.update_sdprod(self,cr=cr)
583      CONTEXT.unset_current_step()
584      return valid
585