merge statut postes
authorOlivier Larchevêque <olivier.larcheveque@auf.org>
Tue, 27 Mar 2012 19:38:54 +0000 (15:38 -0400)
committerOlivier Larchevêque <olivier.larcheveque@auf.org>
Tue, 27 Mar 2012 19:38:54 +0000 (15:38 -0400)
project/dae/admin.py
project/dae/exporter.py [new file with mode: 0644]
project/dae/management/__init__.py [new file with mode: 0644]
project/dae/management/commands/__init__.py [new file with mode: 0644]
project/dae/management/commands/dae.py [new file with mode: 0644]
project/dae/managers.py
project/dae/models.py
project/rh/admin.py
project/rh/models.py

index 76443eb..993f38b 100644 (file)
@@ -1,10 +1,15 @@
 # -*- encoding: utf-8 -*-
 
-from django.contrib import admin
 from django import forms
+
+from django.core.urlresolvers import reverse
+from django.core.mail import mail_admins
+from django.contrib import admin
+from django.contrib import messages
+from django.shortcuts import redirect
 from reversion.admin import VersionAdmin
 from auf.django.workflow.models import WorkflowCommentaire
-from models import Poste, Dossier
+from models import Poste, Dossier, DossierFinalise, PosteFinalise
 
 class PosteAdmin(VersionAdmin):
     list_display = ('nom', 'implantation', 'etat', )
@@ -111,14 +116,108 @@ class DossierStatutAdmin(StatutAdmin):
         return obj.poste.implantation
 
 
-
 class PosteStatutAdmin(StatutAdmin):
     search_fields = ('nom', )
     list_display = ('nom', 'implantation', '_etat', )
     form = StatutPosteForm
 
 
+class ImportAdmin(admin.ModelAdmin,):
+
+    def has_delete_permission(self, request, obj=None):
+        return False
+
+    def has_add_permission(self, request):
+        return False
+
+    def has_change_permission(self, request, obj=None):
+        if obj is None:
+            return True
+        else:
+            return False
+
+    def get_actions(self, request):
+        actions = super(ImportAdmin, self).get_actions(request)
+        del actions['delete_selected']
+        return actions
+
+
+class ImportDossierAdmin(ImportAdmin):
+    list_display = ('_poste', '_employe', '_dae', )
+    actions = ['importer_dans_RH', ]
+
+    def _employe(self, obj):
+        return obj.employe
+
+    def _poste(self, obj):
+        rh = obj.poste.rh_importation()
+        if rh is not None:
+            extra = u"<a href='%s' style='color: red;'> (déjà importé)</a>" % \
+                reverse('admin:rh_poste_change', args=(rh.id,))
+        else:
+            extra = u""
+
+        return u"%s%s" % (obj.poste.nom, extra)
+    _poste.allow_tags = True
+
+
+
+    def _dae(self, obj):
+        url = reverse('embauche_consulter', args=(obj.id, ))
+        return "<a href='%s'>voir la DAE</a>" % url
+    _dae.allow_tags = True
+
+    def importer_dans_RH(self, request, data):
+        selected = request.POST.getlist(admin.ACTION_CHECKBOX_NAME)
+        dae = [d for d in Dossier.objects.filter(id__in=selected)]
+        for d in dae:
+            try:
+                d.importer(dry_run=True)
+                copie = d.importer()
+                messages.add_message(request, messages.SUCCESS, u"%s importé dans\
+                    RH" % d)
+                url = reverse('admin:rh_dossier_change', args=(copie.id, ))
+            except Exception, e:
+                mail_admins('DAE import', unicode(e))
+                messages.add_message(request, messages.ERROR, u"%s ne peut pas\
+                être importé" % d)
+                url = reverse('admin:dae_dossierfinalise_changelist')
+            return redirect(url)
+
+
+class ImportPosteAdmin(ImportAdmin):
+    list_display = ('nom', '_implantation', '_dae', )
+    actions = ['importer_dans_RH', ]
+
+    def _implantation(self, obj):
+        return obj.implantation
+
+    def _dae(self, obj):
+        url = reverse('poste_consulter', args=(obj.id, ))
+        return "<a href='%s'>voir le poste dans DAE</a>" % url
+    _dae.allow_tags = True
+
+    def importer_dans_RH(self, request, data):
+        selected = request.POST.getlist(admin.ACTION_CHECKBOX_NAME)
+        dae = [d for d in Poste.objects.filter(id__in=selected)]
+        for p in dae:
+            try:
+                p.importer(dry_run=True)
+                copie = p.importer()
+                messages.add_message(request, messages.SUCCESS, u"%s importé dans\
+                        RH" % d)
+                url = reverse('admin:rh_poste_change', args=(copie.id, ))
+            except Exception, e:
+                mail_admins('DAE import', unicode(e))
+                messages.add_message(request, messages.ERROR, u"%s ne peut pas\
+                être importé" % p)
+                url = reverse('admin:dae_postefinalise_changelist')
+            return redirect(url)
+
+
 admin.site.register(Poste, PosteAdmin)
 admin.site.register(Dossier, DossierAdmin)
 admin.site.register(ProxyDossierStatut, DossierStatutAdmin)
 admin.site.register(ProxyPosteStatut, PosteStatutAdmin)
+admin.site.register(DossierFinalise, ImportDossierAdmin)
+admin.site.register(PosteFinalise, ImportPosteAdmin)
diff --git a/project/dae/exporter.py b/project/dae/exporter.py
new file mode 100644 (file)
index 0000000..e313640
--- /dev/null
@@ -0,0 +1,233 @@
+# -*- coding: utf-8 -*-
+
+import sys
+import codecs
+import os
+import  shutil
+
+from django.conf import settings
+from project.rh import models as rh
+import models as dae
+
+SKIP = "** SKIP **"
+
+class SuperCopier(object):
+    """
+    Classe abstraite pour faire de la copie profonde sur des objets en BD.
+    Elle prévoit des hooks pour traiter des propriétés en particulier.
+    """
+    dry_run = True
+    verbosity = 0   
+    stdout = codecs.getwriter('utf8')(sys.stdout)
+
+    def __init__(self, dry_run=True, verbosity=0):
+        self.verbosity = verbosity
+        self.dry_run = dry_run
+
+    def out(self, txt, level=0):
+        if self.verbosity >= level:
+            self.stdout.write(txt)
+
+    def clean_id(self, source, copy, parent, field, value):
+        self.out(SKIP, 2)
+        return []
+
+    def parent(self, parent, child):
+        if not self.dry_run:
+            parent.save()
+        klass = parent.__class__.__name__.lower()
+        k = "%s_id" % klass
+        setattr(child, k, parent.id)
+
+    def duplicate(self, obj, parent=None, level=0):
+        klass = getattr(rh, obj.__class__.__name__)
+        copy = klass()
+    
+        indent = " "*level*4
+        self.out(u"\n%s [%s] %s" % (indent, str(obj.__class__),
+            obj), 1)
+        
+        for f in obj._meta.fields:
+            value = getattr(obj, f.name)
+
+            self.out(u"\n%s * %s: %s " % (indent, f.name, value), 2)
+
+            cleanup_fct = "clean_%s" % f.name
+            if hasattr(self, cleanup_fct):
+                cleaner = getattr(self, cleanup_fct)
+                cleaner(obj, copy, parent, f, value)
+            else:
+                setattr(copy, f.name, value)
+
+        for obj_composition in obj._meta._related_objects_cache:
+            app_label, dummy = obj_composition.name.split(':')
+            field_name = obj_composition.field.rel.related_name
+            if field_name == '+':
+                continue
+            self.out(u"\n%s + [%s] %s " % (indent, app_label, field_name), 1)
+            try:
+                cleanup_fct = "clean_%s" % field_name
+                if hasattr(self, cleanup_fct):
+                    cleaner = getattr(self, cleanup_fct)
+                    children = cleaner(obj, copy, parent, f, value)
+                else:
+                    children = getattr(obj, field_name).all()
+            except Exception, e:
+                # no reverse relation
+                self.out(" %s %s" % (field_name, str(e)), 1)
+                continue
+
+            for child in children:
+                child_copy = self.duplicate(child, parent=copy, level=level+1,)
+                parentship_fct = "parent_%s" % (field_name)
+                if hasattr(self,parentship_fct):
+                    parentship = getattr(self, parentship_fct)
+                else:
+                    parentship = getattr(self, "parent")
+                parentship(copy, child_copy)
+                if not self.dry_run:
+                    child_copy.save()
+    
+        return copy
+
+    def copy(self, obj):
+        return self.duplicate(obj)
+
+
+class PosteCopier(SuperCopier):
+
+    def copy(self, obj):
+        copy = super(PosteCopier, self).copy(obj)
+        if not self.dry_run:
+            dae.ImportPoste(dae=obj, rh=copy).save()
+        return copy
+
+    def clean_rh_dossiers(self, source, copy, parent, field, value):
+        self.out(SKIP, 1)
+        return []
+
+    def clean_rh_comparaisons_internes(self, source, copy, parent, field, value):
+        self.out(SKIP, 1)
+        return []
+
+    def clean_rh_financements(self, source, copy, parent, field, value):
+        self.out(SKIP, 1)
+        return []
+
+    def clean_rh_pieces(self, source, copy, parent, field, value):
+        self.out(SKIP, 1)
+        return []
+
+    def clean_dae_dossiers(self, source, copy, parent, field, value):
+        self.out(SKIP, 1)
+        return []
+
+    def clean_poste(self, source, copy, parent, field, value):
+        self.out(SKIP, 1)
+        return []
+
+    def clean_fichier(self, source, copy, parent, field, value):
+        if not self.dry_run and parent.id is None:
+            parent.save()
+        filename = value.path.split('/')[-1]
+        copy.poste_id = parent.id
+        new_value = rh.poste_piece_dispatch(copy, filename)
+
+        app, model, id, f = new_value.split('/')
+        app_path = os.path.join(settings.PRIVE_MEDIA_ROOT, app)
+        model_path = os.path.join(settings.PRIVE_MEDIA_ROOT, app, model)
+        id_path = os.path.join(settings.PRIVE_MEDIA_ROOT, app, model, id)
+        if not os.path.exists(app_path):
+            os.mkdir(app_path)
+        if not os.path.exists(model_path):
+            os.mkdir(model_path)
+        if not os.path.exists(id_path):
+            os.mkdir(id_path)
+        src = value.path
+        dst = os.path.join(settings.PRIVE_MEDIA_ROOT, new_value)
+        shutil.copy(src, dst)
+        setattr(copy, field.name, new_value)
+
+
+class DossierCopier(SuperCopier):
+
+    def copy(self, obj):
+        copy = super(DossierCopier, self).copy(obj)
+        if not self.dry_run:
+            dae.ImportDossier(dae=obj, rh=copy).save()
+        return copy
+
+    def clean_rh_contrats(self, source, copy, parent, field, value):
+        self.out(SKIP, 1)
+        return []
+
+    def clean_rh_dossierpieces(self, source, copy, parent, field, value):
+        self.out(SKIP, 1)
+        return []
+
+    def clean_rh_comparaisons(self, source, copy, parent, field, value):
+        self.out(SKIP, 1)
+        return []
+    
+    def clean_rh_remunerations(self, source, copy, parent, field, value):
+        self.out(SKIP, 1)
+        return []
+
+    def clean_dossier(self, source, copy, parent, field, value):
+        self.out(SKIP, 1)
+        return []
+
+
+    def clean_employe(self, source, copy, parent, field, value):
+        if source.employe.id_rh is not None:
+            copy.employe = source.employe.id_rh
+        else:
+            nouvel_employe = rh.Employe()
+            nouvel_employe.nom = source.employe.nom
+            nouvel_employe.prenom = source.employe.prenom
+            nouvel_employe.genre = source.employe.genre
+            nouvel_employe.save()
+            copy.employe = nouvel_employe
+
+    def clean_poste(self, source, copy, parent, field, value):
+        # poste comparaison a un champs nommé poste
+        if isinstance(value, unicode):
+            setattr(copy, field.name, value)
+            return
+
+        if dae.ImportPoste.objects.filter(dae=value).exists():
+            poste = dae.ImportPoste.objects.get(dae=value).rh
+        else:
+            copier = PosteCopier(verbosity=self.verbosity,
+                    dry_run=self.dry_run)
+            poste = copier.copy(value)
+        copy.poste_id = poste.id
+
+    def clean_fichier(self, source, copy, parent, field, value):
+        if not self.dry_run and parent.id is None:
+            parent.save()
+        filename = value.path.split('/')[-1]
+        copy.dossier_id = parent.id
+
+        ct = copy.__class__.__name__.lower()
+        if ct == 'contrat':
+            new_value = rh.contrat_dispatch(copy, filename)
+        elif ct == 'dossierpiece':
+            new_value = rh.dossier_piece_dispatch(copy, filename)
+        else:
+            raise Exception('fichier %s à mapper!' % ct)
+        
+        app, model, id, f = new_value.split('/')
+        app_path = os.path.join(settings.PRIVE_MEDIA_ROOT, app)
+        model_path = os.path.join(settings.PRIVE_MEDIA_ROOT, app, model)
+        id_path = os.path.join(settings.PRIVE_MEDIA_ROOT, app, model, id)
+        if not os.path.exists(app_path):
+            os.mkdir(app_path)
+        if not os.path.exists(model_path):
+            os.mkdir(model_path)
+        if not os.path.exists(id_path):
+            os.mkdir(id_path)
+        src = value.path
+        dst = os.path.join(settings.PRIVE_MEDIA_ROOT, new_value)
+        shutil.copy(src, dst)
+        setattr(copy, field.name, new_value)
diff --git a/project/dae/management/__init__.py b/project/dae/management/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/project/dae/management/commands/__init__.py b/project/dae/management/commands/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/project/dae/management/commands/dae.py b/project/dae/management/commands/dae.py
new file mode 100644 (file)
index 0000000..0c2b404
--- /dev/null
@@ -0,0 +1,34 @@
+# -*- encoding: utf-8 -*-
+
+from django.core.management.base import BaseCommand
+
+from project.dae import  models as dae
+
+class Command(BaseCommand):
+
+    
+    def handle(self, *args, **options):
+
+        if len(args) < 2:
+            print len(args)
+            self.stdout.write("dae <modele> <id> <run>\n")
+            return
+
+        classname = args[0]
+        klass = getattr(dae, classname, None)
+        if klass is None:
+            self.stdout.write("Le modèle %s n'existe pas dans DAE.\n" % classname)
+
+        id = int(args[1])
+        try:
+            obj = klass.objects.get(id=id)
+        except:
+            self.stdout.write("L'id %s n'existe pas pour le modèle %s dans DAE.\n" % \
+                    (id, classname))
+            return
+
+
+        copy = obj.importer()
+        self.stdout.write(u"\n[%s] DAE:%s => RH:%s\n" %
+                (obj.__class__.__name__, obj.id, copy.id))
+        
index 197d64c..dabe062 100644 (file)
@@ -3,17 +3,14 @@
 from django.db import models
 from django.db.models import Q
 from utils import get_employe_from_user
-from workflow import MAP_GROUPE_ETATS_A_FAIRE
-from workflow import dae_groupes, \
-                     grp_administrateurs, \
-                     grp_directeurs_bureau, \
-                     grp_drh, \
+from workflow import MAP_GROUPE_ETATS_A_FAIRE, DOSSIER_ETAT_FINALISE, \
+                        POSTE_ETAT_FINALISE
+from workflow import grp_drh, \
                      grp_drh2, \
                      grp_accior, \
                      grp_abf, \
                      grp_haute_direction, \
-                     grp_service_utilisateurs, \
-                     grp_correspondants_rh
+                     grp_service_utilisateurs
 
 from rh.managers import DossierManager as RHDossierManager
 from rh.managers import PosteManager as RHPosteManager
@@ -57,3 +54,30 @@ class PosteComparaisonManager(RHPosteComparaisonManager, TodoManagerMixin):
 class DossierComparaisonManager(RHDossierComparaisonManager, TodoManagerMixin):
     pass
 
+class DossierFinaliseManager(models.Manager):
+    """
+    Dossiers finalisés, qui ne sont pas encore importés
+    """
+    def get_query_set(self):
+        from dae.models import ImportDossier
+        dae_importees = [str(i.dae_id) for i in ImportDossier.objects.all()]
+        qs = super(DossierFinaliseManager, self).get_query_set().filter(etat=DOSSIER_ETAT_FINALISE,)
+        if len(dae_importees) > 0:
+            qs = qs.extra(where=['id NOT IN (%s)' % ", ".join(dae_importees)])
+        return qs
+
+
+class PosteFinaliseManager(models.Manager):
+    """
+    Dossiers finalisés, qui ne sont pas encore importés
+    """
+    def get_query_set(self):
+        from dae.models import ImportPoste
+        dae_importees = [str(i.dae_id) for i in ImportPoste.objects.all()]
+
+        qs = super(PosteFinaliseManager, self).get_query_set().filter(etat=POSTE_ETAT_FINALISE,)
+        if len(dae_importees) > 0:
+            qs = qs.extra(where=['id NOT IN (%s)' % ", ".join(dae_importees)])
+        return qs
+
+
index 1a065c9..b70e001 100644 (file)
@@ -11,7 +11,7 @@ from workflow import DOSSIER_ETAT_DRH_FINALISATION, DOSSIER_ETAT_REGION_FINALISA
 from auf.django.metadata.models import AUFMetadata
 from managers import *
 from rh.models import HELP_TEXT_DATE
-
+from exporter import DossierCopier, PosteCopier
 
 # Upload de fichiers
 UPLOAD_STORAGE = FileSystemStorage(settings.PRIVE_MEDIA_ROOT)
@@ -54,49 +54,6 @@ class Poste(PosteWorkflow, rh.Poste_):
     # Managers
     objects = PosteManager()
 
-
-    def est_importe(self):
-        """Test si le poste a déjà été importé"""
-        return ImportPoste.objects.filter(dae=self).exists()
-
-    def importer(self):
-        from django.db.models import AutoField
-        if self.est_importe():
-            return ImportPoste.objects.get(dae=self)
-        rh_poste = rh.Poste()
-        # Faire une copie profonde de l'objet.
-        # PosteFinancement, PosteComparaison, Remun modele a ajuster...
-
-        def copy_model(src, dst, exclude=[]):
-            keys = [f.name for f in src._meta.fields if f.name not in ['id', ] + exclude]
-            for k in keys:
-                setattr(dst, k, getattr(src, k))
-            return dst
-        
-        rh_poste = copy_model(self, rh_poste)
-        rh_poste.save()
-        print rh_poste.id
-
-        for o in self.dae_financements.all():
-            rh_financement = rh.PosteFinancement()
-            rh_financement = copy_model(o, rh_financement, exclude=['poste',])
-            rh_financement.poste = rh_poste
-            rh_financement.save()
-
-        for o in self.dae_pieces.all():
-            rh_piece = rh.PostePiece()
-            rh_piece = copy_model(o, rh_piece, exclude=['poste',])
-            rh_piece.poste = rh_poste
-            rh_piece.save()
-
-        for o in self.dae_comparaisons_internes.all():
-            rh_comp = rh.PosteComparaison()
-            rh_comp = copy_model(o, rh_financement, exclude=['poste',])
-            rh_comp.poste = rh_poste
-            rh_comp.save()
-
-        return rh_poste
-
     def _get_key(self):
         """
         Les vues sont montées selon une clef spéciale
@@ -121,6 +78,15 @@ class Poste(PosteWorkflow, rh.Poste_):
             return []
         return self.id_rh.rh_dossiers.all()
 
+    def rh_importation(self):
+        if ImportPoste.objects.filter(dae=self).exists():
+            return ImportPoste.objects.get(dae=self).rh
+        else:
+            return None
+
+    def importer(self, verbosity=0, dry_run=False):
+        copieur = PosteCopier(verbosity=verbosity, dry_run=dry_run)
+        return copieur.copy(self)
 
     def get_employe(self):
         """
@@ -454,14 +420,9 @@ class Dossier(DossierWorkflow, rh.Dossier_):
     def __unicode__(self):
         return u'[%s] %s - %s' % (self.poste.implantation, self.poste.nom, self.employe)
 
-    def est_importe(self):
-        """Test si le dossier a déjà été importé"""
-        return dae.ImportDossier.objects.filter(dae=self).exists()
-
-    def importer(self):
-        if not self.poste.est_importe():
-            raise Exception('Le poste de cette DAE doît être importé')
-        return True
+    def importer(self, verbosity=0, dry_run=False):
+        copieur = DossierCopier(verbosity=verbosity, dry_run=dry_run)
+        return copieur.copy(self)
 
     def get_salaire_anterieur_euros(self):
         if self.devise_anterieur is None:
@@ -612,6 +573,25 @@ class Remuneration(rh.Remuneration_):
 class Contrat(rh.Contrat_):
     pass
 
+
+class DossierFinalise(Dossier):
+
+    objects = DossierFinaliseManager()
+
+    class Meta:
+        proxy = True
+        verbose_name = "Import d'un dossier finalisé"
+        verbose_name_plural = "Import des dossiers finalisés"
+
+class PosteFinalise(Poste):
+
+    objects = PosteFinaliseManager()
+
+    class Meta:
+        proxy = True
+        verbose_name = "Import d'un poste finalisé"
+        verbose_name_plural = "Import des postes finalisés"
+
 # modèle de liaison entre les systèmes
 
 class ImportDossier(models.Model):
index 9434399..96b228a 100644 (file)
@@ -13,6 +13,7 @@ from auf.django.metadata.admin import AUFMetadataAdminMixin, \
                                       AUF_METADATA_READONLY_FIELDS
 from forms import ContratForm, AyantDroitForm, EmployeAdminForm, AjaxSelect, \
 DossierForm
+from dae import models as dae
 from dae.utils import get_employe_from_user
 from change_list import ChangeList
 from groups import grp_correspondants_rh
@@ -314,6 +315,7 @@ class DossierAdmin(DateRangeMixin, AUFMetadataAdminMixin, \
         '_date_fin',
         '_date_modification',
         'user_modification',
+        '_dae',
     )
     list_display_links = ('_nom',)
     list_filter = (
@@ -394,6 +396,22 @@ class DossierAdmin(DateRangeMixin, AUFMetadataAdminMixin, \
     _apercu.allow_tags = True
     _apercu.short_description = u""
 
+    def _dae(self, d):
+        apercu_link = ""
+        if dae.ImportDossier.objects.filter(rh=d).exists():
+            dae_id = dae.ImportDossier.objects.get(rh=d).dae_id
+            apercu_link = u"""<a title="Aperçu du dossier"
+                                 onclick="return showAddAnotherPopup(this);"
+                                 href='%s'>
+                                 <img src="%simg/loupe.png" />
+                                 </a>""" % \
+                    (reverse('embauche_consulter', args=(dae_id,)),
+                     settings.STATIC_URL,
+                     )
+        return apercu_link
+    _dae.allow_tags = True
+    _dae.short_description = u"DAE"
+
     def _date_debut(self, obj):
         return date(obj.date_debut)
 
@@ -696,6 +714,7 @@ class PosteAdmin(DateRangeMixin, AUFMetadataAdminMixin, \
         'date_fin',
         '_date_modification',
         'user_modification',
+        '_dae',
         )
     list_filter = (
         'implantation__region',
@@ -789,6 +808,22 @@ class PosteAdmin(DateRangeMixin, AUFMetadataAdminMixin, \
     _apercu.allow_tags = True
     _apercu.short_description = ''
 
+    def _dae(self, poste):
+        apercu_link = ""
+        if dae.ImportPoste.objects.filter(rh=poste).exists():
+            dae_id = dae.ImportPoste.objects.get(rh=poste).dae_id
+            apercu_link = u"""<a title="Aperçu du dossier"
+                                 onclick="return showAddAnotherPopup(this);"
+                                 href='%s'>
+                                 <img src="%simg/loupe.png" />
+                                 </a>""" % \
+                    (reverse('poste_consulter', args=("dae-%s" % dae_id,)),
+                     settings.STATIC_URL,
+                     )
+        return apercu_link
+    _dae.allow_tags = True
+    _dae.short_description = u"DAE"
+
     def _id(self, obj):
         return "%s" % obj.id
     _id.short_description = '#'
index 7fbf0ac..ee41fc1 100644 (file)
@@ -730,6 +730,9 @@ class DossierComparaison_(models.Model, DevisableMixin):
     class Meta:
         abstract = True
 
+    def __unicode__(self):
+        return "%s (%s)" % (self.poste, self.personne)
+
 
 class DossierComparaison(DossierComparaison_):
     pass