Skip to content

Commit

Permalink
Merge pull request #2838 from prefeiturasp/release/9.2.0
Browse files Browse the repository at this point in the history
Release/9.2.0
  • Loading branch information
alcfernandes authored Apr 3, 2024
2 parents 7ca8a39 + a5db914 commit 0d662d5
Show file tree
Hide file tree
Showing 95 changed files with 4,431 additions and 437 deletions.
2 changes: 1 addition & 1 deletion requirements/base.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Django
# ------------------------------------------------------------------------------
django==4.2.10
django==4.2.11
django-admin-interface==0.26.1
django-admin-rangefilter==0.5.4
django-allauth==0.56.1
Expand Down
2 changes: 1 addition & 1 deletion sme_ptrf_apps/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = "9.1.0"
__version__ = "9.2.0"

__version_info__ = tuple(
[
Expand Down
27 changes: 20 additions & 7 deletions sme_ptrf_apps/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
AnalisePrestacaoContaFactory, AnaliseLancamentoPrestacaoContaFactory,
SolicitacaoAcertoLancamentoFactory, ProcessoAssociacaoFactory,
PrestacaoContaReprovadaNaoApresentacaoFactory, DemonstrativoFinanceiroFactory,
ItemResumoPorAcaoFactory, ItemDespesaFactory, ItemCreditoFactory,
ItemResumoPorAcaoFactory, ItemDespesaFactory, ItemCreditoFactory, ArquivoDownloadFactory
)
from sme_ptrf_apps.users.fixtures.factories import (
UsuarioFactory, UnidadeEmSuporteFactory, GrupoAcessoFactory, VisaoFactory,
Expand Down Expand Up @@ -63,8 +63,8 @@
TipoTransacaoFactory, ProcessoAssociacaoFactory, OcupanteCargoFactory,
CargoComposicaoFactory, DemonstrativoFinanceiroFactory, ItemResumoPorAcaoFactory,
ItemDespesaFactory, ItemCreditoFactory, TipoReceitaFactory, RelacaoBensFactory,
RelatorioRelacaoBensFactory, ItemRelatorioRelacaoDeBensFactory,
SolicitacaoEncerramentoContaAssociacaoFactory
RelatorioRelacaoBensFactory, ItemRelatorioRelacaoDeBensFactory,
SolicitacaoEncerramentoContaAssociacaoFactory, ArquivoDownloadFactory
]

for factory in factories_to_register:
Expand Down Expand Up @@ -834,8 +834,20 @@ def periodo_2021_2(periodo_2021_1):
)



@pytest.fixture
def periodo_2019_2(periodo):
def periodo_2019_1():
return baker.make(
'Periodo',
referencia='2019.1',
data_inicio_realizacao_despesas=date(2019, 1, 1),
data_fim_realizacao_despesas=date(2019, 5, 31),
periodo_anterior=None,
)


@pytest.fixture
def periodo_2019_2(periodo_2019_1):
return baker.make(
'Periodo',
referencia='2019.2',
Expand All @@ -844,7 +856,7 @@ def periodo_2019_2(periodo):
data_prevista_repasse=date(2019, 6, 1),
data_inicio_prestacao_contas=date(2020, 1, 1),
data_fim_prestacao_contas=date(2020, 1, 10),
periodo_anterior=periodo
periodo_anterior=periodo_2019_1
)


Expand Down Expand Up @@ -2182,12 +2194,13 @@ def tag_ativa():


@pytest.fixture
def processo_associacao_123456_2019(associacao):
def processo_associacao_123456_2019(associacao, periodo_2019_1, periodo_2019_2):
return baker.make(
'ProcessoAssociacao',
associacao=associacao,
numero_processo='123456',
ano='2019'
ano='2019',
periodos=[periodo_2019_1, periodo_2019_2],
)


Expand Down
13 changes: 11 additions & 2 deletions sme_ptrf_apps/core/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -510,11 +510,20 @@ class TagAdmin(admin.ModelAdmin):

@admin.register(ProcessoAssociacao)
class ProcessoAssociacaoAdmin(admin.ModelAdmin):
list_display = ('associacao', 'numero_processo', 'ano')
list_display = ('associacao', 'numero_processo', 'ano', 'periodos_str')
search_fields = ('uuid', 'numero_processo', 'associacao__nome')
list_filter = ('ano', 'associacao', 'associacao__unidade__tipo_unidade', 'associacao__unidade__dre')
readonly_fields = ('uuid', 'id')
filter_horizontal = ('periodos',)
raw_id_fields = ('associacao',)

def periodos_str(self, obj):
"""
Retorna uma string com as referências dos períodos associados ao ProcessoAssociacao.
"""
return ", ".join([periodo.referencia for periodo in obj.periodos.all()])

periodos_str.short_description = "Períodos"

@admin.register(ObservacaoConciliacao)
class ObservacaoConciliacaoAdmin(admin.ModelAdmin):
Expand Down Expand Up @@ -1050,7 +1059,7 @@ class AmbienteAdmin(admin.ModelAdmin):

@admin.register(ArquivoDownload)
class ArquivoDownloadAdmin(admin.ModelAdmin):
list_display = ('identificador', 'status', 'alterado_em', 'lido')
list_display = ('identificador', 'status', 'alterado_em', 'lido', 'informacoes')
readonly_fields = ('uuid', 'id',)
list_display_links = ('identificador',)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from waffle import flag_is_active

from rest_framework import serializers

from sme_ptrf_apps.core.models import PrestacaoConta, ObservacaoConciliacao
Expand Down Expand Up @@ -41,7 +43,8 @@ def get_unidade_nome(self, obj):
return obj.associacao.unidade.nome if obj.associacao and obj.associacao.unidade else ''

def get_processo_sei(self, obj):
return get_processo_sei_da_prestacao(prestacao_contas=obj)
request = self.context.get('request', None)
return get_processo_sei_da_prestacao(prestacao_contas=obj, periodos_processo_sei=flag_is_active(request, 'periodos-processo-sei'))

def get_periodo_uuid(self, obj):
return obj.periodo.uuid if obj.periodo else ''
Expand Down Expand Up @@ -119,7 +122,8 @@ def get_periodo_referencia(self, obj):
return obj.periodo.referencia

def get_processo_sei(self, obj):
return get_processo_sei_da_prestacao(prestacao_contas=obj)
request = self.context.get('request', None)
return get_processo_sei_da_prestacao(prestacao_contas=obj, periodos_processo_sei=flag_is_active(request, 'periodos-processo-sei'))

def get_devolucao_ao_tesouro(self, obj):
return obj.total_devolucao_ao_tesouro_str
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from waffle import flag_is_active

from rest_framework import serializers

from sme_ptrf_apps.core.api.serializers import AssociacaoLookupSerializer
from sme_ptrf_apps.core.models import ProcessoAssociacao, Associacao
from sme_ptrf_apps.core.api.serializers import AssociacaoLookupSerializer, PeriodoLookUpSerializer
from sme_ptrf_apps.core.models import ProcessoAssociacao, Associacao, Periodo


class ProcessoAssociacaoCreateSerializer(serializers.ModelSerializer):
Expand All @@ -10,26 +12,105 @@ class ProcessoAssociacaoCreateSerializer(serializers.ModelSerializer):
required=False,
queryset=Associacao.objects.all()
)
periodos = serializers.SlugRelatedField(
many=True,
slug_field='uuid',
queryset=Periodo.objects.all(),
required=False
)

def validate(self, data):
request = self.context.get('request')

flag_ativa = flag_is_active(request, 'periodos-processo-sei')

if not flag_ativa and 'periodos' in data:
raise serializers.ValidationError({
"periodos": "A feature flag 'periodos-processo-sei' está desligada. Não é possível fornecer 'periodos'."
})

if flag_ativa:
periodos = data.get('periodos', [])
if not periodos:
raise serializers.ValidationError({
"periodos": "É necessário informar ao menos um período quando a feature 'periodos-processo-sei' está ativa."
})

ano_processo = data.get('ano')
if periodos and ano_processo:
for periodo in periodos:
ano_periodo = periodo.referencia.split('.')[0]
if ano_periodo != ano_processo:
raise serializers.ValidationError({
"periodos": f"Todos os períodos devem estar no mesmo ano do campo 'ano' ({ano_processo})."
})

associacao = data.get('associacao')
periodos = data.get('periodos', [])

# Verifica se os períodos estão sendo reutilizados na mesma associação
for periodo in periodos:
processos_existentes = ProcessoAssociacao.objects.filter(
associacao=associacao, periodos=periodo
)

# Se estamos atualizando, excluímos o objeto atual da verificação para evitar falso-positivo
if self.instance:
processos_existentes = processos_existentes.exclude(pk=self.instance.pk)

if processos_existentes.exists():
raise serializers.ValidationError({
"periodos": f"O período {periodo.referencia} já está associado a outro ProcessoAssociacao para a associação {associacao}."
})

return data

class Meta:
model = ProcessoAssociacao
fields = ('uuid', 'associacao', 'numero_processo', 'ano',)
fields = ('uuid', 'associacao', 'numero_processo', 'ano', 'periodos')

def create(self, validated_data):
periodos_data = validated_data.pop('periodos', [])
processo_associacao = ProcessoAssociacao.objects.create(**validated_data)
processo_associacao.periodos.set(periodos_data)
return processo_associacao

def update(self, instance, validated_data):
periodos_data = validated_data.pop('periodos', None)
for attr, value in validated_data.items():
setattr(instance, attr, value)
instance.save()
if periodos_data is not None:
instance.periodos.set(periodos_data)
return instance


class ProcessoAssociacaoRetrieveSerializer(serializers.ModelSerializer):

associacao = AssociacaoLookupSerializer()
permite_exclusao = serializers.SerializerMethodField('get_permite_exclusao')
tooltip_exclusao = serializers.SerializerMethodField('get_tooltip_exclusao')
periodos = PeriodoLookUpSerializer(many=True, read_only=True)

class Meta:
model = ProcessoAssociacao
fields = ('uuid', 'associacao', 'numero_processo', 'ano', 'criado_em', 'alterado_em',
'permite_exclusao', 'tooltip_exclusao')
'permite_exclusao', 'tooltip_exclusao', 'periodos',)

def get_tooltip_exclusao(self, obj):
if obj.e_o_ultimo_processo_do_ano_com_pcs_vinculada:
return "Não é possível excluir o número desse processo SEI, pois este já está vinculado a uma prestação de contas. Caso necessário, é possível editá-lo."
request = self.context.get('request', None)

msg = "Não é possível excluir o número desse processo SEI, pois este já está vinculado a uma prestação de contas. Caso necessário, é possível editá-lo."

if flag_is_active(request, 'periodos-processo-sei'):
return msg if obj.prestacoes_vinculadas_aos_periodos.exists() else ""
else:
return ""
return msg if obj.e_o_ultimo_processo_do_ano_com_pcs_vinculada else ""

def get_permite_exclusao(self, obj):
return not obj.e_o_ultimo_processo_do_ano_com_pcs_vinculada
request = self.context.get('request', None)

if flag_is_active(request, 'periodos-processo-sei'):
return not obj.prestacoes_vinculadas_aos_periodos.exists()
else:
return not obj.e_o_ultimo_processo_do_ano_com_pcs_vinculada
53 changes: 53 additions & 0 deletions sme_ptrf_apps/core/api/views/analises_prestacoes_contas_viewset.py
Original file line number Diff line number Diff line change
Expand Up @@ -523,3 +523,56 @@ def regerar_relatorio_apos_acertos(self, request):
)

return Response({'mensagem': 'Arquivo na fila para processamento.'}, status=status.HTTP_200_OK)

@action(detail=False, methods=['get'], url_path='regerar-previa-relatorio-apos-acertos',
permission_classes=[IsAuthenticated & PermissaoAPITodosComLeituraOuGravacao])
def regerar_previa_relatorio_apos_acertos(self, request):
from sme_ptrf_apps.core.tasks import (
gerar_previa_relatorio_apos_acertos_v2_async,
)

# Define a análise da prestação de contas
analise_prestacao_uuid = self.request.query_params.get('analise_prestacao_uuid')

if analise_prestacao_uuid:
try:
analise_prestacao = AnalisePrestacaoConta.objects.get(uuid=analise_prestacao_uuid)
except AnalisePrestacaoConta.DoesNotExist:
erro = {
'erro': 'Objeto não encontrado.',
'mensagem': f"O objeto analise-prestacao-conta para o uuid {analise_prestacao_uuid} não foi encontrado na base."
}
logger.info('Erro: %r', erro)
return Response(erro, status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
erro = {
'erro': 'Ocorreu um erro!',
'mensagem': f"{e}"
}
logger.info('Erro: %r', erro)
return Response(erro, status=status.HTTP_400_BAD_REQUEST)
else:
erro = {
'erro': 'parametros_requeridos',
'mensagem': 'É necessário enviar o uuid da analise.'
}
return Response(erro, status=status.HTTP_400_BAD_REQUEST)

task_celery_geracao_relatorio_apos_acerto = TaskCelery.objects.create(
nome_task="regerar_previa_relatorio_apos_acertos_v2_async",
associacao=analise_prestacao.prestacao_conta.associacao,
periodo=analise_prestacao.prestacao_conta.periodo,
usuario=request.user
)

gerar_previa_relatorio_apos_acertos_v2_async.apply_async(
(
task_celery_geracao_relatorio_apos_acerto.uuid,
analise_prestacao.prestacao_conta.associacao.uuid,
analise_prestacao.prestacao_conta.periodo.uuid,
request.user.username,
analise_prestacao.uuid
), countdown=1
)

return Response({'mensagem': 'Arquivo na fila para processamento.'}, status=status.HTTP_200_OK)
6 changes: 5 additions & 1 deletion sme_ptrf_apps/core/api/views/arquivos_download_viewset.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import mimetypes
from django.http import HttpResponse
from rest_framework.permissions import IsAuthenticated
from django.db.models import Q


class ArquivosDownloadViewSet(mixins.ListModelMixin,
Expand All @@ -35,7 +36,10 @@ def get_queryset(self):

identificador = self.request.query_params.get('identificador')
if identificador is not None:
qs = qs.filter(identificador__unaccent__icontains=identificador)
qs = qs.filter(
Q(identificador__unaccent__icontains=identificador) |
Q(informacoes__unaccent__icontains=identificador)
)

return qs

Expand Down
Loading

0 comments on commit 0d662d5

Please sign in to comment.