Repository: interlegis/sapl

Issue:
Não é possível adicionar/editar uma filiação passada, se a última filiação ainda for válida
Suponha um parlamentar atualmente filiado a um partido (vide figura):

![image](https://cloud.githubusercontent.com/assets/9326037/15029885/faa438fe-1225-11e6-8a8a-46aaa606aa3f.png)

O SAPL não permite adicionar, nem editar, uma filiação anterior (com data de filiação e desfiliação) conforme a tela abaixo.

![image](https://cloud.githubusercontent.com/assets/9326037/15029855/de6fb92e-1225-11e6-99bb-ec2454783708.png)

O sistema corretamente não permite adicionar duas filiações atuais (i.e., sem data de desfiliação), mas deveria ser possível, na minha opinião, adicionar uma filiação retroativa para fins de registro histórico. Esta issue se relaciona com a issue #401 



Discussion:

Resolving patch:
diff --git a/parlamentares/forms.py b/parlamentares/forms.py
--- a/parlamentares/forms.py
+++ b/parlamentares/forms.py
@@ -1,3 +1,5 @@
+from datetime import date
+
 from django import forms
 from django.core.exceptions import ValidationError
 from django.db import transaction
@@ -6,6 +8,7 @@
 from floppyforms.widgets import ClearableFileInput
 
 import sapl
+from sapl.utils import intervalos_tem_intersecao
 
 from .models import Filiacao, Legislatura, Mandato, Parlamentar
 
@@ -50,59 +53,60 @@ def save(self, commit=True):
         return parlamentar
 
 
-def validate(data, data_desfiliacao, parlamentar, filiacao):
-    data_filiacao = data
-    data_desfiliacao = data_desfiliacao
+def validar_datas(data_filiacao, data_desfiliacao, parlamentar, filiacao):
 
-    # Dá erro caso a data de desfiliação seja anterior a de filiação
+    # Verifica se data de desfiliacao é anterior a data de filiacao
     if data_desfiliacao and data_desfiliacao < data_filiacao:
         error_msg = _("A data de desfiliação não pode anterior \
-                      à data de filiação")
+                       à data de filiação")
         return [False, error_msg]
 
-    # Esse bloco garante que não haverá intersecção entre os
-    # períodos de filiação
-    id_filiacao_atual = filiacao.pk
-    todas_filiacoes = parlamentar.filiacao_set.all()
-
-    for filiacoes in todas_filiacoes:
-        if (not filiacoes.data_desfiliacao and
-                filiacoes.id != id_filiacao_atual):
-            error_msg = _("O parlamentar não pode se filiar a algum partido \
-                       sem antes se desfiliar do partido anterior")
-            return [False, error_msg]
-
+    filiacao_atual_id = filiacao.pk
+    # recupera filiacoes em ordem crescente de data
+    todas_filiacoes = parlamentar.filiacao_set.all().order_by('data')
+    filiacoes_id = [parlamentar.pk for parlamentar in todas_filiacoes]
+
+    # Novo registro inserido com filiacoes ja existentes
+    if filiacao_atual_id not in filiacoes_id and len(filiacoes_id) > 0:
+            ultima_filiacao = todas_filiacoes.last()
+            # Se ultima filiacao aberta e insercao posterior a esta filiacao
+            if (not ultima_filiacao.data_desfiliacao and
+                data_filiacao >= ultima_filiacao.data):
+                    error_msg = _("O parlamentar não pode se filiar \
+                                   a novo partido sem antes se \
+                                   desfiliar do partido anterior")
+                    return [False, error_msg]
+
+    # checa intervalos de interseccao
     error_msg = None
     for filiacoes in todas_filiacoes:
-        if filiacoes.id != id_filiacao_atual:
-
-            data_init = filiacoes.data
-            data_fim = filiacoes.data_desfiliacao
-
-            if data_init <= data_filiacao < data_fim:
-
-                error_msg = _("A data de filiação e \
-                        desfiliação não podem estar no intervalo \
-                        de outro período de filiação")
-                break
-
-            if (data_desfiliacao and
-                    data_init < data_desfiliacao < data_fim):
-
-                error_msg = _("A data de filiação e \
-                        desfiliação não podem estar no intervalo \
-                        de outro período de filiação")
-                break
-
-            if (data_desfiliacao and
-                data_filiacao <= data_init and
-                    data_desfiliacao >= data_fim):
-
-                error_msg = _("A data de filiação e \
-                        desfiliação não podem estar no intervalo \
-                        de outro período de filiação")
-                break
-
+        # nao comparar o registro com ele mesmo
+        if filiacoes.id != filiacao_atual_id:
+
+            # Se a atualizacao eh para remover a data de desfiliacao
+            if not data_desfiliacao:
+                # so permite na ultima data (ou a unica)
+                if filiacao_atual_id != filiacoes_id[-1]:
+                    error_msg = _("Data de desfiliação do parlamentar não \
+                                   pode ser ausente, se existirem datas de \
+                                   filiação posteriores")
+                    return [False, error_msg]
+            else:
+                data_inicio = filiacoes.data
+                data_fim = filiacoes.data_desfiliacao
+
+                # Se filiacao ainda em aberto, preenche uma desfiliacao
+                # ficticia para fins de checagem de interseccao
+                if not data_fim:
+                    data_fim = date.today()
+
+                # finalmente verifica intersecao
+                if intervalos_tem_intersecao(data_inicio, data_fim,
+                                             data_filiacao, data_desfiliacao):
+                    error_msg = _("A data de filiação e \
+                                   desfiliação não podem estar no intervalo \
+                                   de outro período de filiação")
+                    break
     if error_msg:
         return [False, error_msg]
 
@@ -122,10 +126,10 @@ def clean(self):
             return self.errors
 
         filiacao = super(FiliacaoForm, self).save(commit=False)
-        validacao = validate(self.cleaned_data['data'],
-                             self.cleaned_data['data_desfiliacao'],
-                             filiacao.parlamentar,
-                             filiacao)
+        validacao = validar_datas(self.cleaned_data['data'],
+                                  self.cleaned_data['data_desfiliacao'],
+                                  filiacao.parlamentar,
+                                  filiacao)
 
         if not validacao[0]:
             raise ValidationError(validacao[1])
diff --git a/protocoloadm/views.py b/protocoloadm/views.py
--- a/protocoloadm/views.py
+++ b/protocoloadm/views.py
@@ -4,7 +4,7 @@
 from braces.views import FormValidMessageMixin
 from django.core.exceptions import ObjectDoesNotExist
 from django.core.urlresolvers import reverse
-from django.db.models import Q, Max
+from django.db.models import Max, Q
 from django.http import HttpResponse, HttpResponseRedirect
 from django.shortcuts import redirect
 from django.utils.translation import ugettext_lazy as _


Test patch: