Repository: interlegis/sapl

Issue:
Ter um botão para o usuário realizar copias das matérias da ordem do dia para outra ordem do dia 
<!--- Forneça um resumo geral da _issue_ no título acima -->
Hoje na assembleia que trabalhamos funcionar da seguinte forma, temos uma ordem do dia com 200 matérias por exemplo todas incluídas na sessão ordinária [1], imagine que essa sessão [1] não teve  quorum, logo todas a matérias da sessão [1] vão para sessão ordinária [2].

Dá forma que está hoje, todas as 200 matérias devem ser inseridas novamente na sessão [2], seria ideal haver uma OPÇÃO de copiar matérias da sessão [1] para sessão [2], para o usuária não ter que fazer o mesmo trabalho novamente.





## Comportamento Esperado
<!--- Se você está descrevendo um _bug_, conte-nos o que deveria acontecer. -->
<!--- Se você está sugerindo uma mudança/melhoria, conte-nos como deve funcionar. -->
Ter uma OPÇÃO de poder realizar copias de matérias já inseridas na ordem {1}, para ordem do dia [2]. 
## Comportamento Atual
<!--- Se está descrevendo um bug, conte-nos o que acontece em vez do comportamento esperado. -->
<!--- Se está sugerindo uma mudança/melhoria, explique a diferença com o comportamento atual. -->Hoje quando não há quorum na votação das matérias da sessão [1], elas são inseridas novamente na sessão [2] para ir a plenário para votação etc.

## Possível Solução
<!--- Não é obrigatório, mas sugira uma possível correção/razão para o bug -->
<!--- ou ideias de como implementar a adição/mudança. -->

## Passos para Reproduzir (para bugs)
<!--- Forneça um link para um exemplo, ou um conjunto de passos inequívocos -->
<!--- para reproduzir esse bug. Inclua código para reproduzir, se relevante. -->
1.
2.
3.
4.

## Contexto
<!--- Como esse problema o afeta? O que você está tentando realizar? -->
<!--- Fornecer o contexto nos ajuda a encontrar uma solução que seja mais útil no mundo real -->

## Imagens do Ocorrido
<!--- Representação visual em vídeo ou imagem do ocorrido -->
<!--- Se está descrevendo um bug poste imagens ou vídeos na reprodução do bug citado, caso se aplique -->
![1](https://user-images.githubusercontent.com/2423695/74458551-a630c200-4e68-11ea-9192-a84642fdf89c.png)

## Seu Ambiente
<!--- Inclua detalhes relevantes sobre o ambiente em que você presenciou/experienciou o bug. -->
* Versão usada (_Release_):
* Nome e versão do navegador:
* Nome e versão do Sistema Operacional (desktop ou mobile):
* Link para o seu projeto (Caso de fork deste projeto):



Discussion:
@edwardoliveira essa necessidade é de extrema relevância e válida para v. 3.2. No entanto visualizo mais para Sessão Plenária... Penso na sessão plenária, basicamente ordem do dia (OD) e expediente (EP), redesenvolvida com Vue semelhante a quadro de kanban (semelhanças com trello e/ou project do github) onde as colunas são adicionadas como sendo OD ou EP de sessões quaisquer (ou apenas futuras) para que o usuário movimente matérias por move ou copy... existe muita ideia no sentido de haver melhorias numa reconstrução das sessões plenárias podendo gerar movimentações, tramitações automáticas e/ou panels para registro de tramitação em tempo real, votações secundárias de emendas, requerimentos e afins.

Resolving patch:
diff --git a/sapl/sessao/urls.py b/sapl/sessao/urls.py
--- a/sapl/sessao/urls.py
+++ b/sapl/sessao/urls.py
@@ -34,7 +34,9 @@
                                recuperar_nome_tipo_sessao,
                                ExpedienteLeituraView,
                                OrdemDiaLeituraView,
-                               retirar_leitura)
+                               retirar_leitura,
+                               TransferenciaMateriasExpediente, TransferenciaMateriasOrdemDia,
+                               filtra_materias_copia_sessao_ajax)
 
 from .apps import AppConfig
 
@@ -75,6 +77,9 @@
     url(r'^sessao/sessao-legislativa-legislatura-ajax/',
         sessao_legislativa_legislatura_ajax,
         name='sessao_legislativa_legislatura_ajax_view'),
+    url(r'^sessao/filtra-materias-copia-sessao-ajax/',
+        filtra_materias_copia_sessao_ajax, 
+        name='filtra_materias_copia_sessao_ajax_view'),    
 
     url(r'^sessao/(?P<pk>\d+)/(?P<spk>\d+)/abrir-votacao$',
         abrir_votacao,
@@ -191,5 +196,11 @@
 
     url(r'^sessao/(?P<pk>\d+)/(?P<iso>\d+)/(?P<oid>\d+)/retirar-leitura$',
         retirar_leitura, name='retirar_leitura'),
-    
+
+    url(r'^sessao/(?P<pk>\d+)/transf-mat-exp$',
+        TransferenciaMateriasExpediente.as_view(),
+        name="transf_mat_exp"),
+    url(r'^sessao/(?P<pk>\d+)/transf-mat-ordemdia$',
+        TransferenciaMateriasOrdemDia.as_view(),
+        name="transf_mat_ordemdia"),
 ]
diff --git a/sapl/sessao/views.py b/sapl/sessao/views.py
--- a/sapl/sessao/views.py
+++ b/sapl/sessao/views.py
@@ -529,6 +529,194 @@ def get_presencas_generic(model, sessao, legislatura):
             yield (m.parlamentar, False)
 
 
+# Constantes de identificação de categoria de Matéria Legislativa
+# para Cópia de Matérias entre Sessões
+MATERIAS_EXPEDIENTE = 1
+MATERIAS_ORDEMDIA = 2
+
+
+def filtra_materias_copia_sessao_ajax(request):
+    categoria_materia = int(request.GET['categoria_materia'])
+    sessao_plenaria = request.GET['sessao_plenaria_atual']
+    sessao_plenaria_destino = request.GET['sessao_plenaria_destino']
+
+    if categoria_materia == MATERIAS_EXPEDIENTE:
+        materias_sessao_destino = ExpedienteMateria.objects.filter(
+            sessao_plenaria=sessao_plenaria_destino
+        ).values_list('materia__id')
+
+        lista_materias_disponiveis_copia = ExpedienteMateria.objects.filter(
+            sessao_plenaria=sessao_plenaria
+        ).exclude(materia__id__in=materias_sessao_destino)
+
+    elif categoria_materia == MATERIAS_ORDEMDIA:
+        materias_sessao_destino = OrdemDia.objects.filter(
+            sessao_plenaria=sessao_plenaria_destino
+        ).values_list('materia__id')
+
+        lista_materias_disponiveis_copia = OrdemDia.objects.filter(
+            sessao_plenaria=sessao_plenaria
+        ).exclude(materia__id__in=materias_sessao_destino)
+
+    lista_materias = [
+        {
+            'id': opcao.id,
+            'materia_id': opcao.materia.id,
+            'materia_tipo_sigla': opcao.materia.tipo.sigla,
+            'materia_numero': opcao.materia.numero,
+            'materia_ano': opcao.materia.ano,
+            'materia_tipo_descricao': opcao.materia.tipo.descricao
+        } for opcao in lista_materias_disponiveis_copia
+    ]
+
+    return JsonResponse({ 'materias': lista_materias })
+
+
+class TransferenciaMateriasSessaoAbstract(PermissionRequiredMixin, ListView):
+    logger = logging.getLogger(__name__)
+    template_name = 'sessao/transf_mat_sessao.html'
+
+    def get_context_data(self, **kwargs):
+        context = super(
+            TransferenciaMateriasSessaoAbstract, self
+        ).get_context_data(**kwargs)
+
+        sessao_plenaria_atual = SessaoPlenaria.objects.get(pk=self.kwargs['pk'])
+
+        context['subnav_template_name'] = 'sessao/subnav.yaml'
+        context['root_pk'] = self.kwargs['pk']
+
+        if not sessao_plenaria_atual.finalizada:
+            msg = _('A sessão plenária deve estar finalizada.')
+            messages.add_message(self.request, messages.ERROR, msg)
+            return context
+
+        if self.expediente:
+            context['title'] = '%s <small>(%s)</small>' % (
+                self.title, sessao_plenaria_atual.__str__()
+            )
+
+            context["categoria_materia"] = self.categoria_materia
+            materias_sessao = ExpedienteMateria.objects.filter(
+                sessao_plenaria=sessao_plenaria_atual
+            ).exists()
+
+        elif self.ordem:
+            context["title"] = '%s <small>(%s)</small>' % (
+                self.title, sessao_plenaria_atual.__str__()
+            )
+
+            context["categoria_materia"] = self.categoria_materia
+            materias_sessao = OrdemDia.objects.filter(
+                sessao_plenaria=sessao_plenaria_atual
+            ).exists()
+
+        if materias_sessao:
+            context['sessoes_destino'] = SessaoPlenaria.objects.filter(
+                data_inicio__gte=sessao_plenaria_atual.data_inicio
+            ).exclude(pk=sessao_plenaria_atual.pk).order_by("-data_inicio")
+
+        context['materias_sessao'] = materias_sessao
+        return context
+
+    def post(self, request, *args, **kwargs):
+        marcadas = request.POST.getlist('opcao_id')
+
+        if not marcadas:
+            msg = _('Nenhuma matéria foi selecionada.')
+            messages.add_message(request, messages.ERROR, msg)
+            return self.get(request, self.kwargs)
+
+        sessao_plenaria_destino_id = request.POST['sessao_plenaria']
+        if not sessao_plenaria_destino_id:
+            self.logger.error(
+                "Sessão plenária de destino inexistente."
+            )
+
+            msg = _('Ocorreu um erro inesperado. Tente novamente.')
+            messages.add_message(request, messages.ERROR, msg)
+
+            msg_c = _(
+                'Se o problema persistir, entre em contato com o suporte do ' \
+                'Interlegis.'
+            )
+            messages.add_message(request, messages.WARNING, msg_c)
+
+            return self.get(request, self.kwargs)
+
+        sessao = SessaoPlenaria.objects.get(id=sessao_plenaria_destino_id)
+        if self.expediente:
+            lista_expediente = []
+
+            exp = ExpedienteMateria.objects.filter(sessao_plenaria=sessao)
+            numero_ordem = exp.last().numero_ordem if exp.exists() else 0
+            for num_ordem, expediente in enumerate(
+                ExpedienteMateria.objects.filter(id__in=marcadas),
+                numero_ordem+1
+            ):
+                lista_expediente.append(
+                    ExpedienteMateria(
+                        sessao_plenaria=sessao, materia=expediente.materia,
+                        data_ordem=expediente.data_ordem,
+                        observacao=expediente.observacao,
+                        numero_ordem=num_ordem,
+                        tipo_votacao=expediente.tipo_votacao,
+                        votacao_aberta=False, registro_aberto=False
+                    )
+                )
+            ExpedienteMateria.objects.bulk_create(lista_expediente)
+
+        elif self.ordem:
+            lista_ordemdia = []
+
+            o = OrdemDia.objects.filter(sessao_plenaria=sessao)
+            numero_ordem = o.last().numero_ordem if o.exists() else 0
+            for num_ordem, ordemdia in enumerate(
+                OrdemDia.objects.filter(id__in=marcadas), numero_ordem+1
+            ):
+                lista_ordemdia.append(
+                    OrdemDia(
+                        sessao_plenaria=sessao, materia=ordemdia.materia,
+                        data_ordem=ordemdia.data_ordem,
+                        observacao=ordemdia.observacao,
+                        numero_ordem=num_ordem,
+                        tipo_votacao=ordemdia.tipo_votacao,
+                        votacao_aberta=False, registro_aberto=False
+                    )
+                )
+            OrdemDia.objects.bulk_create(lista_ordemdia)
+
+        msg = _('Matéria(s) copiada(s) com sucesso.')
+        messages.add_message(request, messages.SUCCESS, msg)
+
+        success_url = reverse(
+            self.listagem_url, kwargs={'pk': sessao_plenaria_destino_id}
+        )
+        return HttpResponseRedirect(success_url)
+
+
+class TransferenciaMateriasExpediente(TransferenciaMateriasSessaoAbstract):
+    expediente = True
+    ordem = False
+    title = "Copiar Matérias do Expediente"
+    categoria_materia = MATERIAS_EXPEDIENTE
+    listagem_url = 'sapl.sessao:expedientemateria_list'
+
+    model = ExpedienteMateria
+    permission_required = ('sessao.change_expedientemateria', )
+
+
+class TransferenciaMateriasOrdemDia(TransferenciaMateriasSessaoAbstract):
+    expediente = False
+    ordem = True
+    title = "Copiar Matérias da Ordem do Dia"
+    categoria_materia = MATERIAS_ORDEMDIA
+    listagem_url = 'sapl.sessao:ordemdia_list'
+
+    model = OrdemDia
+    permission_required = ('sessao.change_ordemdia', )
+
+
 class TipoExpedienteCrud(CrudAux):
     model = TipoExpediente
 
@@ -598,6 +786,7 @@ def get_context_data(self, **kwargs):
                 self.paginate_by = None
 
             context = super().get_context_data(**kwargs)
+
             has_permition = self.request.user.has_module_perms(AppConfig.label)
             return customize_link_materia(context, self.kwargs['pk'], has_permition, False)
 


Test patch: