X-Git-Url: http://git.auf.org/?p=auf_rh_dae.git;a=blobdiff_plain;f=project%2Fdae%2Fforms.py;h=373099fa80f012ecc4a8be598e7c45741881f94e;hp=0a3622585d0586f9fcd8e3137aeac2467c557f05;hb=892a50fd99f2506664f1699024d673fd39765c65;hpb=f258e4e73e087ffb7c1a0e48f574723e9ee23db8 diff --git a/project/dae/forms.py b/project/dae/forms.py index 0a36225..373099f 100644 --- a/project/dae/forms.py +++ b/project/dae/forms.py @@ -1,138 +1,621 @@ # -*- encoding: utf-8 -*- -from django.db.models import Q +import datetime +from ordereddict import OrderedDict +from dateutil.relativedelta import relativedelta from django import forms -from django.forms.models import inlineformset_factory -from django.contrib.admin import widgets as admin_widgets +from django.core.urlresolvers import reverse +from django.core.exceptions import MultipleObjectsReturned +from django.forms.models import BaseInlineFormSet +from django.forms.models import ( + inlineformset_factory, + modelformset_factory, + _get_foreign_key, + ) +from django.db.models import Q, Max, Count +from django.shortcuts import redirect +from django.contrib.admin import widgets as admin_widgets + from ajax_select.fields import AutoCompleteSelectField + +from auf.django.references import models as ref from auf.django.workflow.forms import WorkflowFormMixin -from datamaster_modeles import models as ref -from dae import models as dae -from utils import get_employe_from_user, is_user_dans_service -from rh_v1 import models as rh -from workflow import grp_drh +from auf.django.workflow.models import WorkflowCommentaire + +from project import groups +from project.rh import models as rh +from project.dae import models as dae +from .widgets import ReadOnlyChoiceWidget, ReadOnlyWidget +from project.dae.workflow import POSTE_ETATS_BOUTONS, POSTE_ETAT_FINALISE + + +def filtered_archived_fields_form_factory(*fields): + """ + Retourne un model form + """ + class FilterArchivedFields(object): + def __init__(self, *a, **kw): + super(FilterArchivedFields, self).__init__(*a, **kw) + for f in fields: + self.fields[f].queryset = ( + self.fields[f]._queryset.filter(archive=False) + ) + return FilterArchivedFields + + +class BaseInlineFormSetWithInitial(BaseInlineFormSet): + """ + Cette classe permet de fournir l'option initial aux inlineformsets. + Elle devient désuette en django 1.4. + """ + def __init__(self, data=None, files=None, instance=None, + save_as_new=False, prefix=None, queryset=None, **kwargs): + + self.initial_extra = kwargs.pop('initial', None) + + from django.db.models.fields.related import RelatedObject + if instance is None: + self.instance = self.fk.rel.to() + else: + self.instance = instance + self.save_as_new = save_as_new + # is there a better way to get the object descriptor? + self.rel_name = RelatedObject(self.fk.rel.to, self.model, self.fk).get_accessor_name() + if queryset is None: + queryset = self.model._default_manager + qs = queryset.filter(**{self.fk.name: self.instance}) + super(BaseInlineFormSetWithInitial, self).__init__(data, files, prefix=prefix, + queryset=qs, **kwargs) + + def _construct_form(self, i, **kwargs): + if self.is_bound and i < self.initial_form_count(): + # Import goes here instead of module-level because importing + # django.db has side effects. + from django.db import connections + pk_key = "%s-%s" % (self.add_prefix(i), self.model._meta.pk.name) + pk = self.data[pk_key] + pk_field = self.model._meta.pk + pk = pk_field.get_db_prep_lookup('exact', pk, + connection=connections[self.get_queryset().db]) + if isinstance(pk, list): + pk = pk[0] + kwargs['instance'] = self._existing_object(pk) + if i < self.initial_form_count() and not kwargs.get('instance'): + kwargs['instance'] = self.get_queryset()[i] + if i >= self.initial_form_count() and self.initial_extra: + # Set initial values for extra forms + try: + kwargs['initial'] = self.initial_extra[i-self.initial_form_count()] + except IndexError: + pass + + defaults = {'auto_id': self.auto_id, 'prefix': self.add_prefix(i)} + if self.is_bound: + defaults['data'] = self.data + defaults['files'] = self.files + if self.initial: + try: + defaults['initial'] = self.initial[i] + except IndexError: + pass + # Allow extra forms to be empty. + if i >= self.initial_form_count(): + defaults['empty_permitted'] = True + defaults.update(kwargs) + form = self.form(**defaults) + self.add_fields(form, i) + return form + def _implantation_choices(obj, request): # TRAITEMENT NORMAL - employe = get_employe_from_user(request.user) - # SERVICE - if is_user_dans_service(request.user): - q = Q(**{ 'id' : employe.implantation_id }) - # REGION - else: - q = Q(**{ 'region' : employe.implantation.region }) + employe = groups.get_employe_from_user(request.user) + q = Q(**{'zone_administrative__in': groups.get_zones_from_user(request.user)}) # TRAITEMENT DRH - if grp_drh in request.user.groups.all(): + user_groupes = [g.name for g in request.user.groups.all()] + if groups.DRH_NIVEAU_1 in user_groupes or \ + groups.DRH_NIVEAU_2 in user_groupes: q = Q() - return [('', '----------')] + [(i.id, unicode(i), )for i in ref.Implantation.objects.filter(q)] + return [('', '----------')] + \ + [(i.id, unicode(i), )for i in ref.Implantation.objects.filter(q)] -def _employe_choices(obj, request): - q = Q(id_rh__isnull=True) & Q(id_rh__isnull=True) +def _employe_choices(obj, request): # TRAITEMENT NORMAL - employe = get_employe_from_user(request.user) - # SERVICE - if is_user_dans_service(request.user): - q_region_service = Q(implantation1=employe.implantation) | Q(implantation2=employe.implantation) - # REGION - else: - q_region_service = Q(implantation1__region=employe.implantation.region) | Q(implantation2__region=employe.implantation.region) + employe = groups.get_employe_from_user(request.user) + q_dae_region_service = Q( + poste__implantation__zone_administrative__in=( + groups.get_zones_from_user(request.user) + ) + ) + q_rh_region_service = Q( + poste__implantation__zone_administrative__in=( + groups.get_zones_from_user(request.user) + ) + ) # TRAITEMENT DRH - if grp_drh in request.user.groups.all(): - q_region_service = Q() - - # Construction de la liste des employés en puisant dans DAE (pas d'info) et dans rh_v1 - # Pour le filtrage par région/service, on est obligé d'aller regarder le dossier rh_v1 - # car l'information dans le modèle rh_v1.Employe n'existe pas. - dae_ = dae.Employe.objects.filter(id_rh__isnull=True) - copies = dae.Employe.objects.filter(Q(id_rh__isnull=False)) + user_groupes = [g.name for g in request.user.groups.all()] + if groups.DRH_NIVEAU_1 in user_groupes or \ + groups.DRH_NIVEAU_2 in user_groupes: + q_dae_region_service = Q() + q_rh_region_service = Q() + + # On filtre les employes avec les droits régionaux et on s'assure que + # c'est bien le dernier dossier en date pour sortir l'employe. On retient + # un employé qui travaille présentement dans la même région que le user + # connecté. + dossiers_regionaux_ids = [ + d.id for d in dae.Dossier.objects.filter(q_dae_region_service) + ] + employes_ids = [ + d['employe'] + for d in dae.Dossier.objects + .values('employe') + .annotate(dernier_dossier=Max('id')) + if d['dernier_dossier'] in dossiers_regionaux_ids + ] + dae_employe = dae.Employe.objects.filter(id__in=employes_ids) + dae_ = dae_employe.filter(id_rh__isnull=True) + copies = dae_employe.filter(Q(id_rh__isnull=False)) id_copies = [p.id_rh_id for p in copies.all()] - employes_ids = list(set([d.employe_id for d in rh.Dossier.objects.filter(q_region_service)])) - rhv1 = rh.Employe.objects.filter(id__in=employes_ids).exclude(id__in=id_copies) - - def option_label(employe): - return "%s %s" % (employe.nom.upper(), employe.prenom.title()) - return [('', 'Nouvel employé')] + \ - sorted([('dae-%s' % p.id, option_label(p)) for p in dae_ | copies] + - [('rh-%s' % p.id, option_label(p)) for p in rhv1], - key=lambda t: t[1]) + dossiers_regionaux_ids = [ + d.id for d in rh.Dossier.objects.filter(q_rh_region_service) + ] + employes_ids = [ + d['employe'] + for d in rh.Dossier.objects + .values('employe') + .annotate(dernier_dossier=Max('id')) + if d['dernier_dossier'] in dossiers_regionaux_ids + ] + rhv1 = rh.Employe.objects \ + .filter(id__in=employes_ids) \ + .exclude(id__in=id_copies) + + # On ajoute les nouveaux Employés DAE qui ont été crées, mais qui n'ont + # pas de Dossier associés + employes_avec_dae = [d.employe_id for d in dae.Dossier.objects.all()] + employes_orphelins = dae.Employe.objects.exclude(id__in=employes_avec_dae) + + def option_label(employe, extra=""): + if extra: + extra = " [%s]" % extra + return "%s %s %s" % (employe.nom.upper(), employe.prenom.title(), extra) + + lbl_rh = sorted([('rh-%s' % p.id, option_label(p, "existant dans rh")) for p in rhv1], + key=lambda t: t[1]) + lbl_dae = sorted([('dae-%s' % p.id, option_label(p)) for p in dae_ | copies | employes_orphelins], + key=lambda t: t[1]) + return [('', 'Nouvel employé')] + lbl_rh + lbl_dae def label_poste_display(poste): """Formate un visuel pour un poste dans une liste déroulante""" - label = u"%s - %s [%s]" %(poste.type_poste, poste.type_poste.famille_emploi.nom, poste.id) + annee = "" + if poste.date_debut: + annee = poste.date_debut.year + + nom = poste.nom + label = u"%s (%s) %s [%s]" % ( + annee, + poste.implantation.nom_court, + nom, + #poste.type_poste.categorie_emploi.nom, + poste.id, + ) return label -class PostePieceForm(inlineformset_factory(dae.Poste, dae.PostePiece)): - pass -class DossierPieceForm(inlineformset_factory(dae.Dossier, dae.DossierPiece)): - pass +PostePieceFormSet = inlineformset_factory(dae.Poste, dae.PostePiece,) +DossierPieceForm = inlineformset_factory(dae.Dossier, dae.DossierPiece) + +# Ce formset est utilisé dans le cas de la création de poste prépopulé avec les +# données de RH +FinancementFormSetInitial = inlineformset_factory( + dae.Poste, + dae.PosteFinancement, + formset=BaseInlineFormSetWithInitial, + extra=2 +) +FinancementFormSet = inlineformset_factory( + dae.Poste, + dae.PosteFinancement, + extra=2 +) + + +class DossierComparaisonForm( + filtered_archived_fields_form_factory( + 'classement', + ), + forms.ModelForm): + + recherche = AutoCompleteSelectField('dossiers', required=False) + poste = forms.CharField( + max_length=255, widget=forms.TextInput(attrs={'size': '60'}) + ) + cmp_dossier = forms.IntegerField( + widget=forms.widgets.HiddenInput, + required=False + ) -class FinancementForm(inlineformset_factory(dae.Poste, dae.PosteFinancement, extra=1)): - pass + class Meta: + model = dae.DossierComparaison + exclude = ('dossier',) -class JustificationNouvelEmployeForm(inlineformset_factory(dae.Dossier, - dae.JustificationNouvelEmploye, - extra=0, - can_delete=False, - exclude=('question',))): - """ - Formulaire de justification d'un nouvel employé. - Le dossier a besoin d'être enregistré une première fois afin de prépopuler les questions. - """ - def __init__(self, *args, **kwargs): - instance = kwargs['instance'] - if instance.id: - q_ids = [j.question.id for j in instance.justificationnouvelemploye_set.filter(dossier=instance)] - for q in dae.JustificationQuestion.objects.filter(type="N"): - if q.id in q_ids: - continue - j = dae.JustificationNouvelEmploye() - j.dossier = instance - j.question = q - j.save() - super(self.__class__, self).__init__(*args, **kwargs) - -class JustificationAutreEmployeForm(inlineformset_factory(dae.Dossier, - dae.JustificationAutreEmploye, - extra=0, - can_delete=False, - exclude=('question',))): +DossierComparaisonFormSet = modelformset_factory( + dae.DossierComparaison, extra=3, max_num=3, form=DossierComparaisonForm +) + + +class PosteComparaisonForm( + filtered_archived_fields_form_factory('classement'), + forms.ModelForm): + + recherche = AutoCompleteSelectField('dae_postes', required=False) + + cmp_poste = forms.IntegerField( + widget=forms.widgets.HiddenInput, + required=False, + ) + + class Meta: + model = dae.PosteComparaison + exclude = ('poste',) + +# Ce formset est utilisé dans le cas de la création de poste prépopulé avec les +# données de RH +PosteComparaisonFormSetInitial = inlineformset_factory( + dae.Poste, + dae.PosteComparaison, + extra=3, + max_num=3, + form=PosteComparaisonForm, + formset=BaseInlineFormSetWithInitial, +) +PosteComparaisonFormSet = inlineformset_factory( + dae.Poste, + dae.PosteComparaison, + extra=3, + max_num=3, + form=PosteComparaisonForm, +) + + +class FlexibleRemunForm( + filtered_archived_fields_form_factory( + 'type', + ), + forms.ModelForm): + # Utilisé dans templats. + montant_mensuel = forms.DecimalField(required=False) + montant = forms.DecimalField(required=True, label='Montant annuel') + + class Meta: + model = dae.Remuneration + + def __init__(self, *a, **kw): + super(FlexibleRemunForm, self).__init__(*a, **kw) + # self.fields['type'].widget = ReadOnlyChoiceWidget(choices=self.fields['type'].choices) + + def clean_devise(self): + devise = self.cleaned_data['devise'] + if devise.code == 'EUR': + return devise + implantation = ref.Implantation.objects.get( + id=self.data['implantation'] + ) + liste_taux = devise.tauxchange_set.order_by('-annee') + if len(liste_taux) == 0: + raise forms.ValidationError( + u"La devise %s n'a pas de taux pour l'implantation %s" % + (devise, implantation) + ) + else: + return devise + + def has_changed(self): + """ + Modification de has_changed pour qu'il ignore les montant a 0 + et les 'types'. + """ + + changed_data = self.changed_data + + # Type is set in hidden fields, it shouldn't be changed by the + # user; ignore when checking if data has changed. + if 'type' in changed_data: + changed_data.pop(changed_data.index('type')) + + # Montant is set to 0 in javascript, ifnore 'montant' data if + # its value is 0. + + # Generer le key tel qu'identifié dans self.data: + montant_key = '-'.join((self.prefix, 'montant')) + + if ('montant' in changed_data and + self.data.get(montant_key, '0') == '0'): + changed_data.pop(changed_data.index('montant')) + + return bool(changed_data) + + +class ReadOnlyRemunForm(FlexibleRemunForm): + # Utilisé dans templats. + + def __init__(self, *a, **kw): + super (ReadOnlyRemunForm, self).__init__(*a, **kw) + for field in self.fields: + field = self.fields[field] + if not isinstance(field.widget, ( + forms.widgets.HiddenInput, + forms.widgets.Select)): + field.widget = ReadOnlyWidget() + elif isinstance(field.widget, forms.widgets.Select): + field.widget = ReadOnlyChoiceWidget(choices=field.choices) + + +class GroupedInlineFormset(BaseInlineFormSet): + + def set_groups(self, + groups, + group_accessor, + choice_overrides=[]): + + + # Create pre-defined groups. + self.groups = OrderedDict() + for group in groups: + self.groups[group[0]] = { + 'name': group[1], + 'key': group[0], + 'forms': [], + } + + # Assign each form to a group. + ungrouped_forms = [] + for form in self.forms: + if bool(form.initial): + grp = group_accessor(form) + if grp[0] not in self.groups: + self.groups[grp[0]] = { + 'name': grp[1], + 'key': grp[0], + 'forms': [], + } + self.groups[grp[0]]['forms'].append(form) + else: + ungrouped_forms.append(form) + + + # Distribuer les extras de django dans les groupes, et ajouter + # des extras pour les groupes en nécessitant. + f_count = len(self.forms) + for g in self.groups: + for i in xrange(f_count, f_count + self.extra): + if len(ungrouped_forms) == 0: + f_count += 1 + + if len(ungrouped_forms) > 0: + new_form = ungrouped_forms.pop() + else: + new_form = self._construct_form(i) + self.forms.append(new_form) + + self.groups[g]['forms'].append(new_form) + + + # Override form choices with the data provided in + # choice_overrides + for key in choice_overrides: + for form in self.groups.get(key, {'forms': []})['forms']: + for field_key in choice_overrides[key]: + form.fields[field_key].choices = choice_overrides[ + key][field_key] + + + # Create an iterable for easier access in template. + self.group_list = self.groups.values() + + +def remun_formset_factory(parent_model, + model, + form=forms.ModelForm, + formset=GroupedInlineFormset, + fk_name=None, + fields=None, + exclude=None, + can_order=False, + can_delete=True, + read_only=False, + extra=2, + max_num=None, + formfield_callback=None, + groups=None, + choice_overrides=[]): + trs = rh.TypeRemuneration.objects.all() + # extra = max_num = trs.count() + fk = _get_foreign_key(parent_model, model, fk_name=fk_name) + # enforce a max_num=1 when the foreign key to the parent model is unique. + if fk.unique: + max_num = 1 + kwargs = { + 'form': form, + 'formfield_callback': formfield_callback, + 'formset': formset, + 'extra': extra, + 'can_delete': can_delete, + 'can_order': can_order, + 'fields': fields, + 'exclude': exclude, + 'max_num': max_num, + } + FormSet = modelformset_factory(model, **kwargs) + FormSet.fk = fk + FormSet.read_only = read_only + + def grouper(form): + rtype = form.initial['type'] + if not isinstance(rtype, rh.TypeRemuneration): + rtype = rh.TypeRemuneration.objects.get(id=rtype) + return (rtype.nature_remuneration, + rtype.nature_remuneration + ) + + + + # Monkey patch FormSet. + def __init__(inst, *a, **kw): + super(inst.__class__, inst).__init__(*a, **kw) + inst.set_groups(groups, grouper, choice_overrides) + + FormSet.__init__ = __init__ + + return FormSet + + +def remun_formset_factory_factory( + read_only=False, + parent_model=dae.Dossier, + model=dae.Remuneration, + exclude_archived=False): """ - Formulaire de justification d'un nouvel employé. - Le dossier a besoin d'être enregistré une première fois afin de prépopuler les questions. + Don't we love factory factories? """ - def __init__(self, *args, **kwargs): - instance = kwargs['instance'] - if instance.id: - q_ids = [j.question.id for j in instance.justificationautreemploye_set.filter(dossier=instance)] - for q in dae.JustificationQuestion.objects.filter(type="R"): - if q.id in q_ids: - continue - j = dae.JustificationAutreEmploye() - j.dossier = instance - j.question = q - j.save() - super(self.__class__, self).__init__(*args, **kwargs) - -class PosteForm(forms.ModelForm): - """ Formulaire des postes. """ + null_choice = ('', '-' * 10) + extras = 2 if not read_only else 0 + can_delete = False if read_only else True + form_class = ReadOnlyRemunForm if read_only else FlexibleRemunForm + + choice_override_extra_q = {} + + if exclude_archived: + choice_override_extra_q.update({ + 'archive': False + }) + return remun_formset_factory( + parent_model, + model, + form=form_class, + extra=extras, + can_delete=can_delete, + read_only=read_only, + groups = rh.NATURE_REMUNERATION_CHOICES, + choice_overrides = { + u'Traitement': { + 'type': [null_choice] + list( + rh.TypeRemuneration.objects.filter( + nature_remuneration=u'Traitement', + **choice_override_extra_q).values_list( + 'id', 'nom') + ) + }, + u'Indemnité': { + 'type': [null_choice] + list( + rh.TypeRemuneration.objects.filter( + nature_remuneration=u'Indemnité', + **choice_override_extra_q).values_list( + 'id', 'nom') + ) + }, + u'Charges': { + 'type': [null_choice] + list( + rh.TypeRemuneration.objects.filter( + nature_remuneration=u'Charges', + **choice_override_extra_q).values_list( + 'id', 'nom') + ) + }, + u'Accessoire': { + 'type': [null_choice] + list( + rh.TypeRemuneration.objects.filter( + nature_remuneration=u'Accessoire', + **choice_override_extra_q).values_list( + 'id', 'nom') + ) + }, + u'RAS': { + 'type': [null_choice] + list( + rh.TypeRemuneration.objects.filter( + nature_remuneration=u'RAS', + **choice_override_extra_q).values_list( + 'id', 'nom') + ) + }, + }, + ) + + +RemunForm = remun_formset_factory_factory( + read_only=False, + parent_model=dae.Dossier, + model=dae.Remuneration, + exclude_archived=True, +) + +ReadOnlyRemunFormSet = remun_formset_factory_factory( + read_only=True, + parent_model=dae.Dossier, + model=dae.Remuneration, + ) + +PosteCompReadOnlyRemunFormSet = remun_formset_factory_factory( + read_only=True, + parent_model=dae.PosteComparaison, + model=dae.PosteComparaisonRemuneration, + ) + +DossierCompReadOnlyRemunFormSet = remun_formset_factory_factory( + read_only=True, + parent_model=dae.DossierComparaison, + model=dae.DossierComparaisonRemuneration, + ) + + +class PosteForm(filtered_archived_fields_form_factory( + 'classement_min', + 'classement_max',), + forms.ModelForm): + """ Formulaire des postes. """ + + # On ne propose que les services actifs + service = forms.ModelChoiceField( + queryset=rh.Service.objects.all(), required=True + ) + + responsable = AutoCompleteSelectField('responsables', required=True) + #responsable = forms.ModelChoiceField( + # queryset=rh.Poste.objects.select_related(depth=1)) + + # La liste des choix est laissée vide. Voir __init__ pour la raison. + poste = forms.ChoiceField(label="Nouveau poste ou évolution du poste", + choices=(), required=False) + + valeur_point_min = forms.ModelChoiceField( + queryset=rh.ValeurPoint.actuelles.all(), required=False + ) + valeur_point_max = forms.ModelChoiceField( + queryset=rh.ValeurPoint.actuelles.all(), required=False + ) + class Meta: model = dae.Poste - - fields = ('poste', 'implantation', 'type_poste', 'service', 'nom', + fields = ('type_intervention', + 'poste', 'implantation', 'type_poste', 'service', 'nom', 'responsable', 'local', 'expatrie', 'mise_a_disposition', - 'appel', 'date_debut', 'date_fin', 'actif', + 'appel', 'date_debut', 'date_fin', 'regime_travail', 'regime_travail_nb_heure_semaine', 'classement_min', 'classement_max', 'valeur_point_min', 'valeur_point_max', 'devise_min', 'devise_max', - 'salaire_min', 'salaire_max', 'indemn_min', 'indemn_max', + 'salaire_min', 'salaire_max', + 'indemn_expat_min', 'indemn_expat_max', + 'indemn_fct_min', 'indemn_fct_max', + 'charges_patronales_min', 'charges_patronales_max', 'autre_min', 'autre_max', 'devise_comparaison', 'comp_locale_min', 'comp_locale_max', 'comp_universite_min', 'comp_universite_max', @@ -140,9 +623,9 @@ class PosteForm(forms.ModelForm): 'comp_ong_min', 'comp_ong_max', 'comp_autre_min', 'comp_autre_max', 'justification', - 'etat', ) - widgets = dict(appel=forms.RadioSelect(), + widgets = dict(type_intervention=forms.RadioSelect(), + appel=forms.RadioSelect(), nom=forms.TextInput(attrs={'size': 60},), date_debut=admin_widgets.AdminDateWidget(), date_fin=admin_widgets.AdminDateWidget(), @@ -151,18 +634,7 @@ class PosteForm(forms.ModelForm): #devise_max=forms.Select(attrs={'disabled':'disabled'}), ) - responsable=AutoCompleteSelectField('responsables', required=True) - #responsable = forms.ModelChoiceField( - # queryset=rh.Poste.objects.select_related(depth=1)) - - # La liste des choix est laissée vide. Voir __init__ pour la raison. - poste = forms.ChoiceField(label="Nouveau poste ou évolution du poste", - choices=(), required=False) - - valeur_point_min = forms.ModelChoiceField(queryset=rh.ValeurPoint.actuelles.all(), required=False) - valeur_point_max = forms.ModelChoiceField(queryset=rh.ValeurPoint.actuelles.all(), required=False) - - def __init__(self, request, *args, **kwargs): + def __init__(self, *args, **kwargs): """ Mise à jour dynamique du contenu du menu des postes. Si on ne met le menu à jour de cette façon, à chaque instantiation du @@ -174,89 +646,104 @@ class PosteForm(forms.ModelForm): car le "id" de chaque choix est spécial (voir _poste_choices). """ + request = kwargs.pop('request') super(PosteForm, self).__init__(*args, **kwargs) self.fields['poste'].choices = self._poste_choices(request) - self.fields['implantation'].choices = _implantation_choices(self, request) + + self.fields['implantation'].choices = \ + _implantation_choices(self, request) # Quand le dae.Poste n'existe pas, on recherche dans les dossiers rhv1 if self.instance and self.instance.id is None: dossiers = self.instance.get_dossiers() if len(dossiers) > 0: - self.initial['service'] = dossiers[0].service_id - self.initial['nom'] = "%s %s" % (self.initial['nom'], self.instance.get_complement_nom()) - + self.initial['service'] = dossiers[0].poste.service def _poste_choices(self, request): """ Menu déroulant pour les postes. - - Constitué des postes de dae et des postes de rh_v1 qui n'ont pas - d'équivalent dans dae. - + Constitué des postes de RH """ - dae_ = dae.Poste.objects.ma_region_ou_service(request.user).filter(actif=True, id_rh__isnull=True) - copies = dae.Poste.objects.ma_region_ou_service(request.user).exclude(id_rh__isnull=True) - id_copies = [p.id_rh_id for p in copies.all()] - rhv1 = rh.Poste.objects.ma_region_ou_service(request.user).filter(actif=True).exclude(id__in=id_copies) - # Optimisation de la requête - rhv1 = rhv1.select_related(depth=1) + postes_rh = rh.Poste.objects.ma_region_ou_service(request.user).all() + postes_rh = postes_rh.select_related(depth=1) return [('', '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], + sorted([('rh-%s' % p.id, label_poste_display(p)) for p in + postes_rh], key=lambda t: t[1]) def clean(self): """ Validation conditionnelles de certains champs. """ - cleaned_data = self.cleaned_data - - # Gestion de la mise à disposition - mise_a_disposition = cleaned_data.get("mise_a_disposition") - valeur_point_min = cleaned_data.get("valeur_point_min") - valeur_point_max = cleaned_data.get("valeur_point_max") - if mise_a_disposition is False and (valeur_point_min is None or valeur_point_max is None): - msg = u"Ce champ est obligatoire." - self._errors["valeur_point_min"] = self.error_class([msg]) - self._errors["valeur_point_max"] = self.error_class([msg]) - raise forms.ValidationError("Les valeurs de point sont vides") - - return cleaned_data - + cleaned_data = self.cleaned_data + if cleaned_data.get("local") is False \ + and cleaned_data.get("expatrie") is False: + msg = "Le poste doit au moins être ouvert localement " \ + "ou aux expatriés" + self._errors["local"] = self.error_class([msg]) + self._errors["expatrie"] = '' + raise forms.ValidationError(msg) - def save(self, *args, **kwargs): - kwargs2 = kwargs.copy() - kwargs2['commit'] = False - poste = super(PosteForm, self).save(*args, **kwargs2) - # id_rh - if 'commit' not in kwargs or kwargs['commit']: - poste.save() - return poste + return cleaned_data -class ChoosePosteForm(forms.ModelForm): +class ChoosePosteForm(forms.Form): class Meta: - model = dae.Poste fields = ('poste',) # La liste des choix est laissée vide. Voir PosteForm.__init__. - poste = forms.ChoiceField(choices=(), required=False) + postes_dae = forms.ChoiceField(choices=(), required=False) + postes_rh = forms.ChoiceField(choices=(), required=False) def __init__(self, request=None, *args, **kwargs): super(ChoosePosteForm, self).__init__(*args, **kwargs) - self.fields['poste'].choices = self._poste_choices(request) + self.fields['postes_dae'].choices = self._poste_dae_choices(request) + self.fields['postes_rh'].choices = self._poste_rh_choices(request) - def _poste_choices(self, request): - """ Menu déroulant pour les postes. """ - dae_ = dae.Poste.objects.ma_region_ou_service(request.user).filter(id_rh__isnull=True) - copies = dae.Poste.objects.ma_region_ou_service(request.user).exclude(id_rh__isnull=True) - id_copies = [p.id_rh_id for p in copies.all()] + def _poste_dae_choices(self, request): + """ Menu déroulant pour les postes.""" + postes_dae = dae.Poste.objects.ma_region_ou_service(request.user) \ + .exclude(etat__in=(POSTE_ETAT_FINALISE, )) \ + .annotate(num_dae=Count('dae_dossiers')) \ + .filter(num_dae=0) \ + .order_by('implantation', '-date_debut', ) return [('', '----------')] + \ - sorted([('dae-%s' % p.id, unicode(p)) for p in dae_ | copies], - key=lambda t: t[1]) + [('dae-%s' % p.id, label_poste_display(p)) for p in postes_dae] + + def _poste_rh_choices(self, request): + """ Menu déroulant pour les postes.""" + postes_dae = dae.Poste.objects.exclude(etat__in=(POSTE_ETAT_FINALISE, )) + today = datetime.date.today() + id_poste_dae_commences = [p.id_rh_id for p in postes_dae if p.id_rh is not None] + postes_rh = rh.Poste.objects.ma_region_ou_service(request.user) \ + .exclude(id__in=id_poste_dae_commences) \ + .filter(Q(date_debut__lte=today) & + (Q(date_fin__gte=today) | + Q(date_fin__isnull=True)) + ) \ + .order_by('implantation', '-date_debut', ) + return [('', '----------')] + \ + [('rh-%s' % p.id, label_poste_display(p)) for p in postes_rh] + + def clean(self): + cleaned_data = super(ChoosePosteForm, self).clean() + postes_dae = cleaned_data.get("postes_dae") + postes_rh = cleaned_data.get("postes_rh") + if (postes_dae is u"" and postes_rh is u"") or \ + (postes_dae is not u"" and postes_rh is not u""): + raise forms.ValidationError("Choisissez un poste DAE ou un poste RH") + return cleaned_data + + def redirect(self): + poste_dae_key = self.cleaned_data.get("postes_dae") + if poste_dae_key is not u"": + return redirect(reverse('embauche', args=(poste_dae_key,))) + poste_rh_key = self.cleaned_data.get("postes_rh") + if poste_rh_key is not u"": + return redirect("%s?creer_dossier_dae='M'" % reverse('poste', args=(poste_rh_key,))) class EmployeForm(forms.ModelForm): """ Formulaire des employés. """ @@ -267,31 +754,102 @@ class EmployeForm(forms.ModelForm): # La liste des choix est laissée vide. Voir Poste.__init__ pour la raison. employe = forms.ChoiceField(choices=(), required=False) - def __init__(self, request, *args, **kwargs): + def __init__(self, *args, **kwargs): """ Mise à jour dynamique du contenu du menu des employés. """ + request = kwargs.pop('request', None) super(EmployeForm, self).__init__(*args, **kwargs) self.fields['employe'].choices = _employe_choices(self, request) - -class DossierForm(forms.ModelForm): +class DossierForm( + filtered_archived_fields_form_factory( + 'classement', + 'classement_anterieur', + 'classement_titulaire_anterieur', + ), + forms.ModelForm): """ Formulaire des dossiers. """ class Meta: - exclude= ('etat', ) + exclude = ('etat', 'employe', 'poste', 'date_debut',) model = dae.Dossier widgets = dict(statut_residence=forms.RadioSelect(), contrat_date_debut=admin_widgets.AdminDateWidget(), contrat_date_fin=admin_widgets.AdminDateWidget(), ) +WF_HELP_TEXT = "" + + class PosteWorkflowForm(WorkflowFormMixin): - + bouton_libelles = POSTE_ETATS_BOUTONS + class Meta: fields = ('etat', ) model = dae.Poste + def __init__(self, *args, **kwargs): + super(PosteWorkflowForm, self).__init__(*args, **kwargs) + self.fields['etat'].help_text = WF_HELP_TEXT + + class DossierWorkflowForm(WorkflowFormMixin): - + bouton_libelles = POSTE_ETATS_BOUTONS # meme workflow que poste... + class Meta: fields = ('etat', ) model = dae.Dossier + + def __init__(self, *args, **kwargs): + super(DossierWorkflowForm, self).__init__(*args, **kwargs) + self.fields['etat'].help_text = WF_HELP_TEXT + self._etat_initial = self.instance.etat + + def save(self): + super(DossierWorkflowForm, self).save() + poste = self.instance.poste + + if poste.etat == self._etat_initial: + poste.etat = self.instance.etat + poste.save() + + # créer le commentaire automatique pour le poste associé + commentaire = WorkflowCommentaire() + commentaire.content_object = poste + texte = u"Validation automatique à travers le dossier [%s] de %s\n%s" %( + self.instance.id, + self.instance, + self.data.get('commentaire', ''), + ) + commentaire.texte = texte + commentaire.etat_initial = self.instance._etat_courant + commentaire.etat_final = self.instance.etat + commentaire.owner = self.request.user + commentaire.save() + + +class ContratForm(forms.ModelForm): + + class Meta: + fields = ('type_contrat', 'fichier', ) + model = dae.Contrat + + +class DAENumeriseeForm(forms.ModelForm): + + class Meta: + model = dae.Dossier + fields = ('dae_numerisee',) + + +class DAEFinaliseesSearchForm(forms.Form): + q = forms.CharField( + label='Recherche', required=False, + widget=forms.TextInput(attrs={'size': 40}) + ) + importees = forms.ChoiceField( + label='Importation', required=False, choices=( + ('', ''), + ('oui', 'DAE importées seulement'), + ('non', 'DAE non-importées seulement'), + ) + )