diff --git a/README.md b/README.md index 9eb5c1cb4..5331d2c6a 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,27 @@ API da aplicação *SIG.Escola* da Secretaria de Educação da cidade de São Pa License: MIT -Versão: 0.6.0 +Versão: 0.7.0 ## Release Notes +### 0.7.0 - 20/08/2020 - Entregas da Sprint 8 +* Exportação de dados da Associação; +* Gestão de valor realizado nas despesas da Associação; +* Notificação de transações não demonstradas a mais de certo tempo; +* Prestação de contas: Permitir selecionar apenas períodos até o próximo período pendente; +* Atualiza carga de usuários para incluir a visão (UE, DRE ou SME); +* Menus sensíveis às visões UE e DRE; +* Permite ao usuário alternar entre visões e unidades (UEs ou DREs); +* Lista de associações da DRE; +* Consulta dados de uma associação da DRE; +* Consulta dados de uma UE da DRE; +* Cadastro de processos SEI de regularidade e prestação de contas de uma Associação; +* Checklists de regularidade de uma associação da DRE; +* Consulta de dados da DRE; +* Cadastro de técnicos da DRE. + ### 0.6.0 - 28/07/2020 - Entregas da Sprint 7 * Carga de valores reprogramados (implantação de saldos); * Novos campos (e-mail e CCM) no cadastro da Associação; diff --git a/config/api_router.py b/config/api_router.py index 1ab4c7004..26aa55ab5 100644 --- a/config/api_router.py +++ b/config/api_router.py @@ -1,5 +1,5 @@ from django.conf import settings -from django.urls import include, path +from django.urls import path from rest_framework.decorators import api_view from rest_framework.response import Response from rest_framework.routers import DefaultRouter, SimpleRouter @@ -13,11 +13,15 @@ PeriodosViewSet, PrestacoesContasViewSet, RelacaoBensViewSet, + ProcessosAssociacaoViewSet, + UnidadesViewSet + ) from sme_ptrf_apps.despesas.api.views.despesas_viewset import DespesasViewSet from sme_ptrf_apps.despesas.api.views.especificacoes_viewset import EspecificacaoMaterialServicoViewSet from sme_ptrf_apps.despesas.api.views.fornecedores_viewset import FornecedoresViewSet from sme_ptrf_apps.despesas.api.views.rateios_despesas_viewset import RateiosDespesasViewSet +from sme_ptrf_apps.dre.api.views import TecnicosDreViewSet from sme_ptrf_apps.receitas.api.views import ReceitaViewSet, RepasseViewSet from sme_ptrf_apps.users.api.views import EsqueciMinhaSenhaViewSet, LoginView, RedefinirSenhaViewSet, UserViewSet @@ -48,6 +52,9 @@ def versao(request): router.register("membros-associacao", MembroAssociacaoViewSet) router.register("esqueci-minha-senha", EsqueciMinhaSenhaViewSet) router.register("redefinir-senha", RedefinirSenhaViewSet) +router.register("processos-associacao", ProcessosAssociacaoViewSet) +router.register("unidades", UnidadesViewSet) +router.register("tecnicos-dre", TecnicosDreViewSet) app_name = "api" urlpatterns = router.urls diff --git a/config/settings/base.py b/config/settings/base.py index ae7052515..0d426a9aa 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -98,6 +98,7 @@ "sme_ptrf_apps.users.apps.UsersConfig", "sme_ptrf_apps.despesas.apps.DespesasConfig", "sme_ptrf_apps.receitas.apps.ReceitasConfig", + "sme_ptrf_apps.dre.apps.DreConfig", # Your stuff: custom apps go here ] # https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps diff --git a/docker-compose.yml b/docker-compose.yml index 8b1c6e3a8..edd26a225 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,8 +7,8 @@ services: env_file: .env volumes: - ../ptrf-postgres-data:/var/lib/postgresql/data - # ports: - # - 5434:5432 + ports: + - 5434:5432 api_ptrf: container_name: api-ptrf diff --git a/sme_ptrf_apps/__init__.py b/sme_ptrf_apps/__init__.py index 3ff4c16dd..add8febda 100644 --- a/sme_ptrf_apps/__init__.py +++ b/sme_ptrf_apps/__init__.py @@ -1,4 +1,4 @@ -__version__ = "0.6.0" +__version__ = "0.7.0" __version_info__ = tuple( [ int(num) if num.isdigit() else num diff --git a/sme_ptrf_apps/conftest.py b/sme_ptrf_apps/conftest.py index 417bfd9a6..98cd47d5a 100644 --- a/sme_ptrf_apps/conftest.py +++ b/sme_ptrf_apps/conftest.py @@ -15,31 +15,37 @@ @pytest.fixture -def fake_user(client, django_user_model, associacao): +def fake_user(client, django_user_model, unidade): password = 'teste' username = 'fake' - user = django_user_model.objects.create_user(username=username, password=password, associacao=associacao) + user = django_user_model.objects.create_user(username=username, password=password) client.login(username=username, password=password) + user.unidades.add(unidade) + user.save() return user @pytest.fixture -def authenticated_client(client, django_user_model, associacao): +def authenticated_client(client, django_user_model, unidade): password = 'teste' username = 'fake' - django_user_model.objects.create_user(username=username, password=password, associacao=associacao) + user = django_user_model.objects.create_user(username=username, password=password) client.login(username=username, password=password) + user.unidades.add(unidade) + user.save() return client @pytest.fixture -def usuario(associacao): +def usuario(unidade): from django.contrib.auth import get_user_model senha = 'Sgp0418' login = '7210418' email = 'sme@amcom.com.br' User = get_user_model() - user = User.objects.create_user(username=login, password=senha, associacao=associacao, email=email) + user = User.objects.create_user(username=login, password=senha, email=email) + user.unidades.add(unidade) + user.save() return user @@ -120,7 +126,29 @@ def dre(): @pytest.fixture def unidade(dre): - return baker.make('Unidade', codigo_eol='123456', dre=dre, tipo_unidade='CEU', nome='Escola Teste') + return baker.make( + 'Unidade', + nome='Escola Teste', + tipo_unidade='CEU', + codigo_eol='123456', + dre=dre, + sigla='ET', + cep='5868120', + tipo_logradouro='Travessa', + logradouro='dos Testes', + bairro='COHAB INSTITUTO ADVENTISTA', + numero='200', + complemento='fundos', + telefone='58212627', + email='emefjopfilho@sme.prefeitura.sp.gov.br', + qtd_alunos=1000, + diretor_nome='Pedro Amaro', + dre_cnpj='63.058.286/0001-86', + dre_diretor_regional_rf='1234567', + dre_diretor_regional_nome='Anthony Edward Stark', + dre_designacao_portaria='Portaria nº 0.000', + dre_designacao_ano='2017', + ) @pytest.fixture @@ -130,13 +158,10 @@ def associacao(unidade, periodo_anterior): nome='Escola Teste', cnpj='52.302.275/0001-83', unidade=unidade, - presidente_associacao_nome='Fulano', - presidente_associacao_rf='1234567', - presidente_conselho_fiscal_nome='Ciclano', - presidente_conselho_fiscal_rf='7654321', periodo_inicial=periodo_anterior, ccm='0.000.00-0', - email="ollyverottoboni@gmail.com" + email="ollyverottoboni@gmail.com", + processo_regularidade='123456' ) @@ -147,10 +172,6 @@ def outra_associacao(unidade, periodo_anterior): nome='Outra', cnpj='52.302.275/0001-99', unidade=unidade, - presidente_associacao_nome='Fulano', - presidente_associacao_rf='1234567', - presidente_conselho_fiscal_nome='Ciclano', - presidente_conselho_fiscal_rf='7654321', periodo_inicial=periodo_anterior, ) @@ -160,12 +181,8 @@ def associacao_sem_periodo_inicial(unidade): return baker.make( 'Associacao', nome='Escola Teste', - cnpj='52.302.275/0001-83', + cnpj='44.219.758/0001-90', unidade=unidade, - presidente_associacao_nome='Fulano', - presidente_associacao_rf='1234567', - presidente_conselho_fiscal_nome='Ciclano', - presidente_conselho_fiscal_rf='7654321', periodo_inicial=None, ) @@ -501,7 +518,8 @@ def fechamento_2020_1(periodo_2020_1, associacao, conta_associacao, acao_associa @pytest.fixture -def fechamento_2020_1_com_livre(periodo_2020_1, associacao, conta_associacao, acao_associacao, prestacao_conta_2020_1_conciliada): +def fechamento_2020_1_com_livre(periodo_2020_1, associacao, conta_associacao, acao_associacao, + prestacao_conta_2020_1_conciliada): return baker.make( 'FechamentoPeriodo', periodo=periodo_2020_1, @@ -1115,7 +1133,8 @@ def rateio_fora_periodo_50_custeio(associacao, despesa_fora_periodo, conta_assoc def parametros(): return baker.make( 'Parametros', - permite_saldo_conta_negativo=True + permite_saldo_conta_negativo=True, + fique_de_olho='', ) @@ -1135,6 +1154,21 @@ def parametros_nao_aceita_saldo_negativo_em_conta(): ) +@pytest.fixture +def parametros_tempo_nao_conferido_10_dias(): + return baker.make( + 'Parametros', + tempo_notificar_nao_demonstrados=10 + ) + +@pytest.fixture +def parametros_tempo_nao_conferido_60_dias(): + return baker.make( + 'Parametros', + tempo_notificar_nao_demonstrados=60 + ) + + @pytest.fixture def ata_2020_1_cheque_aprovada(prestacao_conta_2020_1_conciliada): return baker.make( @@ -1193,6 +1227,34 @@ def membro_associacao(associacao): ) +@pytest.fixture +def membro_associacao_presidente_conselho(associacao): + return baker.make( + 'MembroAssociacao', + nome='Arthur Nobrega', + associacao=associacao, + cargo_associacao=MembroEnum.PRESIDENTE_CONSELHO_FISCAL.value, + cargo_educacao='Coordenador', + representacao=RepresentacaoCargo.SERVIDOR.value, + codigo_identificacao='567432', + email='ollyverottoboni@gmail.com' + ) + + +@pytest.fixture +def membro_associacao_presidente_associacao(associacao): + return baker.make( + 'MembroAssociacao', + nome='Arthur Nobrega', + associacao=associacao, + cargo_associacao=MembroEnum.PRESIDENTE_DIRETORIA_EXECUTIVA.value, + cargo_educacao='Coordenador', + representacao=RepresentacaoCargo.SERVIDOR.value, + codigo_identificacao='567432', + email='ollyverottoboni@gmail.com' + ) + + @pytest.fixture def payload_membro_servidor(associacao): payload = { @@ -1258,3 +1320,14 @@ def tag_ativa(): nome="COVID-19", status=StatusTag.ATIVO.name ) + +@pytest.fixture +def processo_associacao_123456_2019(associacao): + return baker.make( + 'ProcessoAssociacao', + associacao=associacao, + numero_processo='123456', + ano='2019' + ) + + diff --git a/sme_ptrf_apps/core/admin.py b/sme_ptrf_apps/core/admin.py index f40457724..5bc3c81ee 100644 --- a/sme_ptrf_apps/core/admin.py +++ b/sme_ptrf_apps/core/admin.py @@ -19,6 +19,7 @@ TipoConta, Unidade, Tag, + ProcessoAssociacao, ) admin.site.register(TipoConta) @@ -37,15 +38,11 @@ def get_nome_escola(self, obj): get_nome_escola.short_description = 'Escola' - list_display = ('nome', 'cnpj', 'get_nome_escola', 'get_usuarios') + list_display = ('nome', 'cnpj', 'get_nome_escola') search_fields = ('uuid', 'nome', 'cnpj', 'unidade__nome') list_filter = ('unidade__dre', 'periodo_inicial') readonly_fields = ('uuid', 'id') - def get_usuarios(self, obj): - return ','.join([u.name for u in obj.usuarios.all()]) if obj.usuarios else '' - - get_usuarios.short_description = 'Usuários' @admin.register(ContaAssociacao) class ContaAssociacaoAdmin(admin.ModelAdmin): @@ -79,6 +76,41 @@ class UnidadeAdmin(admin.ModelAdmin): search_fields = ('nome', 'codigo_eol', 'sigla') list_filter = ('tipo_unidade', 'dre') list_display_links = ('nome',) + readonly_fields = ('uuid',) + + fieldsets = ( + ('Dados da Unidade', { + 'fields': ( + 'nome', + 'tipo_unidade', + 'codigo_eol', + 'dre', + 'sigla', + 'cep', + 'tipo_logradouro', + 'logradouro', + 'bairro', + 'numero', + 'complemento', + 'telefone', + 'email', + 'qtd_alunos', + 'diretor_nome', + 'uuid' + ) + }), + + ('Dados da Diretoria da Unidade', { + 'fields': ( + 'dre_cnpj', + 'dre_diretor_regional_rf', + 'dre_diretor_regional_nome', + 'dre_designacao_portaria', + 'dre_designacao_ano' + ) + }), + ) + @admin.register(FechamentoPeriodo) @@ -145,9 +177,11 @@ def get_referencia_periodo(self, obj): get_referencia_periodo.short_description = 'Período' list_display = ( - 'get_eol_unidade', 'get_referencia_periodo', 'get_nome_conta', 'data_reuniao', 'tipo_ata', 'tipo_reuniao', 'convocacao', + 'get_eol_unidade', 'get_referencia_periodo', 'get_nome_conta', 'data_reuniao', 'tipo_ata', 'tipo_reuniao', + 'convocacao', 'parecer_conselho') - list_filter = ('parecer_conselho', 'tipo_ata', 'tipo_reuniao', 'convocacao', 'associacao', 'conta_associacao__tipo_conta') + list_filter = ( + 'parecer_conselho', 'tipo_ata', 'tipo_reuniao', 'convocacao', 'associacao', 'conta_associacao__tipo_conta') list_display_links = ('get_eol_unidade',) readonly_fields = ('uuid', id) search_fields = ('associacao__unidade__codigo_eol',) @@ -156,7 +190,7 @@ def get_referencia_periodo(self, obj): @admin.register(Arquivo) class ArquivoAdmin(admin.ModelAdmin): list_display = ['identificador', 'conteudo', 'tipo_carga'] - actions = ['processa_carga',] + actions = ['processa_carga', ] def processa_carga(self, request, queryset): processa_cargas(queryset) @@ -170,3 +204,11 @@ class TagAdmin(admin.ModelAdmin): list_display = ['uuid', 'nome', 'status'] search_fields = ['status'] list_filter = ['nome', 'status'] + + +@admin.register(ProcessoAssociacao) +class ProcessoAssociacaoAdmin(admin.ModelAdmin): + list_display = ('associacao', 'numero_processo', 'ano') + search_fields = ('uuid', 'numero_processo') + list_filter = ('ano', 'associacao',) + readonly_fields = ('uuid', 'id') diff --git a/sme_ptrf_apps/core/api/serializers/__init__.py b/sme_ptrf_apps/core/api/serializers/__init__.py index 6a0d2bcf0..245682881 100644 --- a/sme_ptrf_apps/core/api/serializers/__init__.py +++ b/sme_ptrf_apps/core/api/serializers/__init__.py @@ -13,6 +13,7 @@ ) from .membro_associacao_serializer import MembroAssociacaoCreateSerializer, MembroAssociacaoListSerializer from .prestacao_conta_serializer import PrestacaoContaLookUpSerializer +from .processo_associacao_serializer import ProcessoAssociacaoRetrieveSerializer, ProcessoAssociacaoCreateSerializer +from .tag_serializer import TagLookupSerializer from .tipo_conta_serializer import TipoContaSerializer from .unidade_serializer import UnidadeInfoAtaSerializer, UnidadeLookUpSerializer, UnidadeSerializer -from .tag_serializer import TagLookupSerializer \ No newline at end of file diff --git a/sme_ptrf_apps/core/api/serializers/associacao_serializer.py b/sme_ptrf_apps/core/api/serializers/associacao_serializer.py index c1a083e83..8cca08a78 100644 --- a/sme_ptrf_apps/core/api/serializers/associacao_serializer.py +++ b/sme_ptrf_apps/core/api/serializers/associacao_serializer.py @@ -1,14 +1,26 @@ from rest_framework import serializers -from ...api.serializers.unidade_serializer import UnidadeInfoAtaSerializer, UnidadeLookUpSerializer +from ...api.serializers.unidade_serializer import (UnidadeInfoAtaSerializer, UnidadeLookUpSerializer, + UnidadeListSerializer, UnidadeSerializer) from ...models import Associacao, Unidade class AssociacaoSerializer(serializers.ModelSerializer): unidade = UnidadeLookUpSerializer(many=False) + class Meta: model = Associacao - fields ='__all__' + fields = ( + 'uuid', + 'ccm', + 'cnpj', + 'email', + 'nome', + 'status_regularidade', + 'unidade', + 'id', + 'processo_regularidade', + ) class AssociacaoLookupSerializer(serializers.ModelSerializer): @@ -23,18 +35,52 @@ class AssociacaoCreateSerializer(serializers.ModelSerializer): required=False, queryset=Unidade.objects.all() ) + class Meta: model = Associacao - fields ='__all__' + fields = '__all__' class AssociacaoInfoAtaSerializer(serializers.ModelSerializer): unidade = UnidadeInfoAtaSerializer(many=False) + class Meta: model = Associacao - fields =[ + fields = [ 'uuid', 'nome', 'cnpj', 'unidade', ] + + +class AssociacaoListSerializer(serializers.ModelSerializer): + unidade = UnidadeListSerializer(many=False) + + class Meta: + model = Associacao + fields = [ + 'uuid', + 'nome', + 'unidade', + 'status_regularidade', + ] + + +class AssociacaoCompletoSerializer(serializers.ModelSerializer): + unidade = UnidadeSerializer(many=False) + + class Meta: + model = Associacao + fields = [ + 'uuid', + 'nome', + 'unidade', + 'status_regularidade', + 'cnpj', + 'ccm', + 'email', + 'presidente_associacao', + 'presidente_conselho_fiscal', + 'processo_regularidade', + ] diff --git a/sme_ptrf_apps/core/api/serializers/processo_associacao_serializer.py b/sme_ptrf_apps/core/api/serializers/processo_associacao_serializer.py new file mode 100644 index 000000000..7ad293596 --- /dev/null +++ b/sme_ptrf_apps/core/api/serializers/processo_associacao_serializer.py @@ -0,0 +1,23 @@ +from rest_framework import serializers + +from sme_ptrf_apps.core.api.serializers import AssociacaoLookupSerializer +from sme_ptrf_apps.core.models import ProcessoAssociacao, Associacao + + +class ProcessoAssociacaoCreateSerializer(serializers.ModelSerializer): + associacao = serializers.SlugRelatedField( + slug_field='uuid', + required=False, + queryset=Associacao.objects.all() + ) + + class Meta: + model = ProcessoAssociacao + fields = ('uuid', 'associacao', 'numero_processo', 'ano',) + +class ProcessoAssociacaoRetrieveSerializer(serializers.ModelSerializer): + associacao = AssociacaoLookupSerializer() + + class Meta: + model = ProcessoAssociacao + fields = ('uuid', 'associacao', 'numero_processo', 'ano', 'criado_em', 'alterado_em') diff --git a/sme_ptrf_apps/core/api/serializers/unidade_serializer.py b/sme_ptrf_apps/core/api/serializers/unidade_serializer.py index a7b90a0f0..ca45ca0b6 100644 --- a/sme_ptrf_apps/core/api/serializers/unidade_serializer.py +++ b/sme_ptrf_apps/core/api/serializers/unidade_serializer.py @@ -17,14 +17,42 @@ class Meta: class UnidadeSerializer(serializers.ModelSerializer): - dre = UnidadeLookUpSerializer() + dre = DreSerializer() class Meta: model = Unidade - fields = '__all__' + fields = ( + 'uuid', + 'codigo_eol', + 'tipo_unidade', + 'nome', + 'sigla', + 'dre', + 'email', + 'telefone', + 'tipo_logradouro', + 'logradouro', + 'numero', + 'complemento', + 'bairro', + 'cep', + 'qtd_alunos', + 'diretor_nome', + 'dre_cnpj', + 'dre_diretor_regional_rf', + 'dre_diretor_regional_nome', + 'dre_designacao_portaria', + 'dre_designacao_ano', + ) class UnidadeInfoAtaSerializer(serializers.ModelSerializer): class Meta: model = Unidade fields = ('tipo_unidade', 'nome') + + +class UnidadeListSerializer(serializers.ModelSerializer): + class Meta: + model = Unidade + fields = ('uuid', 'codigo_eol', 'nome_com_tipo',) diff --git a/sme_ptrf_apps/core/api/views/__init__.py b/sme_ptrf_apps/core/api/views/__init__.py index ee8e3b737..646c7577a 100644 --- a/sme_ptrf_apps/core/api/views/__init__.py +++ b/sme_ptrf_apps/core/api/views/__init__.py @@ -4,4 +4,6 @@ from .membro_associacao_viewset import MembroAssociacaoViewSet from .periodos_viewset import PeriodosViewSet from .prestacoes_contas_viewset import PrestacoesContasViewSet +from .processos_associacao_viewset import ProcessosAssociacaoViewSet from .relacao_bens_viewset import RelacaoBensViewSet +from .unidades_viewset import UnidadesViewSet diff --git a/sme_ptrf_apps/core/api/views/associacoes_viewset.py b/sme_ptrf_apps/core/api/views/associacoes_viewset.py index 2ad7f6449..98cc96bcd 100644 --- a/sme_ptrf_apps/core/api/views/associacoes_viewset.py +++ b/sme_ptrf_apps/core/api/views/associacoes_viewset.py @@ -1,46 +1,78 @@ import datetime import logging +from io import BytesIO +from django.db.models import Q +from django.http import HttpResponse +from django_filters import rest_framework as filters +from openpyxl.writer.excel import save_virtual_workbook from rest_framework import mixins, status from rest_framework.decorators import action +from rest_framework.exceptions import ValidationError +from rest_framework.filters import SearchFilter from rest_framework.permissions import AllowAny from rest_framework.response import Response from rest_framework.viewsets import GenericViewSet -from ...models import Associacao, ContaAssociacao, Periodo +from ..serializers.acao_associacao_serializer import AcaoAssociacaoLookUpSerializer +from ..serializers.associacao_serializer import (AssociacaoCreateSerializer, AssociacaoSerializer, + AssociacaoListSerializer, AssociacaoCompletoSerializer) +from ..serializers.conta_associacao_serializer import ( + ContaAssociacaoCreateSerializer, + ContaAssociacaoDadosSerializer, + ContaAssociacaoLookUpSerializer, +) +from ..serializers.periodo_serializer import PeriodoLookUpSerializer +from ..serializers.processo_associacao_serializer import ProcessoAssociacaoRetrieveSerializer +from ...models import Associacao, ContaAssociacao, Periodo, Unidade from ...services import ( implanta_saldos_da_associacao, implantacoes_de_saldo_da_associacao, info_acoes_associacao_no_periodo, status_aceita_alteracoes_em_transacoes, status_periodo_associacao, + gerar_planilha ) -from ..serializers.acao_associacao_serializer import AcaoAssociacaoLookUpSerializer -from ..serializers.associacao_serializer import AssociacaoCreateSerializer, AssociacaoSerializer -from ..serializers.conta_associacao_serializer import ( - ContaAssociacaoCreateSerializer, - ContaAssociacaoDadosSerializer, - ContaAssociacaoLookUpSerializer, +from ....dre.services import ( + verifica_regularidade_associacao, + marca_item_verificacao_associacao, + desmarca_item_verificacao_associacao, + marca_lista_verificacao_associacao, + desmarca_lista_verificacao_associacao ) -from ..serializers.periodo_serializer import PeriodoLookUpSerializer logger = logging.getLogger(__name__) -class AssociacoesViewSet(mixins.RetrieveModelMixin, +class AssociacoesViewSet(mixins.ListModelMixin, + mixins.RetrieveModelMixin, mixins.UpdateModelMixin, - GenericViewSet): + GenericViewSet, ): permission_classes = [AllowAny] lookup_field = 'uuid' queryset = Associacao.objects.all() serializer_class = AssociacaoSerializer + filter_backends = (filters.DjangoFilterBackend, SearchFilter,) + filter_fields = ('unidade__dre__uuid', 'status_regularidade', 'unidade__tipo_unidade') def get_serializer_class(self): - if self.action in ['retrieve', 'list']: - return AssociacaoSerializer + if self.action == 'retrieve': + return AssociacaoCompletoSerializer + elif self.action == 'list': + return AssociacaoListSerializer else: return AssociacaoCreateSerializer + def get_queryset(self): + qs = Associacao.objects.all() + + nome = self.request.query_params.get('nome') + if nome is not None: + qs = qs.filter(Q(nome__unaccent__icontains=nome) | Q( + unidade__nome__unaccent__icontains=nome)) + + return qs + @action(detail=True, url_path='painel-acoes') def painel_acoes(self, request, uuid=None): @@ -57,7 +89,8 @@ def painel_acoes(self, request, uuid=None): ultima_atualizacao = datetime.datetime.now() info_acoes = info_acoes_associacao_no_periodo(associacao_uuid=uuid, periodo=periodo) - info_acoes = [info for info in info_acoes if info['saldo_reprogramado'] or info['receitas_no_periodo'] or info['despesas_no_periodo']] + info_acoes = [info for info in info_acoes if + info['saldo_reprogramado'] or info['receitas_no_periodo'] or info['despesas_no_periodo']] result = { 'associacao': f'{uuid}', @@ -253,3 +286,194 @@ def contas_update(self, request, uuid=None): status_code = status.HTTP_200_OK return Response(resultado, status=status_code) + + @action(detail=False, url_path='tabelas') + def tabelas(self, _): + result = { + 'tipos_unidade': Unidade.tipos_unidade_to_json(), + 'status_regularidade': Associacao.status_regularidade_to_json(), + } + return Response(result) + + @staticmethod + def _gerar_planilha(associacao_uuid): + associacao = Associacao.by_uuid(associacao_uuid) + xlsx = gerar_planilha(associacao) + return xlsx + + @action(detail=True, methods=['get'], url_path='exportar') + def exportar(self, _, uuid=None): + + xlsx = self._gerar_planilha(uuid) + + result = BytesIO(save_virtual_workbook(xlsx)) + + filename = 'associacao.xlsx' + response = HttpResponse( + result, + content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' + ) + response['Content-Disposition'] = 'attachment; filename=%s' % filename + + return response + + @action(detail=True, url_path='periodos-para-prestacao-de-contas', methods=['get']) + def periodos_para_prestacao_de_contas(self, request, uuid=None): + associacao = self.get_object() + periodos = associacao.periodos_para_prestacoes_de_conta() + return Response(PeriodoLookUpSerializer(periodos, many=True).data) + + @action(detail=True, url_path='processos', methods=['get']) + def processos_da_associacao(self, request, uuid=None): + associacao = self.get_object() + processos = associacao.processos.all() + return Response(ProcessoAssociacaoRetrieveSerializer(processos, many=True).data) + + @action(detail=True, url_path='verificacao-regularidade', methods=['get']) + def verificacao_regularidade(self, request, uuid=None): + verificacao = verifica_regularidade_associacao(uuid) + return Response(verificacao) + + @action(detail=True, url_path='marca-item-verificacao', methods=['get']) + def marca_item_verificacao(self, request, uuid=None): + item = request.query_params.get('item') + + if item is None: + erro = { + 'erro': 'parametros_requerido', + 'mensagem': 'É necessário enviar o uuid do item de verificação pelo parâmetro item.' + } + return Response(erro, status=status.HTTP_400_BAD_REQUEST) + + try: + marca_item_verificacao_associacao(associacao_uuid=uuid, item_verificacao_uuid=item) + result = { + 'associacao': f'{uuid}', + 'item_verificacao': f'{item}', + 'mensagem': 'Item de verificação marcado.' + } + status_code = status.HTTP_200_OK + except ValidationError as e: + result = { + 'erro': 'Objeto não encontrado.', + 'mensagem': f'{e}' + } + status_code = status.HTTP_400_BAD_REQUEST + + return Response(result, status=status_code) + + @action(detail=True, url_path='desmarca-item-verificacao', methods=['get']) + def desmarca_item_verificacao(self, request, uuid=None): + item = request.query_params.get('item') + + if item is None: + erro = { + 'erro': 'parametros_requerido', + 'mensagem': 'É necessário enviar o uuid do item de verificação pelo parâmetro item.' + } + return Response(erro, status=status.HTTP_400_BAD_REQUEST) + + try: + desmarca_item_verificacao_associacao(associacao_uuid=uuid, item_verificacao_uuid=item) + result = { + 'associacao': f'{uuid}', + 'item_verificacao': f'{item}', + 'mensagem': 'Item de verificação desmarcado.' + } + status_code = status.HTTP_200_OK + except ValidationError as e: + result = { + 'erro': 'Objeto não encontrado.', + 'mensagem': f'{e}' + } + status_code = status.HTTP_400_BAD_REQUEST + + return Response(result, status=status_code) + + @action(detail=True, url_path='marca-lista-verificacao', methods=['get']) + def marca_lista_verificacao(self, request, uuid=None): + lista = request.query_params.get('lista') + + if lista is None: + erro = { + 'erro': 'parametros_requerido', + 'mensagem': 'É necessário enviar o uuid da lista de verificação pelo parâmetro lista.' + } + return Response(erro, status=status.HTTP_400_BAD_REQUEST) + + try: + marca_lista_verificacao_associacao(associacao_uuid=uuid, lista_verificacao_uuid=lista) + result = { + 'associacao': f'{uuid}', + 'lista_verificacao': f'{lista}', + 'mensagem': 'Itens da lista de verificação marcados.' + } + status_code = status.HTTP_200_OK + except ValidationError as e: + result = { + 'erro': 'Objeto não encontrado.', + 'mensagem': f'{e}' + } + status_code = status.HTTP_400_BAD_REQUEST + + return Response(result, status=status_code) + + @action(detail=True, url_path='desmarca-lista-verificacao', methods=['get']) + def desmarca_lista_verificacao(self, request, uuid=None): + lista = request.query_params.get('lista') + + if lista is None: + erro = { + 'erro': 'parametros_requerido', + 'mensagem': 'É necessário enviar o uuid da lista de verificação pelo parâmetro lista.' + } + return Response(erro, status=status.HTTP_400_BAD_REQUEST) + + try: + desmarca_lista_verificacao_associacao(associacao_uuid=uuid, lista_verificacao_uuid=lista) + result = { + 'associacao': f'{uuid}', + 'lista_verificacao': f'{lista}', + 'mensagem': 'Itens da lista de verificação desmarcados.' + } + status_code = status.HTTP_200_OK + except ValidationError as e: + result = { + 'erro': 'Objeto não encontrado.', + 'mensagem': f'{e}' + } + status_code = status.HTTP_400_BAD_REQUEST + + return Response(result, status=status_code) + + @action(detail=True, url_path='atualiza-itens-verificacao', methods=['post']) + def atualiza_itens_verificacao(self, request, uuid=None): + itens = request.data + + if not itens: + result_error = { + 'erro': 'campo_requerido', + 'mensagem': 'É necessário enviar os itens de verificacao com o seu status.' + } + return Response(result_error, status=status.HTTP_400_BAD_REQUEST) + + for item in itens: + try: + if item['regular']: + marca_item_verificacao_associacao(associacao_uuid=uuid, item_verificacao_uuid=item['uuid']) + else: + desmarca_item_verificacao_associacao(associacao_uuid=uuid, item_verificacao_uuid=item['uuid']) + + except ValidationError as e: + result = { + 'erro': 'Objeto não encontrado.', + 'mensagem': f'{e}' + } + status_code = status.HTTP_400_BAD_REQUEST + + result = { + 'associacao': f'{uuid}', + 'mensagem': 'Itens de verificação atualizados.' + } + status_code = status.HTTP_200_OK + return Response(result, status=status_code) diff --git a/sme_ptrf_apps/core/api/views/demonstrativo_financeiro_viewset.py b/sme_ptrf_apps/core/api/views/demonstrativo_financeiro_viewset.py index c8b12bfda..5039af702 100644 --- a/sme_ptrf_apps/core/api/views/demonstrativo_financeiro_viewset.py +++ b/sme_ptrf_apps/core/api/views/demonstrativo_financeiro_viewset.py @@ -1,3 +1,4 @@ +import logging from io import BytesIO from tempfile import NamedTemporaryFile @@ -15,6 +16,9 @@ from sme_ptrf_apps.core.services.info_por_acao_services import info_acoes_associacao_no_periodo +logger = logging.getLogger(__name__) + + class DemonstrativoFinanceiroViewSet(GenericViewSet): permission_classes = [AllowAny] lookup_field = 'uuid' @@ -22,6 +26,7 @@ class DemonstrativoFinanceiroViewSet(GenericViewSet): @action(detail=False, methods=['get']) def previa(self, request): + logger.info("Previa do demonstrativo financeiro") acao_associacao_uuid = self.request.query_params.get('acao-associacao') conta_associacao_uuid = self.request.query_params.get('conta-associacao') periodo_uuid = self.request.query_params.get('periodo') @@ -42,7 +47,7 @@ def previa(self, request): content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ) response['Content-Disposition'] = 'attachment; filename=%s' % filename - + logger.info("Previa Pronta. Retornando conteúdo para o frontend") return response @action(detail=False, methods=['get'], url_path='documento-final') diff --git a/sme_ptrf_apps/core/api/views/membro_associacao_viewset.py b/sme_ptrf_apps/core/api/views/membro_associacao_viewset.py index 67646577e..c7bca2f61 100644 --- a/sme_ptrf_apps/core/api/views/membro_associacao_viewset.py +++ b/sme_ptrf_apps/core/api/views/membro_associacao_viewset.py @@ -30,6 +30,21 @@ def get_serializer_class(self): else: return MembroAssociacaoCreateSerializer + + def get_queryset(self): + associacao_uuid = self.request.query_params.get('associacao_uuid') + if associacao_uuid is None: + erro = { + 'erro': 'parametros_requerido', + 'mensagem': 'É necessário enviar o uuid da associação como parâmetro..' + } + return Response(erro, status=status.HTTP_400_BAD_REQUEST) + + qs = MembroAssociacao.objects.filter(associacao__uuid=associacao_uuid) + + return qs + + @action(detail=False, methods=['get'], url_path='codigo-identificacao') def consulta_codigo_identificacao(self, request): rf = self.request.query_params.get('rf') diff --git a/sme_ptrf_apps/core/api/views/processos_associacao_viewset.py b/sme_ptrf_apps/core/api/views/processos_associacao_viewset.py new file mode 100644 index 000000000..06bccd3e7 --- /dev/null +++ b/sme_ptrf_apps/core/api/views/processos_associacao_viewset.py @@ -0,0 +1,23 @@ +from rest_framework import mixins +from rest_framework.permissions import AllowAny +from rest_framework.viewsets import GenericViewSet + +from sme_ptrf_apps.core.api.serializers import ProcessoAssociacaoCreateSerializer, ProcessoAssociacaoRetrieveSerializer +from sme_ptrf_apps.core.models import ProcessoAssociacao + + +class ProcessosAssociacaoViewSet(mixins.RetrieveModelMixin, + mixins.CreateModelMixin, + mixins.UpdateModelMixin, + mixins.DestroyModelMixin, + GenericViewSet): + lookup_field = 'uuid' + permission_classes = [AllowAny] + serializer_class = ProcessoAssociacaoRetrieveSerializer + queryset = ProcessoAssociacao.objects.all() + + def get_serializer_class(self): + if self.action == 'retrieve': + return ProcessoAssociacaoRetrieveSerializer + else: + return ProcessoAssociacaoCreateSerializer diff --git a/sme_ptrf_apps/core/api/views/unidades_viewset.py b/sme_ptrf_apps/core/api/views/unidades_viewset.py new file mode 100644 index 000000000..b3dde4701 --- /dev/null +++ b/sme_ptrf_apps/core/api/views/unidades_viewset.py @@ -0,0 +1,11 @@ +from rest_framework import viewsets +from rest_framework.permissions import AllowAny +from ..serializers import UnidadeSerializer +from ...models import Unidade + + +class UnidadesViewSet(viewsets.ModelViewSet): + permission_classes = [AllowAny] + lookup_field = 'uuid' + queryset = Unidade.objects.all() + serializer_class = UnidadeSerializer diff --git a/sme_ptrf_apps/core/migrations/0061_associacao_status_regularidade.py b/sme_ptrf_apps/core/migrations/0061_associacao_status_regularidade.py new file mode 100644 index 000000000..2f2081994 --- /dev/null +++ b/sme_ptrf_apps/core/migrations/0061_associacao_status_regularidade.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.10 on 2020-07-29 14:45 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0060_auto_20200727_0219'), + ] + + operations = [ + migrations.AddField( + model_name='associacao', + name='status_regularidade', + field=models.CharField(choices=[('PENDENTE', 'Pendente'), ('REGULAR', 'Regular')], default='PENDENTE', max_length=15, verbose_name='Status de Regularidade'), + ), + ] diff --git a/sme_ptrf_apps/core/migrations/0062_auto_20200730_1348.py b/sme_ptrf_apps/core/migrations/0062_auto_20200730_1348.py new file mode 100644 index 000000000..1fc3351e3 --- /dev/null +++ b/sme_ptrf_apps/core/migrations/0062_auto_20200730_1348.py @@ -0,0 +1,59 @@ +# Generated by Django 2.2.10 on 2020-07-30 13:48 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0061_associacao_status_regularidade'), + ] + + operations = [ + migrations.AddField( + model_name='unidade', + name='bairro', + field=models.CharField(blank=True, default='', max_length=255, verbose_name='Bairro'), + ), + migrations.AddField( + model_name='unidade', + name='cep', + field=models.CharField(blank=True, default='', max_length=20, validators=[django.core.validators.RegexValidator(message='Digite o CEP no formato XXXXX-XXX. Com 8 digitos', regex='^\\d{5}-\\d{3}$')], verbose_name='CEP'), + ), + migrations.AddField( + model_name='unidade', + name='complemento', + field=models.CharField(blank=True, default='', max_length=255, verbose_name='Complemento'), + ), + migrations.AddField( + model_name='unidade', + name='email', + field=models.EmailField(blank=True, default='', max_length=254, verbose_name='E-mail'), + ), + migrations.AddField( + model_name='unidade', + name='logradouro', + field=models.CharField(blank=True, default='', max_length=255, verbose_name='Logradouro'), + ), + migrations.AddField( + model_name='unidade', + name='numero', + field=models.CharField(blank=True, default='', max_length=255, verbose_name='Numero'), + ), + migrations.AddField( + model_name='unidade', + name='qtd_alunos', + field=models.PositiveSmallIntegerField(default=0, verbose_name='Quantidade de alunos'), + ), + migrations.AddField( + model_name='unidade', + name='telefone', + field=models.CharField(blank=True, default='', max_length=20, validators=[django.core.validators.RegexValidator(message='Digite o telefone no formato (XX) 12345-6789. Entre 8 ou 9 digitos', regex='^\\(\\d{2}\\) [\\d\\-]{9,10}$')], verbose_name='Telefone'), + ), + migrations.AddField( + model_name='unidade', + name='tipo_logradouro', + field=models.CharField(blank=True, default='', max_length=50, verbose_name='Tipo de Logradouro'), + ), + ] diff --git a/sme_ptrf_apps/core/migrations/0063_auto_20200730_1418.py b/sme_ptrf_apps/core/migrations/0063_auto_20200730_1418.py new file mode 100644 index 000000000..ed37f660e --- /dev/null +++ b/sme_ptrf_apps/core/migrations/0063_auto_20200730_1418.py @@ -0,0 +1,23 @@ +# Generated by Django 2.2.10 on 2020-07-30 14:18 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0062_auto_20200730_1348'), + ] + + operations = [ + migrations.AlterField( + model_name='unidade', + name='cep', + field=models.CharField(blank=True, default='', max_length=20, verbose_name='CEP'), + ), + migrations.AlterField( + model_name='unidade', + name='telefone', + field=models.CharField(blank=True, default='', max_length=20, verbose_name='Telefone'), + ), + ] diff --git a/sme_ptrf_apps/core/migrations/0064_auto_20200730_1431.py b/sme_ptrf_apps/core/migrations/0064_auto_20200730_1431.py new file mode 100644 index 000000000..f811ff69f --- /dev/null +++ b/sme_ptrf_apps/core/migrations/0064_auto_20200730_1431.py @@ -0,0 +1,29 @@ +# Generated by Django 2.2.10 on 2020-07-30 14:31 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0063_auto_20200730_1418'), + ] + + operations = [ + migrations.RemoveField( + model_name='associacao', + name='presidente_associacao_nome', + ), + migrations.RemoveField( + model_name='associacao', + name='presidente_associacao_rf', + ), + migrations.RemoveField( + model_name='associacao', + name='presidente_conselho_fiscal_nome', + ), + migrations.RemoveField( + model_name='associacao', + name='presidente_conselho_fiscal_rf', + ), + ] diff --git a/sme_ptrf_apps/core/migrations/0065_unidade_diretor_nome.py b/sme_ptrf_apps/core/migrations/0065_unidade_diretor_nome.py new file mode 100644 index 000000000..afdcf1163 --- /dev/null +++ b/sme_ptrf_apps/core/migrations/0065_unidade_diretor_nome.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.10 on 2020-07-30 17:18 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0064_auto_20200730_1431'), + ] + + operations = [ + migrations.AddField( + model_name='unidade', + name='diretor_nome', + field=models.CharField(blank=True, default='', max_length=160, verbose_name='Nome do diretor da unidade'), + ), + ] diff --git a/sme_ptrf_apps/core/migrations/0066_parametros_tempo_notificar_nao_demonstrados.py b/sme_ptrf_apps/core/migrations/0066_parametros_tempo_notificar_nao_demonstrados.py new file mode 100644 index 000000000..02ccfc661 --- /dev/null +++ b/sme_ptrf_apps/core/migrations/0066_parametros_tempo_notificar_nao_demonstrados.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.10 on 2020-07-31 08:48 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0065_unidade_diretor_nome'), + ] + + operations = [ + migrations.AddField( + model_name='parametros', + name='tempo_notificar_nao_demonstrados', + field=models.PositiveSmallIntegerField(default=0, verbose_name='Tempo para notificação de transações não demonstradas (dias)'), + ), + ] diff --git a/sme_ptrf_apps/core/migrations/0067_associacao_processo_regularidade.py b/sme_ptrf_apps/core/migrations/0067_associacao_processo_regularidade.py new file mode 100644 index 000000000..7570c536e --- /dev/null +++ b/sme_ptrf_apps/core/migrations/0067_associacao_processo_regularidade.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.10 on 2020-08-06 11:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0066_parametros_tempo_notificar_nao_demonstrados'), + ] + + operations = [ + migrations.AddField( + model_name='associacao', + name='processo_regularidade', + field=models.CharField(blank=True, default='', max_length=100, verbose_name='Nº processo regularidade'), + ), + ] diff --git a/sme_ptrf_apps/core/migrations/0067_auto_20200805_1922.py b/sme_ptrf_apps/core/migrations/0067_auto_20200805_1922.py new file mode 100644 index 000000000..74274eebd --- /dev/null +++ b/sme_ptrf_apps/core/migrations/0067_auto_20200805_1922.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.10 on 2020-08-05 19:22 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0066_parametros_tempo_notificar_nao_demonstrados'), + ] + + operations = [ + migrations.AlterField( + model_name='arquivo', + name='tipo_carga', + field=models.CharField(choices=[('REPASSE_REALIZADO', 'Repasses realizados'), ('CARGA_PERIODO_INICIAL', 'Carga período inicial'), ('REPASSE_PREVISTO', 'Repasses previstos'), ('CARGA_ASSOCIACOES', 'Carga de Associações'), ('CARGA_USUARIOS', 'Carga de usuários')], default='REPASSE_REALIZADO', max_length=35, verbose_name='tipo de carga'), + ), + ] diff --git a/sme_ptrf_apps/core/migrations/0068_processoassociacao.py b/sme_ptrf_apps/core/migrations/0068_processoassociacao.py new file mode 100644 index 000000000..7dc70b8db --- /dev/null +++ b/sme_ptrf_apps/core/migrations/0068_processoassociacao.py @@ -0,0 +1,32 @@ +# Generated by Django 2.2.10 on 2020-08-06 11:42 + +import uuid + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0067_associacao_processo_regularidade'), + ] + + operations = [ + migrations.CreateModel( + name='ProcessoAssociacao', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('criado_em', models.DateTimeField(auto_now_add=True, verbose_name='Criado em')), + ('alterado_em', models.DateTimeField(auto_now=True, verbose_name='Alterado em')), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)), + ('numero_processo', models.CharField(blank=True, default='', max_length=100, verbose_name='Nº processo prestação de conta')), + ('ano', models.CharField(blank=True, default='', max_length=4, verbose_name='Ano')), + ('associacao', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='processos', to='core.Associacao')), + ], + options={ + 'verbose_name': 'Processo de prestação de contas', + 'verbose_name_plural': 'Processos de prestação de contas', + }, + ), + ] diff --git a/sme_ptrf_apps/core/migrations/0069_merge_20200807_1141.py b/sme_ptrf_apps/core/migrations/0069_merge_20200807_1141.py new file mode 100644 index 000000000..af5b64441 --- /dev/null +++ b/sme_ptrf_apps/core/migrations/0069_merge_20200807_1141.py @@ -0,0 +1,14 @@ +# Generated by Django 2.2.10 on 2020-08-07 11:41 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0068_processoassociacao'), + ('core', '0067_auto_20200805_1922'), + ] + + operations = [ + ] diff --git a/sme_ptrf_apps/core/migrations/0070_unidade_dre_cnpj.py b/sme_ptrf_apps/core/migrations/0070_unidade_dre_cnpj.py new file mode 100644 index 000000000..30ac506e3 --- /dev/null +++ b/sme_ptrf_apps/core/migrations/0070_unidade_dre_cnpj.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.10 on 2020-08-08 13:02 + +from django.db import migrations, models +import sme_ptrf_apps.core.models.validators + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0069_merge_20200807_1141'), + ] + + operations = [ + migrations.AddField( + model_name='unidade', + name='dre_cnpj', + field=models.CharField(blank=True, default='', max_length=20, null=True, validators=[sme_ptrf_apps.core.models.validators.cnpj_validation], verbose_name='CNPJ da DRE'), + ), + ] diff --git a/sme_ptrf_apps/core/migrations/0071_unidade_dre_diretor_regional_rf.py b/sme_ptrf_apps/core/migrations/0071_unidade_dre_diretor_regional_rf.py new file mode 100644 index 000000000..a0ade0eca --- /dev/null +++ b/sme_ptrf_apps/core/migrations/0071_unidade_dre_diretor_regional_rf.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.10 on 2020-08-08 13:07 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0070_unidade_dre_cnpj'), + ] + + operations = [ + migrations.AddField( + model_name='unidade', + name='dre_diretor_regional_rf', + field=models.CharField(blank=True, default='', max_length=10, null=True, verbose_name='RF do diretor regional '), + ), + ] diff --git a/sme_ptrf_apps/core/migrations/0072_unidade_dre_diretor_regional_nome.py b/sme_ptrf_apps/core/migrations/0072_unidade_dre_diretor_regional_nome.py new file mode 100644 index 000000000..0190cd106 --- /dev/null +++ b/sme_ptrf_apps/core/migrations/0072_unidade_dre_diretor_regional_nome.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.10 on 2020-08-08 13:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0071_unidade_dre_diretor_regional_rf'), + ] + + operations = [ + migrations.AddField( + model_name='unidade', + name='dre_diretor_regional_nome', + field=models.CharField(blank=True, default='', max_length=160, verbose_name='Nome do diretor regional'), + ), + ] diff --git a/sme_ptrf_apps/core/migrations/0073_unidade_dre_designacao_portaria.py b/sme_ptrf_apps/core/migrations/0073_unidade_dre_designacao_portaria.py new file mode 100644 index 000000000..cb3ddd56f --- /dev/null +++ b/sme_ptrf_apps/core/migrations/0073_unidade_dre_designacao_portaria.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.10 on 2020-08-08 13:15 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0072_unidade_dre_diretor_regional_nome'), + ] + + operations = [ + migrations.AddField( + model_name='unidade', + name='dre_designacao_portaria', + field=models.CharField(blank=True, default='', max_length=160, verbose_name='Designação portaria'), + ), + ] diff --git a/sme_ptrf_apps/core/migrations/0074_unidade_dre_designacao_ano.py b/sme_ptrf_apps/core/migrations/0074_unidade_dre_designacao_ano.py new file mode 100644 index 000000000..2aae9b834 --- /dev/null +++ b/sme_ptrf_apps/core/migrations/0074_unidade_dre_designacao_ano.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.10 on 2020-08-08 13:28 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0073_unidade_dre_designacao_portaria'), + ] + + operations = [ + migrations.AddField( + model_name='unidade', + name='dre_designacao_ano', + field=models.CharField(blank=True, default='', max_length=10, verbose_name='Designação ano'), + ), + ] diff --git a/sme_ptrf_apps/core/models/__init__.py b/sme_ptrf_apps/core/models/__init__.py index 91d0cb77d..e839014de 100644 --- a/sme_ptrf_apps/core/models/__init__.py +++ b/sme_ptrf_apps/core/models/__init__.py @@ -6,12 +6,13 @@ from .conta_associacao import ContaAssociacao from .demonstrativo_financeiro import DemonstrativoFinanceiro from .fechamento_periodo import STATUS_ABERTO, STATUS_FECHADO, STATUS_IMPLANTACAO, FechamentoPeriodo -from .membros_associacao import MembroAssociacao +from .membro_associacao import MembroAssociacao from .observacao import Observacao from .parametros import Parametros from .periodo import Periodo from .prestacao_conta import STATUS_ABERTO, STATUS_FECHADO, PrestacaoConta +from .proccessos_associacao import ProcessoAssociacao from .relacao_bens import RelacaoBens +from .tag import Tag from .tipo_conta import TipoConta from .unidade import Unidade -from .tag import Tag diff --git a/sme_ptrf_apps/core/models/acao_associacao.py b/sme_ptrf_apps/core/models/acao_associacao.py index 40d4b7e73..d31765c99 100644 --- a/sme_ptrf_apps/core/models/acao_associacao.py +++ b/sme_ptrf_apps/core/models/acao_associacao.py @@ -34,10 +34,10 @@ def __str__(self): return f"{associacao} - Ação {acao} - {status}" @classmethod - def get_valores(cls, user=None): + def get_valores(cls, user=None, associacao_uuid=None): query = cls.objects.filter(status=cls.STATUS_ATIVA) if user: - query = query.filter(associacao=user.associacao) + query = query.filter(associacao__uuid=associacao_uuid) return query.all() class Meta: diff --git a/sme_ptrf_apps/core/models/arquivo.py b/sme_ptrf_apps/core/models/arquivo.py index 01b7c3784..ffe38c75a 100644 --- a/sme_ptrf_apps/core/models/arquivo.py +++ b/sme_ptrf_apps/core/models/arquivo.py @@ -7,12 +7,14 @@ CARGA_PERIODO_INICIAL = 'CARGA_PERIODO_INICIAL' CARGA_REPASSE_PREVISTO = 'REPASSE_PREVISTO' CARGA_ASSOCIACOES = 'CARGA_ASSOCIACOES' +CARGA_USUARIOS = 'CARGA_USUARIOS' CARGA_NOMES = { CARGA_REPASSE_REALIZADO: 'Repasses realizados', CARGA_PERIODO_INICIAL: 'Carga período inicial', CARGA_REPASSE_PREVISTO: 'Repasses previstos', CARGA_ASSOCIACOES: 'Carga de Associações', + CARGA_USUARIOS: 'Carga de usuários', } CARGA_CHOICES = ( @@ -20,6 +22,7 @@ (CARGA_PERIODO_INICIAL, CARGA_NOMES[CARGA_PERIODO_INICIAL]), (CARGA_REPASSE_PREVISTO, CARGA_NOMES[CARGA_REPASSE_PREVISTO]), (CARGA_ASSOCIACOES, CARGA_NOMES[CARGA_ASSOCIACOES]), + (CARGA_USUARIOS, CARGA_NOMES[CARGA_USUARIOS]), ) diff --git a/sme_ptrf_apps/core/models/associacao.py b/sme_ptrf_apps/core/models/associacao.py index 6049da840..4bbeb4cd8 100644 --- a/sme_ptrf_apps/core/models/associacao.py +++ b/sme_ptrf_apps/core/models/associacao.py @@ -2,12 +2,24 @@ from sme_ptrf_apps.core.models_abstracts import ModeloIdNome from .validators import cnpj_validation +from ..choices import MembroEnum -# from sme_ptrf_apps.users.models import User +class Associacao(ModeloIdNome): + # Status de Regularidade + STATUS_REGULARIDADE_PENDENTE = 'PENDENTE' + STATUS_REGULARIDADE_REGULAR = 'REGULAR' + STATUS_REGULARIDADE_NOMES = { + STATUS_REGULARIDADE_PENDENTE: 'Pendente', + STATUS_REGULARIDADE_REGULAR: 'Regular', + } + + STATUS_REGULARIDADE_CHOICES = ( + (STATUS_REGULARIDADE_PENDENTE, STATUS_REGULARIDADE_NOMES[STATUS_REGULARIDADE_PENDENTE]), + (STATUS_REGULARIDADE_REGULAR, STATUS_REGULARIDADE_NOMES[STATUS_REGULARIDADE_REGULAR]), + ) -class Associacao(ModeloIdNome): unidade = models.ForeignKey('Unidade', on_delete=models.PROTECT, related_name="associacoes", to_field="codigo_eol", null=True) @@ -15,15 +27,6 @@ class Associacao(ModeloIdNome): "CNPJ", max_length=20, validators=[cnpj_validation], blank=True, default="", unique=True ) - presidente_associacao_nome = models.CharField('nome do presidente da associação', max_length=70, blank=True, - default="") - presidente_associacao_rf = models.CharField('RF do presidente associação', max_length=10, blank=True, default="") - - presidente_conselho_fiscal_nome = models.CharField('nome do presidente da associação', max_length=70, blank=True, - default="") - presidente_conselho_fiscal_rf = models.CharField('RF do presidente associação', max_length=10, blank=True, - default="") - periodo_inicial = models.ForeignKey('Periodo', on_delete=models.PROTECT, verbose_name='período inicial', related_name='associacoes_iniciadas_no_periodo', null=True, blank=True) @@ -31,14 +34,87 @@ class Associacao(ModeloIdNome): email = models.EmailField("E-mail", max_length=254, null=True, blank=True, default="") + status_regularidade = models.CharField( + 'Status de Regularidade', + max_length=15, + choices=STATUS_REGULARIDADE_CHOICES, + default=STATUS_REGULARIDADE_PENDENTE, + ) + + processo_regularidade = models.CharField('Nº processo regularidade', max_length=100, default='', blank=True) + def apaga_implantacoes_de_saldo(self): self.fechamentos_associacao.filter(status='IMPLANTACAO').delete() + @property + def presidente_associacao(self): + cargo = self.cargos.filter(cargo_associacao=MembroEnum.PRESIDENTE_DIRETORIA_EXECUTIVA.name).first() + if cargo: + return { + 'nome': cargo.nome, + 'email': cargo.email, + 'cargo_educacao': cargo.cargo_educacao + } + else: + return { + 'nome': '', + 'email': '', + 'cargo_educacao': '' + } + + @property + def presidente_conselho_fiscal(self): + cargo = self.cargos.filter(cargo_associacao=MembroEnum.PRESIDENTE_CONSELHO_FISCAL.name).first() + if cargo: + return { + 'nome': cargo.nome, + 'email': cargo.email, + 'cargo_educacao': cargo.cargo_educacao + } + else: + return { + 'nome': '', + 'email': '', + 'cargo_educacao': '' + } + + def periodos_com_prestacao_de_contas(self): + periodos = set() + for prestacao in self.prestacoes_de_conta_da_associacao.all(): + periodos.add(prestacao.periodo) + return periodos + + def proximo_periodo_de_prestacao_de_contas(self): + ultima_prestacao_feita = self.prestacoes_de_conta_da_associacao.last() + ultimo_periodo_com_prestacao = ultima_prestacao_feita.periodo if ultima_prestacao_feita else None + if ultimo_periodo_com_prestacao: + return ultimo_periodo_com_prestacao.periodo_seguinte.first() + else: + return self.periodo_inicial.periodo_seguinte.first() if self.periodo_inicial else None + + def periodos_para_prestacoes_de_conta(self): + periodos = list(self.periodos_com_prestacao_de_contas()) + proximo_periodo = self.proximo_periodo_de_prestacao_de_contas() + if proximo_periodo: + periodos.append(proximo_periodo) + return periodos + @classmethod def acoes_da_associacao(cls, associacao_uuid): associacao = cls.objects.filter(uuid=associacao_uuid).first() return associacao.acoes.all().order_by('acao__posicao_nas_pesquisas') if associacao else [] + @classmethod + def status_regularidade_to_json(cls): + result = [] + for choice in cls.STATUS_REGULARIDADE_CHOICES: + status = { + 'id': choice[0], + 'nome': choice[1] + } + result.append(status) + return result + class Meta: verbose_name = "Associação" verbose_name_plural = "Associações" diff --git a/sme_ptrf_apps/core/models/conta_associacao.py b/sme_ptrf_apps/core/models/conta_associacao.py index 25828718d..297ba5909 100644 --- a/sme_ptrf_apps/core/models/conta_associacao.py +++ b/sme_ptrf_apps/core/models/conta_associacao.py @@ -39,10 +39,10 @@ def __str__(self): return f"{associacao} - Conta {tipo_conta} - {status}" @classmethod - def get_valores(cls, user=None): + def get_valores(cls, user=None, associacao_uuid=None): query = cls.objects.filter(status=cls.STATUS_ATIVA) if user: - query = query.filter(associacao=user.associacao) + query = query.filter(associacao__uuid=associacao_uuid) return query.all() class Meta: diff --git a/sme_ptrf_apps/core/models/membros_associacao.py b/sme_ptrf_apps/core/models/membro_associacao.py similarity index 100% rename from sme_ptrf_apps/core/models/membros_associacao.py rename to sme_ptrf_apps/core/models/membro_associacao.py diff --git a/sme_ptrf_apps/core/models/parametros.py b/sme_ptrf_apps/core/models/parametros.py index 839aad855..40b65c3a5 100644 --- a/sme_ptrf_apps/core/models/parametros.py +++ b/sme_ptrf_apps/core/models/parametros.py @@ -1,11 +1,14 @@ +from ckeditor.fields import RichTextField from django.db import models from sme_ptrf_apps.core.models_abstracts import ModeloBase, SingletonModel -from ckeditor.fields import RichTextField + class Parametros(SingletonModel, ModeloBase): permite_saldo_conta_negativo = models.BooleanField('Permite saldo negativo em contas?', default=True) fique_de_olho = RichTextField(null=True) + tempo_notificar_nao_demonstrados = models.PositiveSmallIntegerField( + 'Tempo para notificação de transações não demonstradas (dias)', default=0) def __str__(self): return 'Parâmetros do PTRF' diff --git a/sme_ptrf_apps/core/models/proccessos_associacao.py b/sme_ptrf_apps/core/models/proccessos_associacao.py new file mode 100644 index 000000000..2a8e92673 --- /dev/null +++ b/sme_ptrf_apps/core/models/proccessos_associacao.py @@ -0,0 +1,20 @@ +from django.db import models + +from sme_ptrf_apps.core.models_abstracts import ModeloBase + + +class ProcessoAssociacao(ModeloBase): + associacao = models.ForeignKey('Associacao', on_delete=models.PROTECT, + related_name='processos', + blank=True, null=True) + + numero_processo = models.CharField('Nº processo prestação de conta', max_length=100, default='', blank=True) + + ano = models.CharField('Ano', max_length=4, blank=True, default="") + + class Meta: + verbose_name = "Processo de prestação de contas" + verbose_name_plural = "Processos de prestação de contas" + + def __str__(self): + return f"" diff --git a/sme_ptrf_apps/core/models/tag.py b/sme_ptrf_apps/core/models/tag.py index c23a8789a..4109ad327 100644 --- a/sme_ptrf_apps/core/models/tag.py +++ b/sme_ptrf_apps/core/models/tag.py @@ -18,7 +18,7 @@ def __str__(self): return f"<{self.nome}, {self.status}>" @classmethod - def get_valores(cls, user=None): + def get_valores(cls, user=None, associacao_uuid=None): query = cls.objects.filter(status=StatusTag.ATIVO.name) return query.all() diff --git a/sme_ptrf_apps/core/models/unidade.py b/sme_ptrf_apps/core/models/unidade.py index c1a69afd8..2729906c4 100644 --- a/sme_ptrf_apps/core/models/unidade.py +++ b/sme_ptrf_apps/core/models/unidade.py @@ -2,6 +2,7 @@ from django.db import models from ..models_abstracts import TemNome, ModeloBase +from .validators import cnpj_validation class Unidade(ModeloBase, TemNome): @@ -28,9 +29,52 @@ class Unidade(ModeloBase, TemNome): blank=True, null=True, limit_choices_to={'tipo_unidade': 'DRE'}) sigla = models.CharField(max_length=4, blank=True, default='') + cep = models.CharField('CEP', max_length=20, blank=True, default='') + tipo_logradouro = models.CharField('Tipo de Logradouro', max_length=50, blank=True, default='') + logradouro = models.CharField('Logradouro', max_length=255, blank=True, default='') + bairro = models.CharField('Bairro', max_length=255, blank=True, default='') + numero = models.CharField('Numero', max_length=255, blank=True, default='') + complemento = models.CharField('Complemento', max_length=255, blank=True, default='') + + telefone = models.CharField('Telefone', max_length=20, blank=True, default='') + + email = models.EmailField("E-mail", max_length=254, blank=True, default='') + + qtd_alunos = models.PositiveSmallIntegerField('Quantidade de alunos', default=0) + + diretor_nome = models.CharField('Nome do diretor da unidade', max_length=160, blank=True, default='') + + dre_cnpj = models.CharField( + "CNPJ da DRE", max_length=20, validators=[cnpj_validation] + , blank=True, null=True, default="" + ) + + dre_diretor_regional_rf = models.CharField('RF do diretor regional ', max_length=10, blank=True, null=True, default="") + + dre_diretor_regional_nome = models.CharField('Nome do diretor regional', max_length=160, blank=True, default='') + + dre_designacao_portaria = models.CharField('Designação portaria', max_length=160, blank=True, default='') + + dre_designacao_ano = models.CharField('Designação ano', max_length=10, blank=True, default='') + def __str__(self): return self.nome + @property + def nome_com_tipo(self): + return f'{self.tipo_unidade} {self.nome}' + + @classmethod + def tipos_unidade_to_json(cls): + result = [] + for choice in cls.TIPOS_CHOICE: + tipo_unidade = { + 'id': choice[0], + 'nome': choice[1] + } + result.append(tipo_unidade) + return result + class Meta: verbose_name = 'Unidade' verbose_name_plural = 'Unidades' diff --git a/sme_ptrf_apps/core/models/validators.py b/sme_ptrf_apps/core/models/validators.py index 1c3969d3c..b4426cdb4 100644 --- a/sme_ptrf_apps/core/models/validators.py +++ b/sme_ptrf_apps/core/models/validators.py @@ -1,9 +1,10 @@ import re +from brazilnum.cnpj import validate_cnpj +from django.core import validators from django.core.validators import EMPTY_VALUES from django.forms import ValidationError -from brazilnum.cnpj import validate_cnpj def cnpj_validation(value): """ @@ -22,3 +23,13 @@ def cnpj_validation(value): raise ValidationError("Número de CNPJ inválido.") return value + + +cep_validation = validators.RegexValidator( + regex=r"^\d{5}-\d{3}$", message="Digite o CEP no formato XXXXX-XXX. Com 8 digitos" +) + +phone_validation = validators.RegexValidator( + regex=r"^\(\d{2}\) [\d\-]{9,10}$", + message="Digite o telefone no formato (XX) 12345-6789. Entre 8 ou 9 digitos", +) diff --git a/sme_ptrf_apps/core/models_abstracts.py b/sme_ptrf_apps/core/models_abstracts.py index 6b93ef5ea..d2be21eb8 100644 --- a/sme_ptrf_apps/core/models_abstracts.py +++ b/sme_ptrf_apps/core/models_abstracts.py @@ -54,7 +54,7 @@ class ModeloBase(TemChaveExterna, TemCriadoEm, TemAlteradoEm): objects = models.Manager() @classmethod - def get_valores(cls, user=None): + def get_valores(cls, user=None, associacao_uuid=None): return cls.objects.all() @classmethod @@ -86,7 +86,7 @@ def get(cls): class ModeloIdNome(ModeloBase, TemNome): @classmethod - def get_valores(cls, user=None): + def get_valores(cls, user=None, associacao_uuid=None): return cls.objects.all().order_by('nome') def __str__(self): diff --git a/sme_ptrf_apps/core/services/__init__.py b/sme_ptrf_apps/core/services/__init__.py index 11d908f56..c8bb97904 100644 --- a/sme_ptrf_apps/core/services/__init__.py +++ b/sme_ptrf_apps/core/services/__init__.py @@ -1,3 +1,4 @@ +from .exporta_associacao import gerar_planilha from .implantacao_saldos_services import implanta_saldos_da_associacao, implantacoes_de_saldo_da_associacao from .info_por_acao_services import ( info_acao_associacao_no_periodo, diff --git a/sme_ptrf_apps/core/services/carga_associacoes.py b/sme_ptrf_apps/core/services/carga_associacoes.py index 2f259906c..1677f8993 100644 --- a/sme_ptrf_apps/core/services/carga_associacoes.py +++ b/sme_ptrf_apps/core/services/carga_associacoes.py @@ -72,10 +72,6 @@ def cria_ou_atualiza_associacao_from_row(row, unidade): defaults={ 'unidade': unidade, 'nome': row[__NOME_ASSOCIACAO], - 'presidente_associacao_nome': row[__NOME_PRESIDENTE_DIRETORIA], - 'presidente_associacao_rf': row[__RF_PRESIDENTE_DIRETORIA], - 'presidente_conselho_fiscal_nome': row[__NOME_PRESIDENTE_CONSELHO], - 'presidente_conselho_fiscal_rf': row[__RF_PRESIDENTE_CONSELHO], }, ) diff --git a/sme_ptrf_apps/core/services/demonstrativo_financeiro.py b/sme_ptrf_apps/core/services/demonstrativo_financeiro.py index b11230216..417c94679 100644 --- a/sme_ptrf_apps/core/services/demonstrativo_financeiro.py +++ b/sme_ptrf_apps/core/services/demonstrativo_financeiro.py @@ -66,21 +66,26 @@ def gerar(periodo, acao_associacao, conta_associacao): acao_associacao=acao_associacao, conta_associacao=conta_associacao, periodo=periodo, conferido=True) fechamento_periodo = FechamentoPeriodo.objects.filter( acao_associacao=acao_associacao, conta_associacao=conta_associacao, periodo=periodo).first() + + path = os.path.join(os.path.basename(staticfiles_storage.location), 'cargas') nome_arquivo = os.path.join(path, 'modelo_demonstrativo_financeiro.xlsx') workbook = load_workbook(nome_arquivo) worksheet = workbook.active - - cabecalho(worksheet, periodo, acao_associacao, conta_associacao) - identificacao_apm(worksheet, acao_associacao) - observacoes(worksheet, acao_associacao) - sintese_receita_despesa(worksheet, acao_associacao, conta_associacao, periodo, fechamento_periodo) - creditos_demonstrados(worksheet, receitas_demonstradas) - acc = len(receitas_demonstradas)-1 if len(receitas_demonstradas) > 1 else 0 - pagamentos(worksheet, rateios_conferidos, acc=acc, start_line=28) - acc += len(rateios_conferidos)-1 if len(rateios_conferidos) > 1 else 0 - pagamentos(worksheet, rateios_nao_conferidos, acc=acc, start_line=34) - + try: + cabecalho(worksheet, periodo, acao_associacao, conta_associacao) + identificacao_apm(worksheet, acao_associacao) + observacoes(worksheet, acao_associacao) + sintese_receita_despesa(worksheet, acao_associacao, conta_associacao, periodo, fechamento_periodo) + creditos_demonstrados(worksheet, receitas_demonstradas) + acc = len(receitas_demonstradas)-1 if len(receitas_demonstradas) > 1 else 0 + pagamentos(worksheet, rateios_conferidos, acc=acc, start_line=28) + acc += len(rateios_conferidos)-1 if len(rateios_conferidos) > 1 else 0 + pagamentos(worksheet, rateios_nao_conferidos, acc=acc, start_line=34) + except Exception as e: + LOGGER.info("ERRO no Demonstrativo: %s", str(e)) + + LOGGER.info("DEMONSTRATIVO GERADO") return workbook @@ -155,52 +160,60 @@ def sintese_receita_despesa(worksheet, acao_associacao, conta_associacao, period valor_saldo_bancario_custeio = 0 valor_saldo_reprogramado_proximo_periodo_capital = 0 valor_saldo_bancario_capital = 0 + valor_saldo_reprogramado_proximo_periodo_livre = 0 if saldo_reprogramado_anterior_custeio or valor_custeio_receitas_demonstradas or valor_custeio_rateios_demonstrados or valor_custeio_rateios_nao_demonstrados: row_custeio[SALDO_ANTERIOR].value = f'C {formata_valor(saldo_reprogramado_anterior_custeio)}' row_custeio[CREDITO].value = f'C {formata_valor(valor_custeio_receitas_demonstradas)}' row_custeio[DESPESA_REALIZADA].value = f'C {formata_valor(valor_custeio_rateios_demonstrados)}' - valor_saldo_reprogramado_proximo_periodo_custeio = saldo_reprogramado_anterior_custeio + valor_custeio_receitas_demonstradas - valor_custeio_rateios_demonstrados + valor_saldo_reprogramado_proximo_periodo_custeio = saldo_reprogramado_anterior_custeio + \ + valor_custeio_receitas_demonstradas - valor_custeio_rateios_demonstrados row_custeio[SALDO_REPROGRAMADO_PROXIMO].value = f'C {formata_valor(valor_saldo_reprogramado_proximo_periodo_custeio if valor_saldo_reprogramado_proximo_periodo_custeio > 0 else 0)}' row_custeio[DESPESA_NAO_DEMONSTRADA].value = f'C {formata_valor(valor_custeio_rateios_nao_demonstrados)}' valor_saldo_bancario_custeio = valor_saldo_reprogramado_proximo_periodo_custeio + valor_custeio_rateios_nao_demonstrados + valor_saldo_bancario_custeio = valor_saldo_bancario_custeio if valor_saldo_bancario_custeio > 0 else 0 row_custeio[SALDO_BANCARIO].value = f'C {formata_valor(valor_saldo_bancario_custeio)}' linha += 1 row_capital = list(worksheet.rows)[linha] if saldo_reprogramado_anterior_capital or valor_capital_receitas_demonstradas or valor_capital_rateios_demonstrados or valor_capital_rateios_nao_demonstrados: - row_capital[SALDO_ANTERIOR].value = f'K {formata_valor(saldo_reprogramado_anterior_capital)}' + row_capital[SALDO_ANTERIOR].value = f'K {formata_valor(saldo_reprogramado_anterior_capital)}' row_capital[CREDITO].value = f'K {formata_valor(valor_capital_receitas_demonstradas)}' row_capital[DESPESA_REALIZADA].value = f'K {formata_valor(valor_capital_rateios_demonstrados)}' - valor_saldo_reprogramado_proximo_periodo_capital = saldo_reprogramado_anterior_capital + valor_capital_receitas_demonstradas - valor_capital_rateios_demonstrados + valor_saldo_reprogramado_proximo_periodo_capital = saldo_reprogramado_anterior_capital + \ + valor_capital_receitas_demonstradas - valor_capital_rateios_demonstrados row_capital[SALDO_REPROGRAMADO_PROXIMO].value = f'K {formata_valor(valor_saldo_reprogramado_proximo_periodo_capital if valor_saldo_reprogramado_proximo_periodo_capital > 0 else 0)}' row_capital[DESPESA_NAO_DEMONSTRADA].value = f'K {formata_valor(valor_capital_rateios_nao_demonstrados)}' valor_saldo_bancario_capital = valor_saldo_reprogramado_proximo_periodo_capital + valor_capital_rateios_nao_demonstrados + valor_saldo_bancario_capital = valor_saldo_bancario_capital if valor_saldo_bancario_capital > 0 else 0 row_capital[SALDO_BANCARIO].value = f'K {formata_valor(valor_saldo_bancario_capital)}' linha += 1 row_livre = list(worksheet.rows)[linha] - if saldo_reprogramado_anterior_livre or valor_livre_receitas_demonstradas: + if saldo_reprogramado_anterior_livre or valor_livre_receitas_demonstradas or valor_saldo_reprogramado_proximo_periodo_custeio < 0 or valor_saldo_reprogramado_proximo_periodo_capital < 0: row_livre[SALDO_ANTERIOR].value = f'L {formata_valor(saldo_reprogramado_anterior_livre)}' row_livre[CREDITO].value = f'L {formata_valor(valor_livre_receitas_demonstradas)}' cor_cinza = styles.colors.Color(rgb='808080') fill = styles.fills.PatternFill(patternType='solid', fgColor=cor_cinza) row_livre[DESPESA_REALIZADA].fill = fill - row_livre[DESPESA_NAO_DEMONSTRADA].fill = fill + row_livre[DESPESA_NAO_DEMONSTRADA].fill = fill valor_saldo_reprogramado_proximo_periodo_livre = saldo_reprogramado_anterior_livre + valor_livre_receitas_demonstradas - valor_saldo_reprogramado_proximo_periodo_livre = valor_saldo_reprogramado_proximo_periodo_livre + valor_saldo_reprogramado_proximo_periodo_capital if valor_saldo_reprogramado_proximo_periodo_capital < 0 else valor_saldo_reprogramado_proximo_periodo_livre - valor_saldo_reprogramado_proximo_periodo_livre = valor_saldo_reprogramado_proximo_periodo_livre + valor_saldo_reprogramado_proximo_periodo_custeio if valor_saldo_reprogramado_proximo_periodo_custeio < 0 else valor_saldo_reprogramado_proximo_periodo_livre + valor_saldo_reprogramado_proximo_periodo_livre = valor_saldo_reprogramado_proximo_periodo_livre + \ + valor_saldo_reprogramado_proximo_periodo_capital if valor_saldo_reprogramado_proximo_periodo_capital < 0 else valor_saldo_reprogramado_proximo_periodo_livre + valor_saldo_reprogramado_proximo_periodo_livre = valor_saldo_reprogramado_proximo_periodo_livre + \ + valor_saldo_reprogramado_proximo_periodo_custeio if valor_saldo_reprogramado_proximo_periodo_custeio < 0 else valor_saldo_reprogramado_proximo_periodo_livre row_livre[SALDO_REPROGRAMADO_PROXIMO].value = f'L {formata_valor(valor_saldo_reprogramado_proximo_periodo_livre)}' valor_total_reprogramado_proximo = valor_saldo_reprogramado_proximo_periodo_livre - valor_total_reprogramado_proximo = valor_total_reprogramado_proximo + valor_saldo_reprogramado_proximo_periodo_capital if valor_saldo_reprogramado_proximo_periodo_capital > 0 else valor_total_reprogramado_proximo - valor_total_reprogramado_proximo = valor_total_reprogramado_proximo + valor_saldo_reprogramado_proximo_periodo_custeio if valor_saldo_reprogramado_proximo_periodo_custeio > 0 else valor_total_reprogramado_proximo - row_livre[SALDO_BANCARIO].value = f'L {formata_valor(valor_saldo_reprogramado_proximo_periodo_livre)}' - + valor_total_reprogramado_proximo = valor_total_reprogramado_proximo + \ + valor_saldo_reprogramado_proximo_periodo_capital if valor_saldo_reprogramado_proximo_periodo_capital > 0 else valor_total_reprogramado_proximo + valor_total_reprogramado_proximo = valor_total_reprogramado_proximo + \ + valor_saldo_reprogramado_proximo_periodo_custeio if valor_saldo_reprogramado_proximo_periodo_custeio > 0 else valor_total_reprogramado_proximo + row_livre[SALDO_BANCARIO].value = f'L {formata_valor(valor_saldo_reprogramado_proximo_periodo_livre)}' if valor_saldo_reprogramado_proximo_periodo_livre != 0 else '' row_custeio[TOTAL_REPROGRAMADO_PROXIMO].value = formata_valor(valor_total_reprogramado_proximo) - - row_custeio[TOTAL_SALDO_BANCARIO].value = formata_valor(valor_saldo_bancario_capital + valor_saldo_bancario_custeio + valor_saldo_reprogramado_proximo_periodo_livre) + row_custeio[TOTAL_SALDO_BANCARIO].value = formata_valor( + valor_saldo_bancario_capital + valor_saldo_bancario_custeio + valor_saldo_reprogramado_proximo_periodo_livre) def creditos_demonstrados(worksheet, receitas, acc=0, start_line=22): @@ -330,6 +343,7 @@ def replace(m): def formata_valor(valor): - locale.setlocale(locale.LC_MONETARY, "pt_BR.UTF-8") - - return locale.currency(valor, grouping=True).split(" ")[1] + from babel.numbers import format_currency + sinal, valor_formatado = format_currency(valor, 'BRL', locale='pt_BR').split('\xa0') + sinal = '-' if '-' in sinal else '' + return f'{sinal}{valor_formatado}' diff --git a/sme_ptrf_apps/core/services/enviar_email.py b/sme_ptrf_apps/core/services/enviar_email.py index ef65d2174..94d6a3050 100644 --- a/sme_ptrf_apps/core/services/enviar_email.py +++ b/sme_ptrf_apps/core/services/enviar_email.py @@ -24,4 +24,4 @@ def enviar_email_html(assunto, template, context, enviar_para): email.content_subtype = 'html' email.send() except Exception as err: - logger.error(str(err)) + logger.info("Erro email: %s", str(err)) diff --git a/sme_ptrf_apps/core/services/exporta_associacao.py b/sme_ptrf_apps/core/services/exporta_associacao.py new file mode 100644 index 000000000..66eabb836 --- /dev/null +++ b/sme_ptrf_apps/core/services/exporta_associacao.py @@ -0,0 +1,105 @@ +import logging +import os + +from django.contrib.staticfiles.storage import staticfiles_storage +from openpyxl import load_workbook + +from ..choices.membro_associacao import RepresentacaoCargo + +LOGGER = logging.getLogger(__name__) + +# Worksheets +ASSOCIACAO = 0 +MEMBROS = 1 +CONTAS = 2 + +# Linhas Dados Básicos +NOME_ASSOCIACAO = 0 +EOL = 1 +DRE = 2 +CNPJ = 3 +CCM = 4 +EMAIL_ASSOCIACAO = 5 + +# Colunas Membros +CARGO = 0 +NOME_MEMBRO = 1 +REPRESENTACAO = 2 +RF_EOL = 3 +CARGO_EDUCACAO = 4 +EMAIL_MEMBRO = 5 + +# Linhas Membros +CARGOS = { + 'PRESIDENTE_DIRETORIA_EXECUTIVA': 1, + 'VICE_PRESIDENTE_DIRETORIA_EXECUTIVA': 2, + 'SECRETARIO': 3, + 'TESOUREIRO': 4, + 'VOGAL_1': 5, + 'VOGAL_2': 6, + 'VOGAL_3': 7, + 'VOGAL_4': 8, + 'VOGAL_5': 9, + 'PRESIDENTE_CONSELHO_FISCAL': 10, + 'CONSELHEIRO_1': 11, + 'CONSELHEIRO_2': 12, + 'CONSELHEIRO_3': 13, + 'CONSELHEIRO_4': 14, +} + +# Colunas Contas da Associação +BANCO = 0 +TIPO = 1 +AGENCIA = 2 +NUMERO = 3 + + +def gerar_planilha(associacao): + LOGGER.info(f'EXPORTANDO DADOS DA ASSOCIACAO {associacao.nome}...') + + path = os.path.join(os.path.basename(staticfiles_storage.location), 'modelos') + nome_arquivo = os.path.join(path, 'modelo_exportacao_associacao.xlsx') + workbook = load_workbook(nome_arquivo) + + dados_basicos(workbook, associacao) + membros(workbook, associacao) + contas(workbook, associacao) + + return workbook + + +def dados_basicos(workbook, associacao): + worksheet = workbook.worksheets[ASSOCIACAO] + rows = list(worksheet.rows) + rows[NOME_ASSOCIACAO][1].value = associacao.nome + rows[EOL][1].value = associacao.unidade.codigo_eol + rows[DRE][1].value = associacao.unidade.dre.nome if associacao.unidade.dre else '' + rows[CNPJ][1].value = associacao.cnpj + rows[CCM][1].value = associacao.ccm + rows[EMAIL_ASSOCIACAO][1].value = associacao.email + + +def membros(workbook, associacao): + membros = associacao.cargos.all() + worksheet = workbook.worksheets[MEMBROS] + rows = list(worksheet.rows) + for membro in membros: + linha = CARGOS[membro.cargo_associacao] + rows[linha][NOME_MEMBRO].value = membro.nome + rows[linha][REPRESENTACAO].value = RepresentacaoCargo[membro.representacao].value + rows[linha][RF_EOL].value = membro.codigo_identificacao + rows[linha][CARGO_EDUCACAO].value = membro.cargo_educacao + rows[linha][EMAIL_MEMBRO].value = membro.email + + +def contas(workbook, associacao): + contas = associacao.contas.all() + worksheet = workbook.worksheets[CONTAS] + rows = list(worksheet.rows) + linha = 1 + for conta in contas: + rows[linha][BANCO].value = conta.banco_nome + rows[linha][TIPO].value = conta.tipo_conta.nome if conta.tipo_conta else ' ' + rows[linha][AGENCIA].value = conta.agencia + rows[linha][NUMERO].value = conta.numero_conta + linha += 1 diff --git a/sme_ptrf_apps/core/services/processa_cargas.py b/sme_ptrf_apps/core/services/processa_cargas.py index 118b3238c..1f0bf5169 100644 --- a/sme_ptrf_apps/core/services/processa_cargas.py +++ b/sme_ptrf_apps/core/services/processa_cargas.py @@ -1,10 +1,10 @@ from sme_ptrf_apps.core.models.arquivo import (CARGA_PERIODO_INICIAL, CARGA_REPASSE_REALIZADO, CARGA_REPASSE_PREVISTO, - CARGA_ASSOCIACOES) + CARGA_ASSOCIACOES, CARGA_USUARIOS) from sme_ptrf_apps.core.services.carga_associacoes import carrega_associacoes from sme_ptrf_apps.core.services.periodo_inicial import carrega_periodo_inicial from sme_ptrf_apps.receitas.services.carga_repasses_previstos import carrega_repasses_previstos from sme_ptrf_apps.receitas.services.carga_repasses_realizados import carrega_repasses_realizados - +from sme_ptrf_apps.users.services.carga_usuarios import carrega_usuarios def processa_cargas(queryset): for arquivo in queryset.all(): @@ -16,3 +16,5 @@ def processa_cargas(queryset): carrega_repasses_previstos(arquivo) elif arquivo.tipo_carga == CARGA_ASSOCIACOES: carrega_associacoes(arquivo) + elif arquivo.tipo_carga == CARGA_USUARIOS: + carrega_usuarios(arquivo) diff --git a/sme_ptrf_apps/core/tests/tests_api_associacoes/test_associacoes_verificacao_regularidade_update.py b/sme_ptrf_apps/core/tests/tests_api_associacoes/test_associacoes_verificacao_regularidade_update.py new file mode 100644 index 000000000..e96780772 --- /dev/null +++ b/sme_ptrf_apps/core/tests/tests_api_associacoes/test_associacoes_verificacao_regularidade_update.py @@ -0,0 +1,241 @@ +import json + +import pytest +from model_bakery import baker +from rest_framework import status + +from ....dre.models import VerificacaoRegularidadeAssociacao + +pytestmark = pytest.mark.django_db + + +@pytest.fixture +def grupo_verificacao_regularidade_documentos(): + return baker.make('dre.GrupoVerificacaoRegularidade', titulo='Documentos') + + +@pytest.fixture +def lista_verificacao_regularidade_documentos_associacao(grupo_verificacao_regularidade_documentos): + return baker.make( + 'dre.ListaVerificacaoRegularidade', + titulo='Documentos da Associação', + grupo=grupo_verificacao_regularidade_documentos + ) + + +@pytest.fixture +def item_verificacao_regularidade_documentos_associacao_cnpj(lista_verificacao_regularidade_documentos_associacao): + return baker.make( + 'dre.ItemVerificacaoRegularidade', + descricao='CNPJ', + lista=lista_verificacao_regularidade_documentos_associacao + ) + + +@pytest.fixture +def item_verificacao_regularidade_documentos_associacao_rais(lista_verificacao_regularidade_documentos_associacao): + return baker.make( + 'dre.ItemVerificacaoRegularidade', + descricao='RAIS', + lista=lista_verificacao_regularidade_documentos_associacao + ) + + +@pytest.fixture +def verificacao_regularidade_associacao_documento_cnpj(grupo_verificacao_regularidade_documentos, + lista_verificacao_regularidade_documentos_associacao, + item_verificacao_regularidade_documentos_associacao_cnpj, + associacao): + return baker.make( + 'dre.VerificacaoRegularidadeAssociacao', + associacao=associacao, + grupo_verificacao=grupo_verificacao_regularidade_documentos, + lista_verificacao=lista_verificacao_regularidade_documentos_associacao, + item_verificacao=item_verificacao_regularidade_documentos_associacao_cnpj, + regular=True + ) + + +@pytest.fixture +def verificacao_regularidade_associacao_documento_rais(grupo_verificacao_regularidade_documentos, + lista_verificacao_regularidade_documentos_associacao, + item_verificacao_regularidade_documentos_associacao_rais, + associacao): + return baker.make( + 'dre.VerificacaoRegularidadeAssociacao', + associacao=associacao, + grupo_verificacao=grupo_verificacao_regularidade_documentos, + lista_verificacao=lista_verificacao_regularidade_documentos_associacao, + item_verificacao=item_verificacao_regularidade_documentos_associacao_rais, + regular=True + ) + + +def test_marca_item_verificacao_quando_sem_verificacao_ja_feita(client, associacao, + grupo_verificacao_regularidade_documentos, + lista_verificacao_regularidade_documentos_associacao, + item_verificacao_regularidade_documentos_associacao_cnpj, + ): + response = client.get( + f'/api/associacoes/{associacao.uuid}/marca-item-verificacao/?item={item_verificacao_regularidade_documentos_associacao_cnpj.uuid}', + content_type='application/json') + result = json.loads(response.content) + + esperado = { + 'associacao': f'{associacao.uuid}', + 'item_verificacao': f'{item_verificacao_regularidade_documentos_associacao_cnpj.uuid}', + 'mensagem': 'Item de verificação marcado.' + } + + assert response.status_code == status.HTTP_200_OK + assert result == esperado + + verificacao = VerificacaoRegularidadeAssociacao.objects.get(associacao=associacao, + item_verificacao=item_verificacao_regularidade_documentos_associacao_cnpj) + assert verificacao.regular + + +def test_marca_item_verificacao_quando_com_verificacao_ja_feita(client, associacao, + grupo_verificacao_regularidade_documentos, + lista_verificacao_regularidade_documentos_associacao, + item_verificacao_regularidade_documentos_associacao_cnpj, + verificacao_regularidade_associacao_documento_cnpj + ): + response = client.get( + f'/api/associacoes/{associacao.uuid}/marca-item-verificacao/?item={item_verificacao_regularidade_documentos_associacao_cnpj.uuid}', + content_type='application/json') + result = json.loads(response.content) + + esperado = { + 'associacao': f'{associacao.uuid}', + 'item_verificacao': f'{item_verificacao_regularidade_documentos_associacao_cnpj.uuid}', + 'mensagem': 'Item de verificação marcado.' + } + + assert response.status_code == status.HTTP_200_OK + assert result == esperado + + verificacao = VerificacaoRegularidadeAssociacao.objects.get(associacao=associacao, + item_verificacao=item_verificacao_regularidade_documentos_associacao_cnpj) + assert verificacao.regular, 'Deve continuar existindo a verificação.' + + verificacoes = VerificacaoRegularidadeAssociacao.objects.filter(associacao=associacao, + item_verificacao=item_verificacao_regularidade_documentos_associacao_cnpj) + assert verificacoes.count() == 1, 'Deve continuar havendo apenas uma verificação.' + + +def test_desmarca_item_verificacao_quando_com_verificacao_ja_feita(client, associacao, + grupo_verificacao_regularidade_documentos, + lista_verificacao_regularidade_documentos_associacao, + item_verificacao_regularidade_documentos_associacao_cnpj, + verificacao_regularidade_associacao_documento_cnpj + ): + response = client.get( + f'/api/associacoes/{associacao.uuid}/desmarca-item-verificacao/?item={item_verificacao_regularidade_documentos_associacao_cnpj.uuid}', + content_type='application/json') + result = json.loads(response.content) + + esperado = { + 'associacao': f'{associacao.uuid}', + 'item_verificacao': f'{item_verificacao_regularidade_documentos_associacao_cnpj.uuid}', + 'mensagem': 'Item de verificação desmarcado.' + } + + assert response.status_code == status.HTTP_200_OK + assert result == esperado + + verificacoes = VerificacaoRegularidadeAssociacao.objects.filter(associacao=associacao, + item_verificacao=item_verificacao_regularidade_documentos_associacao_cnpj) + assert verificacoes.count() == 0, 'A verificação deveria ter sido removida.' + + +def test_marca_lista_verificacao_quando_sem_verificacao_ja_feita(client, associacao, + grupo_verificacao_regularidade_documentos, + lista_verificacao_regularidade_documentos_associacao, + item_verificacao_regularidade_documentos_associacao_cnpj, + item_verificacao_regularidade_documentos_associacao_rais + ): + response = client.get( + f'/api/associacoes/{associacao.uuid}/marca-lista-verificacao/?lista={lista_verificacao_regularidade_documentos_associacao.uuid}', + content_type='application/json') + result = json.loads(response.content) + + esperado = { + 'associacao': f'{associacao.uuid}', + 'lista_verificacao': f'{lista_verificacao_regularidade_documentos_associacao.uuid}', + 'mensagem': 'Itens da lista de verificação marcados.' + } + + assert response.status_code == status.HTTP_200_OK + assert result == esperado + + verificacao = VerificacaoRegularidadeAssociacao.objects.filter(associacao=associacao, + lista_verificacao=lista_verificacao_regularidade_documentos_associacao) + assert verificacao.count() == 2, 'Deveriam haver dois itens de verificação criados.' + + +def test_desmarca_lista_verificacao(client, associacao, + grupo_verificacao_regularidade_documentos, + lista_verificacao_regularidade_documentos_associacao, + item_verificacao_regularidade_documentos_associacao_cnpj, + item_verificacao_regularidade_documentos_associacao_rais, + verificacao_regularidade_associacao_documento_cnpj, + verificacao_regularidade_associacao_documento_rais + ): + response = client.get( + f'/api/associacoes/{associacao.uuid}/desmarca-lista-verificacao/?lista={lista_verificacao_regularidade_documentos_associacao.uuid}', + content_type='application/json') + result = json.loads(response.content) + + esperado = { + 'associacao': f'{associacao.uuid}', + 'lista_verificacao': f'{lista_verificacao_regularidade_documentos_associacao.uuid}', + 'mensagem': 'Itens da lista de verificação desmarcados.' + } + + assert response.status_code == status.HTTP_200_OK + assert result == esperado + + verificacao = VerificacaoRegularidadeAssociacao.objects.filter(associacao=associacao, + lista_verificacao=lista_verificacao_regularidade_documentos_associacao) + assert verificacao.count() == 0, 'Não deveria haver nenhum itens de verificação.' + + +def test_atualiza_itens_verificacao(client, associacao, + grupo_verificacao_regularidade_documentos, + lista_verificacao_regularidade_documentos_associacao, + item_verificacao_regularidade_documentos_associacao_cnpj, + item_verificacao_regularidade_documentos_associacao_rais, + verificacao_regularidade_associacao_documento_cnpj, + verificacao_regularidade_associacao_documento_rais + ): + payload = [ + { + "uuid": f'{item_verificacao_regularidade_documentos_associacao_cnpj.uuid}', + "regular": False + }, + { + "uuid": f'{item_verificacao_regularidade_documentos_associacao_rais.uuid}', + "regular": True + } + ] + response = client.post( + f'/api/associacoes/{associacao.uuid}/atualiza-itens-verificacao/', data=json.dumps(payload), + content_type='application/json') + result = json.loads(response.content) + + esperado = { + 'associacao': f'{associacao.uuid}', + 'mensagem': 'Itens de verificação atualizados.' + } + + assert response.status_code == status.HTTP_200_OK + assert result == esperado + + verificacao1 = VerificacaoRegularidadeAssociacao.objects.filter(associacao=associacao, + item_verificacao=item_verificacao_regularidade_documentos_associacao_cnpj) + assert verificacao1.count() == 0, 'Esse item não deveria existir' + + verificacao2 = VerificacaoRegularidadeAssociacao.objects.filter(associacao=associacao, + item_verificacao=item_verificacao_regularidade_documentos_associacao_rais) + assert verificacao2.count() == 1, 'Esse item deveria existir' diff --git a/sme_ptrf_apps/core/tests/tests_api_associacoes/test_get_associacoes_tabelas.py b/sme_ptrf_apps/core/tests/tests_api_associacoes/test_get_associacoes_tabelas.py new file mode 100644 index 000000000..aa10ae705 --- /dev/null +++ b/sme_ptrf_apps/core/tests/tests_api_associacoes/test_get_associacoes_tabelas.py @@ -0,0 +1,21 @@ +import json + +import pytest +from rest_framework import status + +from ...models import Associacao, Unidade + +pytestmark = pytest.mark.django_db + + +def test_api_get_associacoes_tabelas(client): + response = client.get('/api/associacoes/tabelas/', content_type='application/json') + result = json.loads(response.content) + + esperado = { + 'tipos_unidade': Unidade.tipos_unidade_to_json(), + 'status_regularidade': Associacao.status_regularidade_to_json(), + } + + assert response.status_code == status.HTTP_200_OK + assert result == esperado diff --git a/sme_ptrf_apps/core/tests/tests_api_associacoes/test_get_associacoes_verificacao_regularidade.py b/sme_ptrf_apps/core/tests/tests_api_associacoes/test_get_associacoes_verificacao_regularidade.py new file mode 100644 index 000000000..908984cae --- /dev/null +++ b/sme_ptrf_apps/core/tests/tests_api_associacoes/test_get_associacoes_verificacao_regularidade.py @@ -0,0 +1,86 @@ +import json + +import pytest +from model_bakery import baker +from rest_framework import status + +pytestmark = pytest.mark.django_db + + +@pytest.fixture +def grupo_verificacao_regularidade_documentos(): + return baker.make('dre.GrupoVerificacaoRegularidade', titulo='Documentos') + + +@pytest.fixture +def lista_verificacao_regularidade_documentos_associacao(grupo_verificacao_regularidade_documentos): + return baker.make( + 'dre.ListaVerificacaoRegularidade', + titulo='Documentos da Associação', + grupo=grupo_verificacao_regularidade_documentos + ) + + +@pytest.fixture +def item_verificacao_regularidade_documentos_associacao_cnpj(lista_verificacao_regularidade_documentos_associacao): + return baker.make( + 'dre.ItemVerificacaoRegularidade', + descricao='CNPJ', + lista=lista_verificacao_regularidade_documentos_associacao + ) + + +@pytest.fixture +def verificacao_regularidade_associacao_documento_cnpj(grupo_verificacao_regularidade_documentos, + lista_verificacao_regularidade_documentos_associacao, + item_verificacao_regularidade_documentos_associacao_cnpj, + associacao): + return baker.make( + 'dre.VerificacaoRegularidadeAssociacao', + associacao=associacao, + grupo_verificacao=grupo_verificacao_regularidade_documentos, + lista_verificacao=lista_verificacao_regularidade_documentos_associacao, + item_verificacao=item_verificacao_regularidade_documentos_associacao_cnpj, + regular=True + ) + + +def test_api_get_associacoes_verificacao_regularidade(client, associacao, + grupo_verificacao_regularidade_documentos, + lista_verificacao_regularidade_documentos_associacao, + item_verificacao_regularidade_documentos_associacao_cnpj, + verificacao_regularidade_associacao_documento_cnpj + ): + response = client.get(f'/api/associacoes/{associacao.uuid}/verificacao-regularidade/', + content_type='application/json') + result = json.loads(response.content) + + esperado = { + 'uuid': f'{associacao.uuid}', + 'verificacao_regularidade': { + 'grupos_verificacao': [ + { + 'uuid': f'{grupo_verificacao_regularidade_documentos.uuid}', + 'titulo': grupo_verificacao_regularidade_documentos.titulo, + 'listas_verificacao': [ + { + 'uuid': f'{lista_verificacao_regularidade_documentos_associacao.uuid}', + 'titulo': lista_verificacao_regularidade_documentos_associacao.titulo, + 'itens_verificacao': [ + { + 'uuid': f'{item_verificacao_regularidade_documentos_associacao_cnpj.uuid}', + 'descricao': item_verificacao_regularidade_documentos_associacao_cnpj.descricao, + 'regular': True + }, + ], + 'status_lista_verificacao': 'Regular' + }, + ] + + }, + ] + } + } + + assert response.status_code == status.HTTP_200_OK + assert result == esperado diff --git a/sme_ptrf_apps/core/tests/tests_api_associacoes/test_get_periodos_para_prestacao_contas.py b/sme_ptrf_apps/core/tests/tests_api_associacoes/test_get_periodos_para_prestacao_contas.py new file mode 100644 index 000000000..234063251 --- /dev/null +++ b/sme_ptrf_apps/core/tests/tests_api_associacoes/test_get_periodos_para_prestacao_contas.py @@ -0,0 +1,117 @@ +import json +from datetime import date + +import pytest +from freezegun import freeze_time +from model_bakery import baker +from rest_framework import status + +pytestmark = pytest.mark.django_db + + +@pytest.fixture +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, 6, 30), + periodo_anterior=None + ) + + +@pytest.fixture +def periodo_2019_2(periodo_2019_1): + return baker.make( + 'Periodo', + referencia='2019.2', + data_inicio_realizacao_despesas=date(2019, 7, 1), + data_fim_realizacao_despesas=date(2019, 12, 31), + periodo_anterior=periodo_2019_1 + ) + + +@pytest.fixture +def periodo_2020_1(periodo_2019_2): + return baker.make( + 'Periodo', + referencia='2020.1', + data_inicio_realizacao_despesas=date(2020, 1, 1), + data_fim_realizacao_despesas=date(2020, 6, 30), + periodo_anterior=periodo_2019_2 + ) + + +@pytest.fixture +def periodo_2020_2(periodo_2020_1): + return baker.make( + 'Periodo', + referencia='2020.2', + data_inicio_realizacao_despesas=date(2020, 7, 1), + data_fim_realizacao_despesas=date(2020, 12, 31), + periodo_anterior=periodo_2020_1 + ) + + +@pytest.fixture +def prestacao_conta_2019_2_cartao(periodo_2019_2, associacao, conta_associacao_cartao): + return baker.make( + 'PrestacaoConta', + periodo=periodo_2019_2, + associacao=associacao, + conta_associacao=conta_associacao_cartao, + ) + + +@pytest.fixture +def prestacao_conta_2020_1_cartao(periodo_2020_1, associacao, conta_associacao_cartao): + return baker.make( + 'PrestacaoConta', + periodo=periodo_2020_1, + associacao=associacao, + conta_associacao=conta_associacao_cartao, + ) + + +@pytest.fixture +def prestacao_conta_2020_1_cheque(periodo_2020_1, associacao, conta_associacao_cheque): + return baker.make( + 'PrestacaoConta', + periodo=periodo_2020_1, + associacao=associacao, + conta_associacao=conta_associacao_cheque, + ) + + +@freeze_time('2020-06-15') +def test_get_periodos_prestacao_de_contas_da_associacao( + client, + associacao, + periodo_2019_1, + periodo_2019_2, + periodo_2020_1, + periodo_2020_2, + prestacao_conta_2019_2_cartao, + prestacao_conta_2020_1_cartao, + prestacao_conta_2020_1_cheque +): + response = client.get(f'/api/associacoes/{associacao.uuid}/periodos-para-prestacao-de-contas/', + content_type='application/json') + result = json.loads(response.content) + + periodos_esperados = [periodo_2019_2, periodo_2020_1, periodo_2020_2] + + result_esperado = [] + for p in periodos_esperados: + result_esperado.append( + { + "uuid": f'{p.uuid}', + "referencia": p.referencia, + "data_inicio_realizacao_despesas": f'{p.data_inicio_realizacao_despesas}' if p.data_inicio_realizacao_despesas else None, + "data_fim_realizacao_despesas": f'{p.data_fim_realizacao_despesas}' if p.data_fim_realizacao_despesas else None, + "referencia_por_extenso": f"{p.referencia.split('.')[1]}° repasse de {p.referencia.split('.')[0]}" + } + ) + + assert response.status_code == status.HTTP_200_OK + assert result == result_esperado diff --git a/sme_ptrf_apps/core/tests/tests_api_associacoes/test_list_associacoes.py b/sme_ptrf_apps/core/tests/tests_api_associacoes/test_list_associacoes.py new file mode 100644 index 000000000..1ec4eef98 --- /dev/null +++ b/sme_ptrf_apps/core/tests/tests_api_associacoes/test_list_associacoes.py @@ -0,0 +1,193 @@ +import json + +import pytest +from model_bakery import baker +from rest_framework import status + +pytestmark = pytest.mark.django_db + + +@pytest.fixture +def dre_1(): + return baker.make('Unidade', codigo_eol='00001', tipo_unidade='DRE', nome='DRE 1') + + +@pytest.fixture +def dre_2(): + return baker.make('Unidade', codigo_eol='00002', tipo_unidade='DRE', nome='DRE 2') + + +@pytest.fixture +def ceu_vassouras_dre_1(dre_1): + return baker.make('Unidade', codigo_eol='00011', dre=dre_1, tipo_unidade='CEU', nome='Escola Vassouras') + + +@pytest.fixture +def emef_mendes_dre_2(dre_2): + return baker.make('Unidade', codigo_eol='00022', dre=dre_2, tipo_unidade='EMEF', nome='Escola Mendes') + + +@pytest.fixture +def associacao_valenca_ceu_vassouras_dre_1(ceu_vassouras_dre_1, periodo_anterior): + return baker.make( + 'Associacao', + nome='Associacao Valença', + cnpj='52.302.275/0001-83', + unidade=ceu_vassouras_dre_1, + status_regularidade='PENDENTE' + ) + + +@pytest.fixture +def associacao_pinheiros_emef_mendes_dre_2(emef_mendes_dre_2, periodo_anterior): + return baker.make( + 'Associacao', + nome='Associação Pinheiros', + cnpj='05.861.145/0001-09', + unidade=emef_mendes_dre_2, + status_regularidade='REGULAR' + ) + + +def test_api_list_associacoes_todas(client, associacao_valenca_ceu_vassouras_dre_1, + associacao_pinheiros_emef_mendes_dre_2): + response = client.get(f'/api/associacoes/', content_type='application/json') + result = json.loads(response.content) + + result_esperado = [ + { + 'uuid': f'{associacao_valenca_ceu_vassouras_dre_1.uuid}', + 'nome': associacao_valenca_ceu_vassouras_dre_1.nome, + 'unidade': { + 'uuid': f'{associacao_valenca_ceu_vassouras_dre_1.unidade.uuid}', + 'codigo_eol': associacao_valenca_ceu_vassouras_dre_1.unidade.codigo_eol, + 'nome_com_tipo': associacao_valenca_ceu_vassouras_dre_1.unidade.nome_com_tipo + }, + 'status_regularidade': associacao_valenca_ceu_vassouras_dre_1.status_regularidade, + }, + { + 'uuid': f'{associacao_pinheiros_emef_mendes_dre_2.uuid}', + 'nome': associacao_pinheiros_emef_mendes_dre_2.nome, + 'unidade': { + 'uuid': f'{associacao_pinheiros_emef_mendes_dre_2.unidade.uuid}', + 'codigo_eol': associacao_pinheiros_emef_mendes_dre_2.unidade.codigo_eol, + 'nome_com_tipo': associacao_pinheiros_emef_mendes_dre_2.unidade.nome_com_tipo + }, + 'status_regularidade': associacao_pinheiros_emef_mendes_dre_2.status_regularidade, + }, + ] + + assert response.status_code == status.HTTP_200_OK + assert result == result_esperado + + +def test_api_list_associacoes_de_uma_dre(client, associacao_valenca_ceu_vassouras_dre_1, + associacao_pinheiros_emef_mendes_dre_2): + response = client.get( + f'/api/associacoes/?unidade__dre__uuid={associacao_valenca_ceu_vassouras_dre_1.unidade.dre.uuid}', + content_type='application/json') + result = json.loads(response.content) + + result_esperado = [ + { + 'uuid': f'{associacao_valenca_ceu_vassouras_dre_1.uuid}', + 'nome': associacao_valenca_ceu_vassouras_dre_1.nome, + 'unidade': { + 'uuid': f'{associacao_valenca_ceu_vassouras_dre_1.unidade.uuid}', + 'codigo_eol': associacao_valenca_ceu_vassouras_dre_1.unidade.codigo_eol, + 'nome_com_tipo': associacao_valenca_ceu_vassouras_dre_1.unidade.nome_com_tipo + }, + 'status_regularidade': associacao_valenca_ceu_vassouras_dre_1.status_regularidade, + }, + ] + + assert response.status_code == status.HTTP_200_OK + assert result == result_esperado + + +def test_api_list_associacoes_pelo_nome_associacao_ignorando_acentos(client, associacao_valenca_ceu_vassouras_dre_1, + associacao_pinheiros_emef_mendes_dre_2): + response = client.get(f'/api/associacoes/?nome=valenca', content_type='application/json') + result = json.loads(response.content) + + result_esperado = [ + { + 'uuid': f'{associacao_valenca_ceu_vassouras_dre_1.uuid}', + 'nome': associacao_valenca_ceu_vassouras_dre_1.nome, + 'unidade': { + 'uuid': f'{associacao_valenca_ceu_vassouras_dre_1.unidade.uuid}', + 'codigo_eol': associacao_valenca_ceu_vassouras_dre_1.unidade.codigo_eol, + 'nome_com_tipo': associacao_valenca_ceu_vassouras_dre_1.unidade.nome_com_tipo + }, + 'status_regularidade': associacao_valenca_ceu_vassouras_dre_1.status_regularidade, + }, + ] + + assert response.status_code == status.HTTP_200_OK + assert result == result_esperado + + +def test_api_list_associacoes_pelo_nome_escola(client, associacao_valenca_ceu_vassouras_dre_1, + associacao_pinheiros_emef_mendes_dre_2): + response = client.get(f'/api/associacoes/?nome=vassouras', content_type='application/json') + result = json.loads(response.content) + + result_esperado = [ + { + 'uuid': f'{associacao_valenca_ceu_vassouras_dre_1.uuid}', + 'nome': associacao_valenca_ceu_vassouras_dre_1.nome, + 'unidade': { + 'uuid': f'{associacao_valenca_ceu_vassouras_dre_1.unidade.uuid}', + 'codigo_eol': associacao_valenca_ceu_vassouras_dre_1.unidade.codigo_eol, + 'nome_com_tipo': associacao_valenca_ceu_vassouras_dre_1.unidade.nome_com_tipo + }, + 'status_regularidade': associacao_valenca_ceu_vassouras_dre_1.status_regularidade, + }, + ] + + assert response.status_code == status.HTTP_200_OK + assert result == result_esperado + + +def test_api_list_associacoes_pelo_status_regularidade(client, associacao_valenca_ceu_vassouras_dre_1, + associacao_pinheiros_emef_mendes_dre_2): + response = client.get(f'/api/associacoes/?status_regularidade=PENDENTE', content_type='application/json') + result = json.loads(response.content) + + result_esperado = [ + { + 'uuid': f'{associacao_valenca_ceu_vassouras_dre_1.uuid}', + 'nome': associacao_valenca_ceu_vassouras_dre_1.nome, + 'unidade': { + 'uuid': f'{associacao_valenca_ceu_vassouras_dre_1.unidade.uuid}', + 'codigo_eol': associacao_valenca_ceu_vassouras_dre_1.unidade.codigo_eol, + 'nome_com_tipo': associacao_valenca_ceu_vassouras_dre_1.unidade.nome_com_tipo + }, + 'status_regularidade': associacao_valenca_ceu_vassouras_dre_1.status_regularidade, + }, + ] + + assert response.status_code == status.HTTP_200_OK + assert result == result_esperado + + +def test_api_list_associacoes_pelo_tipo_unidade(client, associacao_valenca_ceu_vassouras_dre_1, + associacao_pinheiros_emef_mendes_dre_2): + response = client.get(f'/api/associacoes/?unidade__tipo_unidade=CEU', content_type='application/json') + result = json.loads(response.content) + + result_esperado = [ + { + 'uuid': f'{associacao_valenca_ceu_vassouras_dre_1.uuid}', + 'nome': associacao_valenca_ceu_vassouras_dre_1.nome, + 'unidade': { + 'uuid': f'{associacao_valenca_ceu_vassouras_dre_1.unidade.uuid}', + 'codigo_eol': associacao_valenca_ceu_vassouras_dre_1.unidade.codigo_eol, + 'nome_com_tipo': associacao_valenca_ceu_vassouras_dre_1.unidade.nome_com_tipo + }, + 'status_regularidade': associacao_valenca_ceu_vassouras_dre_1.status_regularidade, + }, + ] + + assert response.status_code == status.HTTP_200_OK + assert result == result_esperado diff --git a/sme_ptrf_apps/core/tests/tests_api_associacoes/test_list_proccessos_associacao.py b/sme_ptrf_apps/core/tests/tests_api_associacoes/test_list_proccessos_associacao.py new file mode 100644 index 000000000..4b7658fa4 --- /dev/null +++ b/sme_ptrf_apps/core/tests/tests_api_associacoes/test_list_proccessos_associacao.py @@ -0,0 +1,42 @@ +import json + +import pytest +from model_bakery import baker +from rest_framework import status + +pytestmark = pytest.mark.django_db + + +@pytest.fixture +def processo_de_outra_associacao(associacao_sem_periodo_inicial): + return baker.make( + 'ProcessoAssociacao', + associacao=associacao_sem_periodo_inicial, + numero_processo='777777', + ano='2019' + ) + + +def test_list_processos_da_associacao(client, associacao, processo_associacao_123456_2019, + processo_de_outra_associacao): + response = client.get(f'/api/associacoes/{associacao.uuid}/processos/', + content_type='application/json') + result = json.loads(response.content) + + esperado = [ + { + 'uuid': f'{processo_associacao_123456_2019.uuid}', + 'associacao': + { + 'id': processo_associacao_123456_2019.associacao.id, + 'nome': processo_associacao_123456_2019.associacao.nome + }, + 'criado_em': processo_associacao_123456_2019.criado_em.isoformat("T"), + 'alterado_em': processo_associacao_123456_2019.alterado_em.isoformat("T"), + 'numero_processo': processo_associacao_123456_2019.numero_processo, + 'ano': processo_associacao_123456_2019.ano, + }, + ] + + assert response.status_code == status.HTTP_200_OK + assert result == esperado diff --git a/sme_ptrf_apps/core/tests/tests_api_associacoes/test_retrieve_associacao.py b/sme_ptrf_apps/core/tests/tests_api_associacoes/test_retrieve_associacao.py index b8b6688df..833f73505 100644 --- a/sme_ptrf_apps/core/tests/tests_api_associacoes/test_retrieve_associacao.py +++ b/sme_ptrf_apps/core/tests/tests_api_associacoes/test_retrieve_associacao.py @@ -1,19 +1,93 @@ import json import pytest +from model_bakery import baker from rest_framework import status -from ...models import Associacao -from ...api.serializers import AssociacaoSerializer +from ....core.choices import MembroEnum, RepresentacaoCargo pytestmark = pytest.mark.django_db +@pytest.fixture +def presidente_associacao(associacao): + return baker.make( + 'MembroAssociacao', + nome='Arthur Nobrega', + associacao=associacao, + cargo_associacao=MembroEnum.PRESIDENTE_DIRETORIA_EXECUTIVA.name, + cargo_educacao='Coordenador', + representacao=RepresentacaoCargo.SERVIDOR.value, + codigo_identificacao='567432', + email='arthur@gmail.com' + ) -def test_api_retrieve_associacao(client, associacao): +@pytest.fixture +def presidente_conselho_fiscal(associacao): + return baker.make( + 'MembroAssociacao', + nome='José Firmino', + associacao=associacao, + cargo_associacao=MembroEnum.PRESIDENTE_CONSELHO_FISCAL.name, + cargo_educacao='Coordenador', + representacao=RepresentacaoCargo.SERVIDOR.value, + codigo_identificacao='567433', + email='jose@gmail.com' + ) + + + +def test_api_retrieve_associacao(client, associacao, presidente_associacao, presidente_conselho_fiscal): response = client.get(f'/api/associacoes/{associacao.uuid}/', content_type='application/json') result = json.loads(response.content) - result_esperado = AssociacaoSerializer(Associacao.objects.get(uuid=associacao.uuid), many=False).data + result_esperado = { + 'uuid': f'{associacao.uuid}', + 'ccm': f'{associacao.ccm}', + 'cnpj': f'{associacao.cnpj}', + 'email': f'{associacao.email}', + 'nome': f'{associacao.nome}', + 'status_regularidade': f'{associacao.status_regularidade}', + 'presidente_associacao': { + 'nome': presidente_associacao.nome, + 'email': presidente_associacao.email, + 'cargo_educacao': presidente_associacao.cargo_educacao + }, + 'presidente_conselho_fiscal': { + 'nome': presidente_conselho_fiscal.nome, + 'email': presidente_conselho_fiscal.email, + 'cargo_educacao': presidente_conselho_fiscal.cargo_educacao + }, + 'processo_regularidade': '123456', + 'unidade': { + 'codigo_eol': f'{associacao.unidade.codigo_eol}', + 'dre': { + 'codigo_eol': f'{associacao.unidade.dre.codigo_eol}', + 'nome': f'{associacao.unidade.dre.nome}', + 'sigla': f'{associacao.unidade.dre.sigla}', + 'tipo_unidade': 'DRE', + 'uuid': f'{associacao.unidade.dre.uuid}' + }, + 'dre_cnpj': f'{associacao.unidade.dre_cnpj}', + 'dre_designacao_ano': f'{associacao.unidade.dre_designacao_ano}', + 'dre_designacao_portaria': f'{associacao.unidade.dre_designacao_portaria}', + 'dre_diretor_regional_nome': f'{associacao.unidade.dre_diretor_regional_nome}', + 'dre_diretor_regional_rf': f'{associacao.unidade.dre_diretor_regional_rf}', + 'nome': f'{associacao.unidade.nome}', + 'sigla': f'{associacao.unidade.sigla}', + 'tipo_unidade': f'{associacao.unidade.tipo_unidade}', + 'uuid': f'{associacao.unidade.uuid}', + 'email': f'{associacao.unidade.email}', + 'qtd_alunos': associacao.unidade.qtd_alunos, + 'tipo_logradouro': f'{associacao.unidade.tipo_logradouro}', + 'logradouro': f'{associacao.unidade.logradouro}', + 'numero': f'{associacao.unidade.numero}', + 'complemento': f'{associacao.unidade.complemento}', + 'bairro': f'{associacao.unidade.bairro}', + 'cep': f'{associacao.unidade.cep}', + 'telefone': f'{associacao.unidade.telefone}', + 'diretor_nome': f'{associacao.unidade.diretor_nome}', + }, + } assert response.status_code == status.HTTP_200_OK assert result == result_esperado diff --git a/sme_ptrf_apps/core/tests/tests_api_associacoes/test_update_associacao.py b/sme_ptrf_apps/core/tests/tests_api_associacoes/test_update_associacao.py index f453d2219..228d3550f 100644 --- a/sme_ptrf_apps/core/tests/tests_api_associacoes/test_update_associacao.py +++ b/sme_ptrf_apps/core/tests/tests_api_associacoes/test_update_associacao.py @@ -4,7 +4,6 @@ from rest_framework import status from ...models import Associacao -from ...api.serializers.associacao_serializer import AssociacaoCreateSerializer pytestmark = pytest.mark.django_db @@ -12,7 +11,7 @@ def test_api_update_associacao(client, associacao): payload = { "nome": "Nome alterado", - "presidente_associacao_nome": "Presidente alterado" + "processo_regularidade": "123456" } response = client.put(f'/api/associacoes/{associacao.uuid}/', data=json.dumps(payload), content_type='application/json') @@ -21,4 +20,5 @@ def test_api_update_associacao(client, associacao): assert response.status_code == status.HTTP_200_OK assert registro_alterado.nome == 'Nome alterado' - assert registro_alterado.presidente_associacao_nome == 'Presidente alterado' + assert registro_alterado.processo_regularidade == '123456' + diff --git a/sme_ptrf_apps/core/tests/tests_api_prestacoes_contas/test_get_despesas_prestacao_conta.py b/sme_ptrf_apps/core/tests/tests_api_prestacoes_contas/test_get_despesas_prestacao_conta.py index 39e1d59e9..a37ea87a2 100644 --- a/sme_ptrf_apps/core/tests/tests_api_prestacoes_contas/test_get_despesas_prestacao_conta.py +++ b/sme_ptrf_apps/core/tests/tests_api_prestacoes_contas/test_get_despesas_prestacao_conta.py @@ -29,27 +29,17 @@ def test_api_get_despesas_conferidas_prestacao_conta(client, result = json.loads(response.content) - resultado_esperado = [None, None] - - result_esperado = RateioDespesaListaSerializer(rateio_despesa_2020_role_conferido, many=False).data - # Converto os campos não string em strings para que a comparação funcione - result_esperado['data_documento'] = f'{result_esperado["data_documento"]}' - result_esperado['data_transacao'] = f'{result_esperado["data_transacao"]}' - result_esperado['despesa'] = f'{result_esperado["despesa"]}' - resultado_esperado[0] = result_esperado - - result_esperado = RateioDespesaListaSerializer(rateio_despesa_2019_role_conferido_na_prestacao, many=False).data - # Converto os campos não string em strings para que a comparação funcione - result_esperado['data_documento'] = f'{result_esperado["data_documento"]}' - result_esperado['data_transacao'] = f'{result_esperado["data_transacao"]}' - result_esperado['despesa'] = f'{result_esperado["despesa"]}' - resultado_esperado[1] = result_esperado - + despesas_retornadas = set() + for despesa in result: + despesas_retornadas.add(despesa['uuid']) + despesas_esperadas = set() + despesas_esperadas.add(f'{rateio_despesa_2020_role_conferido.uuid}') + despesas_esperadas.add(f'{rateio_despesa_2019_role_conferido_na_prestacao.uuid}') assert response.status_code == status.HTTP_200_OK - assert result == resultado_esperado, "Não retornou a lista de despesas esperada." + assert despesas_retornadas == despesas_esperadas, "Não retornou a lista de despesas esperada." def test_api_get_despesas_nao_conferidas_prestacao_conta(client, @@ -72,17 +62,17 @@ def test_api_get_despesas_nao_conferidas_prestacao_conta(client, result = json.loads(response.content) - resultado_esperado = [] - result_esperado = RateioDespesaListaSerializer(rateio_despesa_2020_role_nao_conferido, many=False).data - # Converto os campos não string em strings para que a comparação funcione - result_esperado['data_documento'] = f'{result_esperado["data_documento"]}' - result_esperado['data_transacao'] = f'{result_esperado["data_transacao"]}' - result_esperado['despesa'] = f'{result_esperado["despesa"]}' + despesas_retornadas = set() + for despesa in result: + despesas_retornadas.add(despesa['uuid']) + + + despesas_esperadas = set() + despesas_esperadas.add(f'{rateio_despesa_2020_role_nao_conferido.uuid}') - resultado_esperado.append(result_esperado) assert response.status_code == status.HTTP_200_OK - assert result == resultado_esperado, "Não retornou a lista de despesas esperada." + assert despesas_retornadas == despesas_esperadas, "Não retornou a lista de despesas esperada." def test_api_get_despesas_nao_conferidas_prestacao_traz_periodos_anteriores(client, diff --git a/sme_ptrf_apps/core/tests/tests_api_processos_associacoes/__init__.py b/sme_ptrf_apps/core/tests/tests_api_processos_associacoes/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/sme_ptrf_apps/core/tests/tests_api_processos_associacoes/test_processo_associacao_create.py b/sme_ptrf_apps/core/tests/tests_api_processos_associacoes/test_processo_associacao_create.py new file mode 100644 index 000000000..9d972f7d2 --- /dev/null +++ b/sme_ptrf_apps/core/tests/tests_api_processos_associacoes/test_processo_associacao_create.py @@ -0,0 +1,28 @@ +import json + +import pytest +from rest_framework import status + +from sme_ptrf_apps.core.models import ProcessoAssociacao + +pytestmark = pytest.mark.django_db + +@pytest.fixture +def payload_processo_associacao(associacao): + payload = { + 'associacao': str(associacao.uuid), + 'numero_processo': "271170", + 'ano': '2020' + } + return payload + + +def test_create_processo_associacao_servidor(jwt_authenticated_client, associacao, payload_processo_associacao): + response = jwt_authenticated_client.post( + '/api/processos-associacao/', data=json.dumps(payload_processo_associacao), content_type='application/json') + + assert response.status_code == status.HTTP_201_CREATED + + result = json.loads(response.content) + + assert ProcessoAssociacao.objects.filter(uuid=result['uuid']).exists() diff --git a/sme_ptrf_apps/core/tests/tests_api_processos_associacoes/test_processo_associacao_delete.py b/sme_ptrf_apps/core/tests/tests_api_processos_associacoes/test_processo_associacao_delete.py new file mode 100644 index 000000000..f6c06dadb --- /dev/null +++ b/sme_ptrf_apps/core/tests/tests_api_processos_associacoes/test_processo_associacao_delete.py @@ -0,0 +1,16 @@ +import pytest +from rest_framework import status + +from sme_ptrf_apps.core.models import ProcessoAssociacao + +pytestmark = pytest.mark.django_db + +def test_delete_processo_associacao(jwt_authenticated_client, processo_associacao_123456_2019): + assert ProcessoAssociacao.objects.filter(uuid=processo_associacao_123456_2019.uuid).exists() + + response = jwt_authenticated_client.delete( + f'/api/processos-associacao/{processo_associacao_123456_2019.uuid}/', content_type='application/json') + + assert response.status_code == status.HTTP_204_NO_CONTENT + + assert not ProcessoAssociacao.objects.filter(uuid=processo_associacao_123456_2019.uuid).exists() diff --git a/sme_ptrf_apps/core/tests/tests_api_processos_associacoes/test_processo_associacao_retrieve.py b/sme_ptrf_apps/core/tests/tests_api_processos_associacoes/test_processo_associacao_retrieve.py new file mode 100644 index 000000000..d2a47e95e --- /dev/null +++ b/sme_ptrf_apps/core/tests/tests_api_processos_associacoes/test_processo_associacao_retrieve.py @@ -0,0 +1,30 @@ +import json + +import pytest +from rest_framework import status + +pytestmark = pytest.mark.django_db + +def test_retrieve_processo_associacao( + jwt_authenticated_client, + processo_associacao_123456_2019): + + response = jwt_authenticated_client.get( + f'/api/processos-associacao/{processo_associacao_123456_2019.uuid}/', content_type='application/json') + result = json.loads(response.content) + esperado = { + 'uuid': f'{processo_associacao_123456_2019.uuid}', + 'associacao': + { + 'id': processo_associacao_123456_2019.associacao.id, + 'nome': processo_associacao_123456_2019.associacao.nome + }, + 'criado_em': processo_associacao_123456_2019.criado_em.isoformat("T"), + 'alterado_em': processo_associacao_123456_2019.alterado_em.isoformat("T"), + 'numero_processo': processo_associacao_123456_2019.numero_processo, + 'ano': processo_associacao_123456_2019.ano, + } + + assert response.status_code == status.HTTP_200_OK + assert result == esperado + diff --git a/sme_ptrf_apps/core/tests/tests_api_processos_associacoes/test_processo_associacao_update.py b/sme_ptrf_apps/core/tests/tests_api_processos_associacoes/test_processo_associacao_update.py new file mode 100644 index 000000000..0aacb3b9b --- /dev/null +++ b/sme_ptrf_apps/core/tests/tests_api_processos_associacoes/test_processo_associacao_update.py @@ -0,0 +1,38 @@ +import json + +import pytest +from rest_framework import status + +from sme_ptrf_apps.core.models import ProcessoAssociacao + +pytestmark = pytest.mark.django_db + + +@pytest.fixture +def payload_processo_associacao(associacao): + payload = { + 'associacao': str(associacao.uuid), + 'numero_processo': "271170", + 'ano': '2020' + } + return payload + + +def test_update_processo_associacao(jwt_authenticated_client, associacao, processo_associacao_123456_2019, + payload_processo_associacao): + numero_processo_novo = "190889" + payload_processo_associacao['numero_processo'] = numero_processo_novo + response = jwt_authenticated_client.put( + f'/api/processos-associacao/{processo_associacao_123456_2019.uuid}/', + data=json.dumps(payload_processo_associacao), + content_type='application/json') + + assert response.status_code == status.HTTP_200_OK + + result = json.loads(response.content) + + assert ProcessoAssociacao.objects.filter(uuid=result['uuid']).exists() + + processo = ProcessoAssociacao.objects.filter(uuid=result['uuid']).get() + + assert processo.numero_processo == numero_processo_novo diff --git a/sme_ptrf_apps/core/tests/tests_api_unidades/test_list_unidades.py b/sme_ptrf_apps/core/tests/tests_api_unidades/test_list_unidades.py new file mode 100644 index 000000000..e1910b949 --- /dev/null +++ b/sme_ptrf_apps/core/tests/tests_api_unidades/test_list_unidades.py @@ -0,0 +1,110 @@ +import json + +import pytest +from model_bakery import baker +from rest_framework import status + +pytestmark = pytest.mark.django_db + + +@pytest.fixture +def dre_01(): + return baker.make( + 'Unidade', + uuid='f92d2caf-d71f-4ed0-87b2-6d326fb648a7', + codigo_eol='108500', + tipo_unidade='DRE', + nome='GUAIANASES', + sigla='G' + ) + +@pytest.fixture +def unidade_paulo_camilhier_florencano_dre_1(dre_01): + return baker.make( + 'Unidade', + uuid="ae06f0f7-0aca-41d6-94c3-dea20558627d", + codigo_eol="000086", + tipo_unidade="EMEI", + nome="PAULO CAMILHIER FLORENCANO", + sigla="", + dre=dre_01, + email="", + telefone="", + tipo_logradouro="", + logradouro="", + numero="", + complemento="", + bairro="", + cep="", + qtd_alunos=0, + diretor_nome="", + dre_cnpj="", + dre_diretor_regional_rf="", + dre_diretor_regional_nome="", + dre_designacao_portaria="", + dre_designacao_ano="" + ) + + +def test_api_list_unidades_todas(client, unidade_paulo_camilhier_florencano_dre_1, dre_01): + response = client.get(f'/api/unidades/', content_type='application/json') + result = json.loads(response.content) + + result_esperado = [ + { + "uuid": f'{dre_01.uuid}', + "codigo_eol": f'{dre_01.codigo_eol}', + "tipo_unidade": f'{dre_01.tipo_unidade}', + "nome": f'{dre_01.nome}', + "sigla": f'{dre_01.sigla}', + "dre": None, + "email": f'{dre_01.email}', + "telefone": f'{dre_01.telefone}', + "tipo_logradouro": f'{dre_01.tipo_logradouro}', + "logradouro": f'{dre_01.logradouro}', + "numero": f'{dre_01.numero}', + "complemento": f'{dre_01.complemento}', + "bairro": f'{dre_01.bairro}', + "cep": f'{dre_01.cep}', + "qtd_alunos": 0, + "diretor_nome": f'{dre_01.diretor_nome}', + "dre_cnpj": f'{dre_01.dre_cnpj}', + "dre_diretor_regional_rf": f'{dre_01.dre_diretor_regional_rf}', + "dre_diretor_regional_nome": f'{dre_01.dre_diretor_regional_nome}', + "dre_designacao_portaria": f'{dre_01.dre_designacao_portaria}', + "dre_designacao_ano": f'{dre_01.dre_designacao_ano}', + }, + { + "uuid": f'{unidade_paulo_camilhier_florencano_dre_1.uuid}', + "codigo_eol": f'{unidade_paulo_camilhier_florencano_dre_1.codigo_eol}', + "tipo_unidade": f'{unidade_paulo_camilhier_florencano_dre_1.tipo_unidade}', + "nome": f'{unidade_paulo_camilhier_florencano_dre_1.nome}', + "sigla": f'{unidade_paulo_camilhier_florencano_dre_1.sigla}', + "dre": { + 'uuid': f'{unidade_paulo_camilhier_florencano_dre_1.dre.uuid}', + 'codigo_eol': f'{unidade_paulo_camilhier_florencano_dre_1.dre.codigo_eol}', + 'tipo_unidade': f'{unidade_paulo_camilhier_florencano_dre_1.dre.tipo_unidade}', + 'nome': f'{unidade_paulo_camilhier_florencano_dre_1.dre.nome}', + 'sigla': f'{unidade_paulo_camilhier_florencano_dre_1.dre.sigla}', + }, + "email": f'{unidade_paulo_camilhier_florencano_dre_1.email}', + "telefone": f'{unidade_paulo_camilhier_florencano_dre_1.telefone}', + "tipo_logradouro": f'{unidade_paulo_camilhier_florencano_dre_1.tipo_logradouro}', + "logradouro": f'{unidade_paulo_camilhier_florencano_dre_1.logradouro}', + "numero": f'{unidade_paulo_camilhier_florencano_dre_1.numero}', + "complemento": f'{unidade_paulo_camilhier_florencano_dre_1.complemento}', + "bairro": f'{unidade_paulo_camilhier_florencano_dre_1.bairro}', + "cep": f'{unidade_paulo_camilhier_florencano_dre_1.cep}', + "qtd_alunos": 0, + "diretor_nome": f'{unidade_paulo_camilhier_florencano_dre_1.diretor_nome}', + "dre_cnpj": f'{unidade_paulo_camilhier_florencano_dre_1.dre_cnpj}', + "dre_diretor_regional_rf": f'{unidade_paulo_camilhier_florencano_dre_1.dre_diretor_regional_rf}', + "dre_diretor_regional_nome": f'{unidade_paulo_camilhier_florencano_dre_1.dre_diretor_regional_nome}', + "dre_designacao_portaria": f'{unidade_paulo_camilhier_florencano_dre_1.dre_designacao_portaria}', + "dre_designacao_ano": f'{unidade_paulo_camilhier_florencano_dre_1.dre_designacao_ano}', + } + + ] + + assert response.status_code == status.HTTP_200_OK + assert result == result_esperado diff --git a/sme_ptrf_apps/core/tests/tests_api_unidades/test_retrieve_unidade.py b/sme_ptrf_apps/core/tests/tests_api_unidades/test_retrieve_unidade.py new file mode 100644 index 000000000..93c3d03c2 --- /dev/null +++ b/sme_ptrf_apps/core/tests/tests_api_unidades/test_retrieve_unidade.py @@ -0,0 +1,85 @@ +import json + +import pytest +from model_bakery import baker +from rest_framework import status + +pytestmark = pytest.mark.django_db + + +@pytest.fixture +def dre_unidade_educacional(): + return baker.make( + 'Unidade', + uuid='f92d2caf-d71f-4ed0-87b2-6d326fb648a7', + codigo_eol='108500', + tipo_unidade='DRE', + nome='GUAIANASES', + sigla='G' + ) + + +@pytest.fixture +def unidade_educacional(dre_unidade_educacional): + return baker.make( + 'Unidade', + uuid="c5ae0767-9626-4801-9ed9-56808858b6d8", + nome='Escola Teste', + tipo_unidade='CEU', + codigo_eol='123456', + dre=dre_unidade_educacional, + sigla='ET', + cep='5868120', + tipo_logradouro='Travessa', + logradouro='dos Testes', + bairro='COHAB INSTITUTO ADVENTISTA', + numero='200', + complemento='fundos', + telefone='58212627', + email='emefjopfilho@sme.prefeitura.sp.gov.br', + qtd_alunos=0, + diretor_nome='Pedro Amaro', + dre_cnpj='63.058.286/0001-86', + dre_diretor_regional_rf='1234567', + dre_diretor_regional_nome='Anthony Edward Stark', + dre_designacao_portaria='Portaria nº 0.000', + dre_designacao_ano='2017', + ) + + +def test_api_retrieve_unidade(client, unidade_educacional): + response = client.get(f'/api/unidades/{unidade_educacional.uuid}/', content_type='application/json') + result = json.loads(response.content) + + result_esperado = { + "uuid": f'{unidade_educacional.uuid}', + "codigo_eol": f'{unidade_educacional.codigo_eol}', + "tipo_unidade": f'{unidade_educacional.tipo_unidade}', + "nome": f'{unidade_educacional.nome}', + "sigla": f'{unidade_educacional.sigla}', + "dre": { + 'uuid': f'{unidade_educacional.dre.uuid}', + 'codigo_eol': f'{unidade_educacional.dre.codigo_eol}', + 'tipo_unidade': f'{unidade_educacional.dre.tipo_unidade}', + 'nome': f'{unidade_educacional.dre.nome}', + 'sigla': f'{unidade_educacional.dre.sigla}', + }, + "email": f'{unidade_educacional.email}', + "telefone": f'{unidade_educacional.telefone}', + "tipo_logradouro": f'{unidade_educacional.tipo_logradouro}', + "logradouro": f'{unidade_educacional.logradouro}', + "numero": f'{unidade_educacional.numero}', + "complemento": f'{unidade_educacional.complemento}', + "bairro": f'{unidade_educacional.bairro}', + "cep": f'{unidade_educacional.cep}', + "qtd_alunos": 0, + "diretor_nome": f'{unidade_educacional.diretor_nome}', + "dre_cnpj": f'{unidade_educacional.dre_cnpj}', + "dre_diretor_regional_rf": f'{unidade_educacional.dre_diretor_regional_rf}', + "dre_diretor_regional_nome": f'{unidade_educacional.dre_diretor_regional_nome}', + "dre_designacao_portaria": f'{unidade_educacional.dre_designacao_portaria}', + "dre_designacao_ano": f'{unidade_educacional.dre_designacao_ano}', + } + + assert response.status_code == status.HTTP_200_OK + assert result == result_esperado diff --git a/sme_ptrf_apps/core/tests/tests_associacao/test_associacao_model.py b/sme_ptrf_apps/core/tests/tests_associacao/test_associacao_model.py index d715e1148..7ea48ed2c 100644 --- a/sme_ptrf_apps/core/tests/tests_associacao/test_associacao_model.py +++ b/sme_ptrf_apps/core/tests/tests_associacao/test_associacao_model.py @@ -16,14 +16,12 @@ def test_instance_model(associacao): assert model.uuid assert model.id assert model.cnpj - assert model.presidente_associacao_nome - assert model.presidente_associacao_rf - assert model.presidente_conselho_fiscal_nome - assert model.presidente_conselho_fiscal_rf assert isinstance(model.unidade, Unidade) assert isinstance(model.periodo_inicial, Periodo) assert model.ccm assert model.email + assert model.status_regularidade + assert model.processo_regularidade is not None def test_srt_model(associacao): diff --git a/sme_ptrf_apps/core/tests/tests_associacao/test_associacao_serializer.py b/sme_ptrf_apps/core/tests/tests_associacao/test_associacao_serializer.py index e29cd9b6b..75dbe1a2d 100644 --- a/sme_ptrf_apps/core/tests/tests_associacao/test_associacao_serializer.py +++ b/sme_ptrf_apps/core/tests/tests_associacao/test_associacao_serializer.py @@ -1,7 +1,8 @@ import pytest from ...api.serializers.associacao_serializer import (AssociacaoSerializer, AssociacaoLookupSerializer, - AssociacaoCreateSerializer) + AssociacaoCreateSerializer, AssociacaoListSerializer, + AssociacaoCompletoSerializer) pytestmark = pytest.mark.django_db @@ -14,10 +15,8 @@ def test_serializer(associacao): assert serializer.data['nome'] assert serializer.data['unidade'] assert serializer.data['cnpj'] - assert serializer.data['presidente_associacao_nome'] - assert serializer.data['presidente_associacao_rf'] - assert serializer.data['presidente_conselho_fiscal_nome'] - assert serializer.data['presidente_conselho_fiscal_rf'] + assert serializer.data['ccm'] + assert serializer.data['processo_regularidade'] def test_lookup_serializer(associacao): @@ -36,7 +35,28 @@ def test_create_serializer(associacao): assert serializer.data['nome'] assert serializer.data['unidade'] assert serializer.data['cnpj'] - assert serializer.data['presidente_associacao_nome'] - assert serializer.data['presidente_associacao_rf'] - assert serializer.data['presidente_conselho_fiscal_nome'] - assert serializer.data['presidente_conselho_fiscal_rf'] + assert serializer.data['processo_regularidade'] + + +def test_list_serializer(associacao): + serializer = AssociacaoListSerializer(associacao) + + assert serializer.data is not None + assert serializer.data['uuid'] + assert serializer.data['nome'] + assert serializer.data['unidade'] + assert serializer.data['status_regularidade'] + + +def test_completo_serializer(associacao, membro_associacao_presidente_associacao, membro_associacao_presidente_conselho): + serializer = AssociacaoCompletoSerializer(associacao) + + assert serializer.data is not None + assert serializer.data['uuid'] + assert serializer.data['nome'] + assert serializer.data['unidade'] + assert serializer.data['cnpj'] + assert serializer.data['ccm'] + assert serializer.data['presidente_associacao'] + assert serializer.data['presidente_conselho_fiscal'] + assert serializer.data['processo_regularidade'] diff --git a/sme_ptrf_apps/core/tests/tests_membro_associacao/test_membro_associacao_api.py b/sme_ptrf_apps/core/tests/tests_membro_associacao/test_membro_associacao_api.py index 26c92d434..cc59549a2 100644 --- a/sme_ptrf_apps/core/tests/tests_membro_associacao/test_membro_associacao_api.py +++ b/sme_ptrf_apps/core/tests/tests_membro_associacao/test_membro_associacao_api.py @@ -13,7 +13,7 @@ def test_get_membros_associacoes( associacao, membro_associacao): - response = jwt_authenticated_client.get('/api/membros-associacao/', content_type='application/json') + response = jwt_authenticated_client.get(f'/api/membros-associacao/?associacao_uuid={associacao.uuid}', content_type='application/json') result = json.loads(response.content) esperado = [ @@ -46,7 +46,7 @@ def test_get_membro_associacao( membro_associacao): response = jwt_authenticated_client.get( - f'/api/membros-associacao/{membro_associacao.uuid}/', content_type='application/json') + f'/api/membros-associacao/{membro_associacao.uuid}/?associacao_uuid={associacao.uuid}', content_type='application/json') result = json.loads(response.content) esperado = { 'id': membro_associacao.id, @@ -107,7 +107,7 @@ def test_atualizar_membro_associacao_servidor(jwt_authenticated_client, associac nome_novo = "Gabriel Nóbrega" payload_membro_servidor['nome'] = nome_novo response = jwt_authenticated_client.put( - f'/api/membros-associacao/{membro_associacao.uuid}/', data=json.dumps(payload_membro_servidor), content_type='application/json') + f'/api/membros-associacao/{membro_associacao.uuid}/?associacao_uuid={associacao.uuid}', data=json.dumps(payload_membro_servidor), content_type='application/json') assert response.status_code == status.HTTP_200_OK @@ -120,11 +120,11 @@ def test_atualizar_membro_associacao_servidor(jwt_authenticated_client, associac assert membro.nome == nome_novo -def test_deletar_membro_associacao(jwt_authenticated_client, membro_associacao): +def test_deletar_membro_associacao(jwt_authenticated_client, associacao, membro_associacao): assert MembroAssociacao.objects.filter(uuid=membro_associacao.uuid).exists() response = jwt_authenticated_client.delete( - f'/api/membros-associacao/{membro_associacao.uuid}/', content_type='application/json') + f'/api/membros-associacao/{membro_associacao.uuid}/?associacao_uuid={associacao.uuid}', content_type='application/json') assert response.status_code == status.HTTP_204_NO_CONTENT diff --git a/sme_ptrf_apps/core/tests/tests_parametros/test_model.py b/sme_ptrf_apps/core/tests/tests_parametros/test_model.py index c57459e39..d2373f11d 100644 --- a/sme_ptrf_apps/core/tests/tests_parametros/test_model.py +++ b/sme_ptrf_apps/core/tests/tests_parametros/test_model.py @@ -8,3 +8,5 @@ def test_parametros_model(parametros): assert isinstance(parametros, Parametros) assert parametros.permite_saldo_conta_negativo + assert parametros.fique_de_olho == '' + assert parametros.tempo_notificar_nao_demonstrados == 0 diff --git a/sme_ptrf_apps/core/tests/tests_processo_associacao/__init__.py b/sme_ptrf_apps/core/tests/tests_processo_associacao/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/sme_ptrf_apps/core/tests/tests_processo_associacao/test_processo_associacao_model.py b/sme_ptrf_apps/core/tests/tests_processo_associacao/test_processo_associacao_model.py new file mode 100644 index 000000000..68b96143f --- /dev/null +++ b/sme_ptrf_apps/core/tests/tests_processo_associacao/test_processo_associacao_model.py @@ -0,0 +1,19 @@ +import pytest + +from ...models import ProcessoAssociacao, Associacao + +pytestmark = pytest.mark.django_db + + +def test_instance_model(processo_associacao_123456_2019): + model = processo_associacao_123456_2019 + assert isinstance(model, ProcessoAssociacao) + assert isinstance(model.associacao, Associacao) + assert model.numero_processo + assert model.ano + assert model.uuid + + +def test_admin(): + from django.contrib import admin + assert admin.site._registry[ProcessoAssociacao] diff --git a/sme_ptrf_apps/core/tests/tests_processo_associacao/test_processo_associacao_serializer.py b/sme_ptrf_apps/core/tests/tests_processo_associacao/test_processo_associacao_serializer.py new file mode 100644 index 000000000..9d258a70b --- /dev/null +++ b/sme_ptrf_apps/core/tests/tests_processo_associacao/test_processo_associacao_serializer.py @@ -0,0 +1,27 @@ +import pytest + +from sme_ptrf_apps.core.api.serializers import ProcessoAssociacaoRetrieveSerializer, ProcessoAssociacaoCreateSerializer + +pytestmark = pytest.mark.django_db + + +def test_processo_associacao_list_serializer(processo_associacao_123456_2019): + serializer = ProcessoAssociacaoRetrieveSerializer(processo_associacao_123456_2019) + assert serializer.data is not None + assert serializer.data['uuid'] + assert serializer.data['associacao'] + assert serializer.data['numero_processo'] + assert serializer.data['ano'] + assert serializer.data['criado_em'] + assert serializer.data['alterado_em'] + + +def test_processo_associacao_create_serializer(processo_associacao_123456_2019): + serializer = ProcessoAssociacaoCreateSerializer(processo_associacao_123456_2019) + assert serializer.data is not None + assert serializer.data['uuid'] + assert serializer.data['associacao'] + assert serializer.data['numero_processo'] + assert serializer.data['ano'] + + diff --git a/sme_ptrf_apps/core/tests/tests_processo_associacao/test_processos_associacao_viewset.py b/sme_ptrf_apps/core/tests/tests_processo_associacao/test_processos_associacao_viewset.py new file mode 100644 index 000000000..47f610130 --- /dev/null +++ b/sme_ptrf_apps/core/tests/tests_processo_associacao/test_processos_associacao_viewset.py @@ -0,0 +1,17 @@ +import pytest +from rest_framework import status +from rest_framework.test import APIRequestFactory +from rest_framework.test import force_authenticate + +from ...api.views.processos_associacao_viewset import ProcessosAssociacaoViewSet + +pytestmark = pytest.mark.django_db + + +def test_view_set(processo_associacao_123456_2019, fake_user): + request = APIRequestFactory().get("") + detalhe = ProcessosAssociacaoViewSet.as_view({'get': 'retrieve'}) + force_authenticate(request, user=fake_user) + response = detalhe(request, uuid=processo_associacao_123456_2019.uuid) + + assert response.status_code == status.HTTP_200_OK diff --git a/sme_ptrf_apps/core/tests/tests_unidade/tests_unidade_model.py b/sme_ptrf_apps/core/tests/tests_unidade/tests_unidade_model.py index 7c462ddfc..ff32d0031 100644 --- a/sme_ptrf_apps/core/tests/tests_unidade/tests_unidade_model.py +++ b/sme_ptrf_apps/core/tests/tests_unidade/tests_unidade_model.py @@ -1,10 +1,9 @@ import pytest from django.contrib import admin +from ...admin import UnidadeAdmin from ...models import Unidade -from ..admin import UnidadeAdmin - pytestmark = pytest.mark.django_db @@ -16,6 +15,21 @@ def test_instance_model(unidade): assert model.tipo_unidade assert model.codigo_eol assert model.sigla + assert model.cep + assert model.tipo_logradouro + assert model.logradouro + assert model.bairro + assert model.numero + assert model.complemento + assert model.telefone + assert model.email + assert model.qtd_alunos + assert model.diretor_nome + assert model.dre_cnpj + assert model.dre_diretor_regional_rf + assert model.dre_diretor_regional_nome + assert model.dre_designacao_portaria + assert model.dre_designacao_ano def test_srt_model(unidade): diff --git a/sme_ptrf_apps/core/tests/tests_unidade/tests_unidade_serializer.py b/sme_ptrf_apps/core/tests/tests_unidade/tests_unidade_serializer.py index 3922aa034..522826d17 100644 --- a/sme_ptrf_apps/core/tests/tests_unidade/tests_unidade_serializer.py +++ b/sme_ptrf_apps/core/tests/tests_unidade/tests_unidade_serializer.py @@ -1,6 +1,7 @@ import pytest -from ...api.serializers.unidade_serializer import UnidadeLookUpSerializer, UnidadeSerializer +from ...api.serializers.unidade_serializer import (UnidadeLookUpSerializer, UnidadeSerializer, UnidadeInfoAtaSerializer, + UnidadeListSerializer) pytestmark = pytest.mark.django_db @@ -20,3 +21,33 @@ def test_unidade_serializer(unidade): assert 'id' not in unidade_serializer.data assert unidade_serializer.data['dre'] assert 'sigla' in unidade_serializer.data + assert 'cep' in unidade_serializer.data + assert 'tipo_logradouro' in unidade_serializer.data + assert 'logradouro' in unidade_serializer.data + assert 'bairro' in unidade_serializer.data + assert 'numero' in unidade_serializer.data + assert 'complemento' in unidade_serializer.data + assert 'telefone' in unidade_serializer.data + assert 'email' in unidade_serializer.data + assert 'qtd_alunos' in unidade_serializer.data + assert 'diretor_nome' in unidade_serializer.data + assert 'dre_cnpj' in unidade_serializer.data + assert 'dre_diretor_regional_rf' in unidade_serializer.data + assert 'dre_diretor_regional_nome' in unidade_serializer.data + assert 'dre_designacao_portaria' in unidade_serializer.data + assert 'dre_designacao_ano' in unidade_serializer.data + + +def test_unidade_info_ata_serializer(unidade): + unidade_serializer = UnidadeInfoAtaSerializer(unidade) + assert unidade_serializer.data is not None + assert 'tipo_unidade' in unidade_serializer.data + assert 'nome' in unidade_serializer.data + + +def test_unidade_list_serializer(unidade): + unidade_serializer = UnidadeListSerializer(unidade) + assert unidade_serializer.data is not None + assert 'uuid' in unidade_serializer.data + assert 'codigo_eol' in unidade_serializer.data + assert 'nome_com_tipo' in unidade_serializer.data diff --git a/sme_ptrf_apps/core/tests/tests_unidade/tests_unidade_viewset.py b/sme_ptrf_apps/core/tests/tests_unidade/tests_unidade_viewset.py new file mode 100644 index 000000000..ce04cae3b --- /dev/null +++ b/sme_ptrf_apps/core/tests/tests_unidade/tests_unidade_viewset.py @@ -0,0 +1,17 @@ +import pytest +from rest_framework import status +from rest_framework.test import APIRequestFactory +from rest_framework.test import force_authenticate + +from ...api.views.unidades_viewset import UnidadesViewSet + +pytestmark = pytest.mark.django_db + + +def test_view_set(unidade, fake_user): + request = APIRequestFactory().get("") + detalhe = UnidadesViewSet.as_view({'get':'retrieve'}) + force_authenticate(request, user=fake_user) + response = detalhe(request, uuid=unidade.uuid) + + assert response.status_code == status.HTTP_200_OK diff --git a/sme_ptrf_apps/despesas/api/serializers/rateio_despesa_serializer.py b/sme_ptrf_apps/despesas/api/serializers/rateio_despesa_serializer.py index 4d8a614a5..55da2c159 100644 --- a/sme_ptrf_apps/despesas/api/serializers/rateio_despesa_serializer.py +++ b/sme_ptrf_apps/despesas/api/serializers/rateio_despesa_serializer.py @@ -1,16 +1,16 @@ from rest_framework import serializers -from ....core.api.serializers import TagLookupSerializer -from ....core.api.serializers.acao_associacao_serializer import AcaoAssociacaoLookUpSerializer, AcaoAssociacaoSerializer -from ....core.api.serializers.associacao_serializer import AssociacaoSerializer -from ....core.api.serializers.conta_associacao_serializer import ContaAssociacaoSerializer -from ....core.models import AcaoAssociacao, Associacao, ContaAssociacao, Tag -from ...models import Despesa, RateioDespesa from .especificacao_material_servico_serializer import ( EspecificacaoMaterialServicoLookUpSerializer, EspecificacaoMaterialServicoSerializer, ) from .tipo_custeio_serializer import TipoCusteioSerializer +from ...models import Despesa, RateioDespesa +from ....core.api.serializers import TagLookupSerializer +from ....core.api.serializers.acao_associacao_serializer import AcaoAssociacaoLookUpSerializer, AcaoAssociacaoSerializer +from ....core.api.serializers.associacao_serializer import AssociacaoSerializer +from ....core.api.serializers.conta_associacao_serializer import ContaAssociacaoSerializer +from ....core.models import AcaoAssociacao, Associacao, ContaAssociacao, Tag class RateioDespesaSerializer(serializers.ModelSerializer): @@ -126,4 +126,5 @@ class Meta: 'tipo_documento_nome', 'tipo_transacao_nome', 'data_transacao', + 'notificar_dias_nao_conferido', ) diff --git a/sme_ptrf_apps/despesas/api/views/despesas_viewset.py b/sme_ptrf_apps/despesas/api/views/despesas_viewset.py index dd9a67fa0..5651b49e0 100644 --- a/sme_ptrf_apps/despesas/api/views/despesas_viewset.py +++ b/sme_ptrf_apps/despesas/api/views/despesas_viewset.py @@ -34,18 +34,27 @@ def get_serializer_class(self): @action(detail=False, url_path='tabelas') def tabelas(self, request): - def get_valores_from(serializer): - valores = serializer.Meta.model.get_valores(user=request.user) + associacao_uuid = request.query_params.get('associacao_uuid') + + if associacao_uuid is None: + erro = { + 'erro': 'parametros_requerido', + 'mensagem': 'É necessário enviar o uuid da associação (associacao_uuid) como parâmetro.' + } + return Response(erro, status=status.HTTP_400_BAD_REQUEST) + + def get_valores_from(serializer, associacao_uuid): + valores = serializer.Meta.model.get_valores(user=request.user, associacao_uuid=associacao_uuid) return serializer(valores, many=True).data if valores else [] result = { 'tipos_aplicacao_recurso': aplicacoes_recurso_to_json(), - 'tipos_custeio': get_valores_from(TipoCusteioSerializer), - 'tipos_documento': get_valores_from(TipoDocumentoSerializer), - 'tipos_transacao': get_valores_from(TipoTransacaoSerializer), - 'acoes_associacao': get_valores_from(AcaoAssociacaoLookUpSerializer), - 'contas_associacao': get_valores_from(ContaAssociacaoLookUpSerializer), - 'tags': get_valores_from(TagLookupSerializer), + 'tipos_custeio': get_valores_from(TipoCusteioSerializer, associacao_uuid=associacao_uuid), + 'tipos_documento': get_valores_from(TipoDocumentoSerializer, associacao_uuid=associacao_uuid), + 'tipos_transacao': get_valores_from(TipoTransacaoSerializer, associacao_uuid=associacao_uuid), + 'acoes_associacao': get_valores_from(AcaoAssociacaoLookUpSerializer, associacao_uuid=associacao_uuid), + 'contas_associacao': get_valores_from(ContaAssociacaoLookUpSerializer, associacao_uuid=associacao_uuid), + 'tags': get_valores_from(TagLookupSerializer, associacao_uuid=associacao_uuid), } return Response(result) diff --git a/sme_ptrf_apps/despesas/api/views/rateios_despesas_viewset.py b/sme_ptrf_apps/despesas/api/views/rateios_despesas_viewset.py index aa983baf9..780c02173 100644 --- a/sme_ptrf_apps/despesas/api/views/rateios_despesas_viewset.py +++ b/sme_ptrf_apps/despesas/api/views/rateios_despesas_viewset.py @@ -27,8 +27,15 @@ class RateiosDespesasViewSet(mixins.CreateModelMixin, filter_fields = ('aplicacao_recurso', 'acao_associacao__uuid', 'despesa__status', 'associacao__uuid', 'conferido') def get_queryset(self): - user = self.request.user - qs = RateioDespesa.objects.filter(associacao=user.associacao).all().order_by('-despesa__data_documento') + associacao_uuid = self.request.query_params.get('associacao_uuid') or self.request.query_params.get('associacao__uuid') + if associacao_uuid is None: + erro = { + 'erro': 'parametros_requerido', + 'mensagem': 'É necessário enviar o uuid da associação como parâmetro..' + } + return Response(erro, status=status.HTTP_400_BAD_REQUEST) + + qs = RateioDespesa.objects.filter(associacao__uuid=associacao_uuid).all().order_by('-despesa__data_documento') data_inicio = self.request.query_params.get('data_inicio') data_fim = self.request.query_params.get('data_fim') diff --git a/sme_ptrf_apps/despesas/migrations/0028_despesa_valor_original.py b/sme_ptrf_apps/despesas/migrations/0028_despesa_valor_original.py new file mode 100644 index 000000000..9dc486017 --- /dev/null +++ b/sme_ptrf_apps/despesas/migrations/0028_despesa_valor_original.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.10 on 2020-08-03 12:10 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('despesas', '0027_auto_20200729_0841'), + ] + + operations = [ + migrations.AddField( + model_name='despesa', + name='valor_original', + field=models.DecimalField(decimal_places=2, default=0, max_digits=8, verbose_name='Valor original'), + ), + ] diff --git a/sme_ptrf_apps/despesas/migrations/0029_rateiodespesa_valor_original.py b/sme_ptrf_apps/despesas/migrations/0029_rateiodespesa_valor_original.py new file mode 100644 index 000000000..e2917137f --- /dev/null +++ b/sme_ptrf_apps/despesas/migrations/0029_rateiodespesa_valor_original.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.10 on 2020-08-03 12:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('despesas', '0028_despesa_valor_original'), + ] + + operations = [ + migrations.AddField( + model_name='rateiodespesa', + name='valor_original', + field=models.DecimalField(decimal_places=2, default=0, max_digits=8, verbose_name='Valor original'), + ), + ] diff --git a/sme_ptrf_apps/despesas/models/despesa.py b/sme_ptrf_apps/despesas/models/despesa.py index 2488f0004..66cb5cbae 100644 --- a/sme_ptrf_apps/despesas/models/despesa.py +++ b/sme_ptrf_apps/despesas/models/despesa.py @@ -41,6 +41,8 @@ class Despesa(ModeloBase): valor_recursos_proprios = models.DecimalField('Valor pago com recursos próprios', max_digits=8, decimal_places=2, default=0) + valor_original = models.DecimalField('Valor original', max_digits=8, decimal_places=2, default=0) + status = models.CharField( 'status', max_length=15, @@ -71,11 +73,11 @@ def cadastro_completo(self): if completo and self.tipo_documento.numero_documento_digitado: completo = completo and self.numero_documento - + if completo: for rateio in self.rateios.all(): completo = completo and rateio.status == STATUS_COMPLETO - + return completo def atualiza_status(self): diff --git a/sme_ptrf_apps/despesas/models/rateio_despesa.py b/sme_ptrf_apps/despesas/models/rateio_despesa.py index 86ecd43b2..f7c8d28ed 100644 --- a/sme_ptrf_apps/despesas/models/rateio_despesa.py +++ b/sme_ptrf_apps/despesas/models/rateio_despesa.py @@ -5,9 +5,8 @@ from django.db.models.signals import post_save, pre_save from django.dispatch import receiver +from sme_ptrf_apps.core.models import Tag, Parametros from sme_ptrf_apps.core.models_abstracts import ModeloBase -from sme_ptrf_apps.core.models import Tag - from ..status_cadastro_completo import STATUS_CHOICES, STATUS_COMPLETO, STATUS_INCOMPLETO from ..tipos_aplicacao_recurso import APLICACAO_CAPITAL, APLICACAO_CHOICES, APLICACAO_CUSTEIO @@ -44,6 +43,9 @@ class RateioDespesa(ModeloBase): numero_processo_incorporacao_capital = models.CharField('Nº processo incorporação', max_length=100, default='', blank=True) + valor_original = models.DecimalField('Valor original', max_digits=8, decimal_places=2, + default=0) + status = models.CharField( 'status', max_length=15, @@ -57,7 +59,7 @@ class RateioDespesa(ModeloBase): related_name='despesas_conciliadas', verbose_name='prestação de contas de conciliação') - tag = models.ForeignKey(Tag, on_delete=models.SET_NULL, blank=True, + tag = models.ForeignKey(Tag, on_delete=models.SET_NULL, blank=True, null=True, related_name='rateios') def __str__(self): @@ -81,6 +83,21 @@ def cadastro_completo(self): return completo + @property + def notificar_dias_nao_conferido(self): + """ + Se não conferida, retorna o tempo decorrido desde o lançamento, caso esse tempo seja superior ao parametrizado. + Caso contrário, retorna 0 + :rtype: int + """ + result = 0 + if not self.conferido and self.despesa.data_transacao: + decorrido = (date.today() - self.despesa.data_transacao).days + limite = Parametros.get().tempo_notificar_nao_demonstrados + result = decorrido if decorrido >= limite else 0 + return result + + @classmethod def rateios_da_acao_associacao_no_periodo(cls, acao_associacao, periodo, conferido=None, conta_associacao=None, exclude_despesa=None, aplicacao_recurso=None): diff --git a/sme_ptrf_apps/despesas/tests/conftest.py b/sme_ptrf_apps/despesas/tests/conftest.py index ca770e26a..d1952eecc 100644 --- a/sme_ptrf_apps/despesas/tests/conftest.py +++ b/sme_ptrf_apps/despesas/tests/conftest.py @@ -126,6 +126,7 @@ def despesa(associacao, tipo_documento, tipo_transacao): data_transacao=datetime.date(2020, 3, 10), valor_total=100.00, valor_recursos_proprios=10.00, + valor_original=90.00, ) @@ -198,6 +199,7 @@ def rateio_despesa_capital(associacao, despesa, conta_associacao, acao, tipo_apl numero_processo_incorporacao_capital='Teste123456', conferido=True, prestacao_conta=prestacao_conta_iniciada, + valor_original=90.00, ) diff --git a/sme_ptrf_apps/despesas/tests/tests_api_despesas/test_despesas_endpoint.py b/sme_ptrf_apps/despesas/tests/tests_api_despesas/test_despesas_endpoint.py index 586316d1f..6297af7d1 100644 --- a/sme_ptrf_apps/despesas/tests/tests_api_despesas/test_despesas_endpoint.py +++ b/sme_ptrf_apps/despesas/tests/tests_api_despesas/test_despesas_endpoint.py @@ -12,7 +12,7 @@ def test_url_authorized(authenticated_client): def test_url_tabelas(associacao, jwt_authenticated_client): - response = jwt_authenticated_client.get('/api/despesas/tabelas/') + response = jwt_authenticated_client.get(f'/api/despesas/tabelas/?associacao_uuid={associacao.uuid}') result = json.loads(response.content) chaves_esperadas = [ diff --git a/sme_ptrf_apps/despesas/tests/tests_api_despesas/test_get_despesas_tabelas.py b/sme_ptrf_apps/despesas/tests/tests_api_despesas/test_get_despesas_tabelas.py index 7c8cea9d1..4deeb7454 100644 --- a/sme_ptrf_apps/despesas/tests/tests_api_despesas/test_get_despesas_tabelas.py +++ b/sme_ptrf_apps/despesas/tests/tests_api_despesas/test_get_despesas_tabelas.py @@ -10,7 +10,7 @@ def test_api_get_despesas_tabelas(associacao, jwt_authenticated_client, tipo_aplicacao_recurso, tipo_custeio, tipo_documento, tipo_transacao, acao, acao_associacao, tipo_conta, conta_associacao, tag_ativa): - response = jwt_authenticated_client.get('/api/despesas/tabelas/', content_type='application/json') + response = jwt_authenticated_client.get(f'/api/despesas/tabelas/?associacao_uuid={associacao.uuid}', content_type='application/json') result = json.loads(response.content) esperado = { diff --git a/sme_ptrf_apps/despesas/tests/tests_api_rateios_despesas/test_get_rateios_despesas.py b/sme_ptrf_apps/despesas/tests/tests_api_rateios_despesas/test_get_rateios_despesas.py index 3720c6d78..95cd8ac4b 100644 --- a/sme_ptrf_apps/despesas/tests/tests_api_rateios_despesas/test_get_rateios_despesas.py +++ b/sme_ptrf_apps/despesas/tests/tests_api_rateios_despesas/test_get_rateios_despesas.py @@ -6,8 +6,8 @@ pytestmark = pytest.mark.django_db -def test_api_get_rateios_despesas(jwt_authenticated_client, despesa, rateio_despesa_capital): - response = jwt_authenticated_client.get('/api/rateios-despesas/', content_type='application/json') +def test_api_get_rateios_despesas(jwt_authenticated_client, associacao, despesa, rateio_despesa_capital): + response = jwt_authenticated_client.get(f'/api/rateios-despesas/?associacao_uuid={associacao.uuid}', content_type='application/json') result = json.loads(response.content) results = [ @@ -36,6 +36,7 @@ def test_api_get_rateios_despesas(jwt_authenticated_client, despesa, rateio_desp "tipo_documento_nome": despesa.tipo_documento.nome, "tipo_transacao_nome": despesa.tipo_transacao.nome, "data_transacao": '2020-03-10', + 'notificar_dias_nao_conferido': 0, }, ] diff --git a/sme_ptrf_apps/despesas/tests/tests_api_rateios_despesas/test_rateios_despesas_endpoint.py b/sme_ptrf_apps/despesas/tests/tests_api_rateios_despesas/test_rateios_despesas_endpoint.py index dba46e603..eaa753f07 100644 --- a/sme_ptrf_apps/despesas/tests/tests_api_rateios_despesas/test_rateios_despesas_endpoint.py +++ b/sme_ptrf_apps/despesas/tests/tests_api_rateios_despesas/test_rateios_despesas_endpoint.py @@ -5,5 +5,5 @@ def test_endpoint(associacao, jwt_authenticated_client): - response = jwt_authenticated_client.get('/api/rateios-despesas/') + response = jwt_authenticated_client.get(f'/api/rateios-despesas/?associacao_uuid={associacao.uuid}') assert response.status_code == status.HTTP_200_OK diff --git a/sme_ptrf_apps/despesas/tests/tests_despesas/test_despesa_model.py b/sme_ptrf_apps/despesas/tests/tests_despesas/test_despesa_model.py index 086211796..2d61bb5ea 100644 --- a/sme_ptrf_apps/despesas/tests/tests_despesas/test_despesa_model.py +++ b/sme_ptrf_apps/despesas/tests/tests_despesas/test_despesa_model.py @@ -26,6 +26,7 @@ def test_instance_model(despesa): assert model.uuid assert model.id assert model.status == STATUS_COMPLETO + assert model.valor_original def test_srt_model(despesa): diff --git a/sme_ptrf_apps/despesas/tests/tests_rateio_despesa/conftest.py b/sme_ptrf_apps/despesas/tests/tests_rateio_despesa/conftest.py index 7809e9cfd..3639ed7f5 100644 --- a/sme_ptrf_apps/despesas/tests/tests_rateio_despesa/conftest.py +++ b/sme_ptrf_apps/despesas/tests/tests_rateio_despesa/conftest.py @@ -55,6 +55,7 @@ def rateio_despesa_2020_role_custeio_conferido(associacao, despesa_2020_1, conta ) + @pytest.fixture def rateio_despesa_2020_role_capital_conferido(associacao, despesa_2020_1, conta_associacao, acao, tipo_aplicacao_recurso_capital, @@ -74,6 +75,7 @@ def rateio_despesa_2020_role_capital_conferido(associacao, despesa_2020_1, conta ) + @pytest.fixture def rateio_despesa_2020_role_custeio_nao_conferido(associacao, despesa_2020_1, conta_associacao, acao, tipo_aplicacao_recurso_custeio, @@ -149,3 +151,58 @@ def rateio_despesa_2019_role_conferido(associacao, despesa_2019_2, conta_associa conferido=True, ) + + +@pytest.fixture +def despesa_01_03_2020(associacao, tipo_documento, tipo_transacao): + return baker.make( + 'Despesa', + associacao=associacao, + numero_documento='123456', + data_documento=datetime.date(2020, 3, 1), + tipo_documento=tipo_documento, + cpf_cnpj_fornecedor='11.478.276/0001-04', + nome_fornecedor='Fornecedor SA', + tipo_transacao=tipo_transacao, + data_transacao=datetime.date(2020, 3, 1), + valor_total=100.00, + valor_recursos_proprios=10.00, + ) + + +@pytest.fixture +def rateio_despesa_01_03_2020_conferido(associacao, despesa_01_03_2020, conta_associacao, acao, + tipo_aplicacao_recurso_custeio, + tipo_custeio_servico, + especificacao_instalacao_eletrica, acao_associacao_role_cultural): + return baker.make( + 'RateioDespesa', + despesa=despesa_01_03_2020, + associacao=associacao, + conta_associacao=conta_associacao, + acao_associacao=acao_associacao_role_cultural, + aplicacao_recurso=tipo_aplicacao_recurso_custeio, + tipo_custeio=tipo_custeio_servico, + especificacao_material_servico=especificacao_instalacao_eletrica, + valor_rateio=100.00, + conferido=True, + ) + + +@pytest.fixture +def rateio_despesa_01_03_2020_nao_conferido(associacao, despesa_01_03_2020, conta_associacao, acao, + tipo_aplicacao_recurso_custeio, + tipo_custeio_servico, + especificacao_instalacao_eletrica, acao_associacao_role_cultural): + return baker.make( + 'RateioDespesa', + despesa=despesa_01_03_2020, + associacao=associacao, + conta_associacao=conta_associacao, + acao_associacao=acao_associacao_role_cultural, + aplicacao_recurso=tipo_aplicacao_recurso_custeio, + tipo_custeio=tipo_custeio_servico, + especificacao_material_servico=especificacao_instalacao_eletrica, + valor_rateio=100.00, + conferido=False, + ) diff --git a/sme_ptrf_apps/despesas/tests/tests_rateio_despesa/test_rateio_despesa_model.py b/sme_ptrf_apps/despesas/tests/tests_rateio_despesa/test_rateio_despesa_model.py index 4bf650362..9b03192ac 100644 --- a/sme_ptrf_apps/despesas/tests/tests_rateio_despesa/test_rateio_despesa_model.py +++ b/sme_ptrf_apps/despesas/tests/tests_rateio_despesa/test_rateio_despesa_model.py @@ -1,5 +1,6 @@ import pytest from django.contrib import admin +from freezegun import freeze_time from ...models import RateioDespesa from ...status_cadastro_completo import STATUS_COMPLETO @@ -28,6 +29,7 @@ def test_instance_model(rateio_despesa_capital): assert model.status == STATUS_COMPLETO assert model.conferido assert model.prestacao_conta + assert model.valor_original def test_srt_model(rateio_despesa_capital): @@ -49,7 +51,25 @@ def test_marcar_conferido(rateio_despesa_nao_conferido): rateio = RateioDespesa.objects.get(id=rateio_despesa_nao_conferido.id) assert rateio.conferido + def test_desmarcar_conferido(rateio_despesa_conferido): rateio_despesa_conferido.desmarcar_conferido() rateio = RateioDespesa.objects.get(id=rateio_despesa_conferido.id) assert not rateio.conferido + + +@freeze_time('2020-03-12') +def test_notificar_nao_conferido(rateio_despesa_01_03_2020_nao_conferido, parametros_tempo_nao_conferido_10_dias): + assert rateio_despesa_01_03_2020_nao_conferido.notificar_dias_nao_conferido == 11 + + +@freeze_time('2020-03-12') +def test_notificar_nao_conferido_quando_limite_e_superior(rateio_despesa_01_03_2020_nao_conferido, + parametros_tempo_nao_conferido_60_dias): + assert rateio_despesa_01_03_2020_nao_conferido.notificar_dias_nao_conferido == 0 + + +@freeze_time('2020-03-12') +def test_notificar_nao_conferido_quando_conferido(rateio_despesa_01_03_2020_conferido, + parametros_tempo_nao_conferido_10_dias): + assert rateio_despesa_01_03_2020_conferido.notificar_dias_nao_conferido == 0 diff --git a/sme_ptrf_apps/despesas/tests/tests_rateio_despesa/test_rateio_despesa_serializer.py b/sme_ptrf_apps/despesas/tests/tests_rateio_despesa/test_rateio_despesa_serializer.py index f4586e375..df60ccd60 100644 --- a/sme_ptrf_apps/despesas/tests/tests_rateio_despesa/test_rateio_despesa_serializer.py +++ b/sme_ptrf_apps/despesas/tests/tests_rateio_despesa/test_rateio_despesa_serializer.py @@ -32,7 +32,8 @@ def test_serializer_lista(rateio_despesa_capital): 'numero_documento', 'status_despesa', 'especificacao_material_servico', - 'data_documento', 'aplicacao_recurso', + 'data_documento', + 'aplicacao_recurso', 'acao_associacao', 'valor_total', 'conferido', @@ -41,8 +42,9 @@ def test_serializer_lista(rateio_despesa_capital): 'tipo_documento_nome', 'tipo_transacao_nome', 'data_transacao', + 'notificar_dias_nao_conferido' ) assert serializer.data is not None for field in expected_fields: - assert serializer.data[field] + assert serializer.data[field] is not None diff --git a/sme_ptrf_apps/despesas/tests/tests_rateio_despesa/test_rateio_despesa_viewset.py b/sme_ptrf_apps/despesas/tests/tests_rateio_despesa/test_rateio_despesa_viewset.py index 83ca8bcb8..f0e24d508 100644 --- a/sme_ptrf_apps/despesas/tests/tests_rateio_despesa/test_rateio_despesa_viewset.py +++ b/sme_ptrf_apps/despesas/tests/tests_rateio_despesa/test_rateio_despesa_viewset.py @@ -8,7 +8,7 @@ pytestmark = pytest.mark.django_db -def test_view_set(rateio_despesa_capital, jwt_authenticated_client): - response = jwt_authenticated_client.get(f'/api/rateios-despesas/{rateio_despesa_capital.uuid}/') +def test_view_set(associacao, rateio_despesa_capital, jwt_authenticated_client): + response = jwt_authenticated_client.get(f'/api/rateios-despesas/{rateio_despesa_capital.uuid}/?associacao_uuid={associacao.uuid}') assert response.status_code == status.HTTP_200_OK diff --git a/sme_ptrf_apps/dre/__init__.py b/sme_ptrf_apps/dre/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/sme_ptrf_apps/dre/admin.py b/sme_ptrf_apps/dre/admin.py new file mode 100644 index 000000000..0fedbbd0c --- /dev/null +++ b/sme_ptrf_apps/dre/admin.py @@ -0,0 +1,57 @@ +from django.contrib import admin + +from .models import (GrupoVerificacaoRegularidade, ListaVerificacaoRegularidade, ItemVerificacaoRegularidade, + VerificacaoRegularidadeAssociacao, TecnicoDre) + + +class ListasVerificacaoInline(admin.TabularInline): + extra = 1 + model = ListaVerificacaoRegularidade + + +class ItensVerificacaoInline(admin.TabularInline): + extra = 1 + model = ItemVerificacaoRegularidade + + +@admin.register(GrupoVerificacaoRegularidade) +class GrupoVerificacaoRegularidadeAdmin(admin.ModelAdmin): + list_display = ['titulo', ] + search_fields = ['titulo', ] + readonly_fields = ['id', 'uuid'] + inlines = [ + ListasVerificacaoInline + ] + + +@admin.register(ListaVerificacaoRegularidade) +class ListaVerificacaoRegularidadeAdmin(admin.ModelAdmin): + list_display = ['titulo', 'grupo'] + search_fields = ['titulo', ] + readonly_fields = ['id', 'uuid'] + inlines = [ + ItensVerificacaoInline + ] + list_filter = ('grupo',) + + +@admin.register(ItemVerificacaoRegularidade) +class ItemVerificacaoRegularidadeAdmin(admin.ModelAdmin): + list_display = ('descricao', 'lista') + search_fields = ('uuid', 'descricao') + list_filter = ('lista', 'lista__grupo') + readonly_fields = ('uuid', 'id') + +@admin.register(VerificacaoRegularidadeAssociacao) +class VerificacaoRegularidadeAssociacaoAdmin(admin.ModelAdmin): + list_display = ('item_verificacao', 'regular', 'lista_verificacao', 'associacao') + search_fields = ('uuid', 'item_verificacao__descricao') + list_filter = ('associacao','lista_verificacao', 'grupo_verificacao', 'item_verificacao') + readonly_fields = ('uuid', 'id') + +@admin.register(TecnicoDre) +class TecnicoDreAdmin(admin.ModelAdmin): + list_display = ('rf', 'nome', 'dre') + search_fields = ('uuid', 'nome', 'rf') + list_filter = ('dre',) + readonly_fields = ('uuid', 'id') diff --git a/sme_ptrf_apps/dre/api/__init__.py b/sme_ptrf_apps/dre/api/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/sme_ptrf_apps/dre/api/serializers/__init__.py b/sme_ptrf_apps/dre/api/serializers/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/sme_ptrf_apps/dre/api/serializers/tecnico_dre_serializer.py b/sme_ptrf_apps/dre/api/serializers/tecnico_dre_serializer.py new file mode 100644 index 000000000..ff9848530 --- /dev/null +++ b/sme_ptrf_apps/dre/api/serializers/tecnico_dre_serializer.py @@ -0,0 +1,32 @@ +from rest_framework import serializers + +from ...models import TecnicoDre +from ....core.api.serializers.unidade_serializer import DreSerializer +from ....core.models import Unidade + + +class TecnicoDreSerializer(serializers.ModelSerializer): + dre = DreSerializer() + + class Meta: + model = TecnicoDre + fields = ('uuid', 'rf', 'nome', 'dre') + + +class TecnicoDreLookUpSerializer(serializers.ModelSerializer): + + class Meta: + model = TecnicoDre + fields = ('uuid', 'rf', 'nome',) + + + +class TecnicoDreCreateSerializer(serializers.ModelSerializer): + dre = serializers.SlugRelatedField( + slug_field='uuid', + required=False, + queryset=Unidade.objects.all() + ) + class Meta: + model = TecnicoDre + exclude = ('id',) diff --git a/sme_ptrf_apps/dre/api/views/__init__.py b/sme_ptrf_apps/dre/api/views/__init__.py new file mode 100644 index 000000000..c4fdde8f0 --- /dev/null +++ b/sme_ptrf_apps/dre/api/views/__init__.py @@ -0,0 +1 @@ +from .tecnicos_dre_viewset import TecnicosDreViewSet diff --git a/sme_ptrf_apps/dre/api/views/tecnicos_dre_viewset.py b/sme_ptrf_apps/dre/api/views/tecnicos_dre_viewset.py new file mode 100644 index 000000000..f00ae910a --- /dev/null +++ b/sme_ptrf_apps/dre/api/views/tecnicos_dre_viewset.py @@ -0,0 +1,20 @@ +from django_filters import rest_framework as filters +from rest_framework import viewsets +from rest_framework.permissions import AllowAny + +from ..serializers.tecnico_dre_serializer import TecnicoDreSerializer, TecnicoDreCreateSerializer +from ...models import TecnicoDre + + +class TecnicosDreViewSet(viewsets.ModelViewSet): + permission_classes = [AllowAny] + lookup_field = 'uuid' + queryset = TecnicoDre.objects.all() + serializer_class = TecnicoDreSerializer + filter_backends = (filters.DjangoFilterBackend,) + filter_fields = ('dre__uuid', 'rf') + def get_serializer_class(self): + if self.action in ['retrieve', 'list']: + return TecnicoDreSerializer + else: + return TecnicoDreCreateSerializer diff --git a/sme_ptrf_apps/dre/apps.py b/sme_ptrf_apps/dre/apps.py new file mode 100644 index 000000000..e843d4c21 --- /dev/null +++ b/sme_ptrf_apps/dre/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class DreConfig(AppConfig): + name = 'sme_ptrf_apps.dre' diff --git a/sme_ptrf_apps/dre/migrations/0001_initial.py b/sme_ptrf_apps/dre/migrations/0001_initial.py new file mode 100644 index 000000000..9fa855b82 --- /dev/null +++ b/sme_ptrf_apps/dre/migrations/0001_initial.py @@ -0,0 +1,61 @@ +# Generated by Django 2.2.10 on 2020-08-11 13:40 + +import uuid + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='GrupoVerificacaoRegularidade', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('criado_em', models.DateTimeField(auto_now_add=True, verbose_name='Criado em')), + ('alterado_em', models.DateTimeField(auto_now=True, verbose_name='Alterado em')), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)), + ('titulo', models.CharField(max_length=100, verbose_name='Titulo do grupo')), + ], + options={ + 'verbose_name': 'Grupo de verificação de regularidade', + 'verbose_name_plural': 'Grupos de verificação de regularidade', + }, + ), + migrations.CreateModel( + name='ListaVerificacaoRegularidade', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('criado_em', models.DateTimeField(auto_now_add=True, verbose_name='Criado em')), + ('alterado_em', models.DateTimeField(auto_now=True, verbose_name='Alterado em')), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)), + ('titulo', models.CharField(max_length=100, verbose_name='Nome do Grupo')), + ('grupo', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='listas_de_verificacao', to='dre.GrupoVerificacaoRegularidade')), + ], + options={ + 'verbose_name': 'Lista de Verificação de Regularidade', + 'verbose_name_plural': 'Listas de Verificação de Regularidade', + }, + ), + migrations.CreateModel( + name='ItemVerificacaoRegularidade', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('criado_em', models.DateTimeField(auto_now_add=True, verbose_name='Criado em')), + ('alterado_em', models.DateTimeField(auto_now=True, verbose_name='Alterado em')), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)), + ('descricao', models.TextField(verbose_name='Descrição')), + ('lista', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='itens_de_verificacao', to='dre.ListaVerificacaoRegularidade')), + ], + options={ + 'verbose_name': 'Item de verificação de regularidade', + 'verbose_name_plural': 'Itens de verificação de regularidade', + }, + ), + ] diff --git a/sme_ptrf_apps/dre/migrations/0002_auto_20200811_1451.py b/sme_ptrf_apps/dre/migrations/0002_auto_20200811_1451.py new file mode 100644 index 000000000..e3c4e880d --- /dev/null +++ b/sme_ptrf_apps/dre/migrations/0002_auto_20200811_1451.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.10 on 2020-08-11 14:51 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dre', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='listaverificacaoregularidade', + name='titulo', + field=models.CharField(max_length=100, verbose_name='Título da lista'), + ), + ] diff --git a/sme_ptrf_apps/dre/migrations/0003_auto_20200811_1458.py b/sme_ptrf_apps/dre/migrations/0003_auto_20200811_1458.py new file mode 100644 index 000000000..6cfc42e86 --- /dev/null +++ b/sme_ptrf_apps/dre/migrations/0003_auto_20200811_1458.py @@ -0,0 +1,17 @@ +# Generated by Django 2.2.10 on 2020-08-11 14:58 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('dre', '0002_auto_20200811_1451'), + ] + + operations = [ + migrations.AlterModelOptions( + name='listaverificacaoregularidade', + options={'verbose_name': 'Lista de verificação de regularidade', 'verbose_name_plural': 'Listas de verificação de regularidade'}, + ), + ] diff --git a/sme_ptrf_apps/dre/migrations/0004_verificacaoregularidadeassociacao.py b/sme_ptrf_apps/dre/migrations/0004_verificacaoregularidadeassociacao.py new file mode 100644 index 000000000..de2ea95db --- /dev/null +++ b/sme_ptrf_apps/dre/migrations/0004_verificacaoregularidadeassociacao.py @@ -0,0 +1,35 @@ +# Generated by Django 2.2.10 on 2020-08-12 08:40 + +import uuid + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0074_unidade_dre_designacao_ano'), + ('dre', '0003_auto_20200811_1458'), + ] + + operations = [ + migrations.CreateModel( + name='VerificacaoRegularidadeAssociacao', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('criado_em', models.DateTimeField(auto_now_add=True, verbose_name='Criado em')), + ('alterado_em', models.DateTimeField(auto_now=True, verbose_name='Alterado em')), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)), + ('regular', models.BooleanField(default=True, verbose_name='Regular?')), + ('associacao', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='verificacoes_regularidade', to='core.Associacao')), + ('grupo_verificacao', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='grupos_de_verificacao', to='dre.GrupoVerificacaoRegularidade')), + ('item_verificacao', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='itens_de_verificacao', to='dre.ItemVerificacaoRegularidade')), + ('lista_verificacao', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='listas_de_verificacao', to='dre.ListaVerificacaoRegularidade')), + ], + options={ + 'verbose_name': 'Verificação de regularidade de associação', + 'verbose_name_plural': 'Verificações de regularidade de associações', + }, + ), + ] diff --git a/sme_ptrf_apps/dre/migrations/0005_tecnicodre.py b/sme_ptrf_apps/dre/migrations/0005_tecnicodre.py new file mode 100644 index 000000000..afa6390da --- /dev/null +++ b/sme_ptrf_apps/dre/migrations/0005_tecnicodre.py @@ -0,0 +1,33 @@ +# Generated by Django 2.2.10 on 2020-08-14 13:30 + +import uuid + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0074_unidade_dre_designacao_ano'), + ('dre', '0004_verificacaoregularidadeassociacao'), + ] + + operations = [ + migrations.CreateModel( + name='TecnicoDre', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('criado_em', models.DateTimeField(auto_now_add=True, verbose_name='Criado em')), + ('alterado_em', models.DateTimeField(auto_now=True, verbose_name='Alterado em')), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)), + ('nome', models.CharField(max_length=160, verbose_name='Nome')), + ('rf', models.CharField(blank=True, default='', max_length=10, null=True, verbose_name='RF')), + ('dre', models.ForeignKey(blank=True, limit_choices_to={'tipo_unidade': 'DRE'}, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='tecnicos_da_dre', to='core.Unidade')), + ], + options={ + 'verbose_name': 'Técnico de DRE', + 'verbose_name_plural': 'Técnicos de DREs', + }, + ), + ] diff --git a/sme_ptrf_apps/dre/migrations/0006_auto_20200819_0720.py b/sme_ptrf_apps/dre/migrations/0006_auto_20200819_0720.py new file mode 100644 index 000000000..689ddbf9e --- /dev/null +++ b/sme_ptrf_apps/dre/migrations/0006_auto_20200819_0720.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.10 on 2020-08-19 07:20 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dre', '0005_tecnicodre'), + ] + + operations = [ + migrations.AlterField( + model_name='tecnicodre', + name='rf', + field=models.CharField(blank=True, default='', max_length=10, null=True, unique=True, verbose_name='RF'), + ), + ] diff --git a/sme_ptrf_apps/dre/migrations/__init__.py b/sme_ptrf_apps/dre/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/sme_ptrf_apps/dre/models/__init__.py b/sme_ptrf_apps/dre/models/__init__.py new file mode 100644 index 000000000..6a2564410 --- /dev/null +++ b/sme_ptrf_apps/dre/models/__init__.py @@ -0,0 +1,5 @@ +from .grupo_verificacao_regularidade import GrupoVerificacaoRegularidade +from .item_verificacao_regularidade import ItemVerificacaoRegularidade +from .lista_verificacao_regularidade import ListaVerificacaoRegularidade +from .tecnico_diretoria import TecnicoDre +from .verificacao_regularidade_associacao import VerificacaoRegularidadeAssociacao diff --git a/sme_ptrf_apps/dre/models/grupo_verificacao_regularidade.py b/sme_ptrf_apps/dre/models/grupo_verificacao_regularidade.py new file mode 100644 index 000000000..c092c38fd --- /dev/null +++ b/sme_ptrf_apps/dre/models/grupo_verificacao_regularidade.py @@ -0,0 +1,14 @@ +from django.db import models + +from sme_ptrf_apps.core.models_abstracts import ModeloBase + + +class GrupoVerificacaoRegularidade(ModeloBase): + titulo = models.CharField('Titulo do grupo', max_length=100) + + def __str__(self): + return self.titulo + + class Meta: + verbose_name = 'Grupo de verificação de regularidade' + verbose_name_plural = 'Grupos de verificação de regularidade' diff --git a/sme_ptrf_apps/dre/models/item_verificacao_regularidade.py b/sme_ptrf_apps/dre/models/item_verificacao_regularidade.py new file mode 100644 index 000000000..a4edb2f0a --- /dev/null +++ b/sme_ptrf_apps/dre/models/item_verificacao_regularidade.py @@ -0,0 +1,15 @@ +from django.db import models + +from sme_ptrf_apps.core.models_abstracts import ModeloBase + + +class ItemVerificacaoRegularidade(ModeloBase): + descricao = models.TextField('Descrição') + lista = models.ForeignKey('ListaVerificacaoRegularidade', on_delete=models.CASCADE, related_name="itens_de_verificacao") + + def __str__(self): + return self.descricao + + class Meta: + verbose_name = 'Item de verificação de regularidade' + verbose_name_plural = 'Itens de verificação de regularidade' diff --git a/sme_ptrf_apps/dre/models/lista_verificacao_regularidade.py b/sme_ptrf_apps/dre/models/lista_verificacao_regularidade.py new file mode 100644 index 000000000..3f144f453 --- /dev/null +++ b/sme_ptrf_apps/dre/models/lista_verificacao_regularidade.py @@ -0,0 +1,15 @@ +from django.db import models + +from sme_ptrf_apps.core.models_abstracts import ModeloBase + + +class ListaVerificacaoRegularidade(ModeloBase): + titulo = models.CharField('Título da lista', max_length=100) + grupo = models.ForeignKey('GrupoVerificacaoRegularidade', on_delete=models.CASCADE, related_name="listas_de_verificacao") + + def __str__(self): + return self.titulo + + class Meta: + verbose_name = 'Lista de verificação de regularidade' + verbose_name_plural = 'Listas de verificação de regularidade' diff --git a/sme_ptrf_apps/dre/models/tecnico_diretoria.py b/sme_ptrf_apps/dre/models/tecnico_diretoria.py new file mode 100644 index 000000000..9385609a2 --- /dev/null +++ b/sme_ptrf_apps/dre/models/tecnico_diretoria.py @@ -0,0 +1,19 @@ +from django.db import models + +from sme_ptrf_apps.core.models_abstracts import ModeloBase + + +class TecnicoDre(ModeloBase): + dre = models.ForeignKey('core.Unidade', on_delete=models.PROTECT, related_name='tecnicos_da_dre', to_field="codigo_eol", + blank=True, null=True, limit_choices_to={'tipo_unidade': 'DRE'}) + + nome = models.CharField('Nome', max_length=160) + + rf = models.CharField('RF', max_length=10, blank=True, null=True, default="", unique=True) + + class Meta: + verbose_name = "Técnico de DRE" + verbose_name_plural = "Técnicos de DREs" + + def __str__(self): + return f"Nome: {self.nome}, RF: {self.rf}" diff --git a/sme_ptrf_apps/dre/models/verificacao_regularidade_associacao.py b/sme_ptrf_apps/dre/models/verificacao_regularidade_associacao.py new file mode 100644 index 000000000..81509e086 --- /dev/null +++ b/sme_ptrf_apps/dre/models/verificacao_regularidade_associacao.py @@ -0,0 +1,21 @@ +from django.db import models + +from sme_ptrf_apps.core.models_abstracts import ModeloBase + + +class VerificacaoRegularidadeAssociacao(ModeloBase): + associacao = models.ForeignKey('core.Associacao', on_delete=models.PROTECT, related_name='verificacoes_regularidade') + grupo_verificacao = models.ForeignKey('GrupoVerificacaoRegularidade', on_delete=models.PROTECT, + related_name="grupos_de_verificacao") + lista_verificacao = models.ForeignKey('ListaVerificacaoRegularidade', on_delete=models.PROTECT, + related_name="listas_de_verificacao") + item_verificacao = models.ForeignKey('ItemVerificacaoRegularidade', on_delete=models.PROTECT, + related_name="itens_de_verificacao") + regular = models.BooleanField('Regular?', default=True) + + def __str__(self): + return f'{self.item_verificacao.descricao if self.item_verificacao else ""} - {"Regular" if self.regular else "Irregular"}' + + class Meta: + verbose_name = 'Verificação de regularidade de associação' + verbose_name_plural = 'Verificações de regularidade de associações' diff --git a/sme_ptrf_apps/dre/services/__init__.py b/sme_ptrf_apps/dre/services/__init__.py new file mode 100644 index 000000000..0389fc6d8 --- /dev/null +++ b/sme_ptrf_apps/dre/services/__init__.py @@ -0,0 +1,7 @@ +from .regularidade_associacao_service import ( + verifica_regularidade_associacao, + marca_item_verificacao_associacao, + desmarca_item_verificacao_associacao, + marca_lista_verificacao_associacao, + desmarca_lista_verificacao_associacao +) diff --git a/sme_ptrf_apps/dre/services/regularidade_associacao_service.py b/sme_ptrf_apps/dre/services/regularidade_associacao_service.py new file mode 100644 index 000000000..7dcbc4079 --- /dev/null +++ b/sme_ptrf_apps/dre/services/regularidade_associacao_service.py @@ -0,0 +1,144 @@ +import logging + +from rest_framework.exceptions import ValidationError + +from ..models import (GrupoVerificacaoRegularidade, VerificacaoRegularidadeAssociacao, ItemVerificacaoRegularidade, + ListaVerificacaoRegularidade) +from ...core.models import Associacao + +logger = logging.getLogger(__name__) + + +def verifica_regularidade_associacao(associacao_uuid): + associacao = Associacao.by_uuid(associacao_uuid) + + grupos = [] + for grupo in GrupoVerificacaoRegularidade.objects.all(): + listas = [] + for lista in grupo.listas_de_verificacao.all(): + itens = [] + qtd_irregulares = 0 + for item in lista.itens_de_verificacao.all(): + verificacao = VerificacaoRegularidadeAssociacao.objects.filter(associacao=associacao, + item_verificacao=item).first() + if not verificacao or not verificacao.regular: + qtd_irregulares += 1 + + itens.append( + { + 'descricao': item.descricao, + 'regular': verificacao.regular if verificacao else False, + 'uuid': f'{item.uuid}' + } + ) + listas.append( + { + 'itens_verificacao': itens, + 'status_lista_verificacao': 'Regular' if qtd_irregulares == 0 else 'Pendente', + 'titulo': lista.titulo, + 'uuid': f'{lista.uuid}' + } + ) + grupos.append( + { + 'listas_verificacao': listas, + 'titulo': grupo.titulo, + 'uuid': f'{grupo.uuid}' + } + ) + + result = { + 'uuid': f'{associacao.uuid}', + 'verificacao_regularidade': { + 'grupos_verificacao': grupos + } + } + return result + + +def marca_item_verificacao_associacao(associacao_uuid, item_verificacao_uuid): + associacao = Associacao.by_uuid(associacao_uuid) + + if not associacao: + msgError = f'Associacao não encontrada. UUID:{associacao_uuid}' + logger.info(msgError) + raise ValidationError(msgError) + + item_verificacao = ItemVerificacaoRegularidade.by_uuid(item_verificacao_uuid) + + if not item_verificacao: + msgError = f'Item de verificação não encontrado. UUID:{item_verificacao_uuid}' + logger.info(msgError) + raise ValidationError(msgError) + + result = VerificacaoRegularidadeAssociacao.objects.get_or_create( + associacao=associacao, + item_verificacao=item_verificacao, + defaults={ + 'grupo_verificacao': item_verificacao.lista.grupo, + 'lista_verificacao': item_verificacao.lista, + 'regular': True, + } + ) if associacao and item_verificacao else None + + logger.info(f'Item de verificação marcado {result}') + + return result + + +def desmarca_item_verificacao_associacao(associacao_uuid, item_verificacao_uuid): + associacao = Associacao.by_uuid(associacao_uuid) + item_verificacao = ItemVerificacaoRegularidade.by_uuid(item_verificacao_uuid) + + VerificacaoRegularidadeAssociacao.objects.filter( + associacao=associacao, + item_verificacao=item_verificacao + ).delete() + + return 'OK' if associacao and item_verificacao else None + + +def marca_lista_verificacao_associacao(associacao_uuid, lista_verificacao_uuid): + associacao = Associacao.by_uuid(associacao_uuid) + + if not associacao: + msgError = f'Associacao não encontrada. UUID:{associacao_uuid}' + logger.info(msgError) + raise ValidationError(msgError) + + lista_verificacao = ListaVerificacaoRegularidade.by_uuid(lista_verificacao_uuid) + + if not lista_verificacao: + msgError = f'Lista de verificação não encontrada. UUID:{lista_verificacao_uuid}' + logger.info(msgError) + raise ValidationError(msgError) + + logger.info(f'Marcando itens da lista de verificação...') + for item in lista_verificacao.itens_de_verificacao.all(): + marca_item_verificacao_associacao(associacao_uuid=associacao_uuid, item_verificacao_uuid=item.uuid) + + + return 'OK' if associacao and lista_verificacao else None + + +def desmarca_lista_verificacao_associacao(associacao_uuid, lista_verificacao_uuid): + associacao = Associacao.by_uuid(associacao_uuid) + + if not associacao: + msgError = f'Associacao não encontrada. UUID:{associacao_uuid}' + logger.info(msgError) + raise ValidationError(msgError) + + lista_verificacao = ListaVerificacaoRegularidade.by_uuid(lista_verificacao_uuid) + + if not lista_verificacao: + msgError = f'Lista de verificação não encontrada. UUID:{lista_verificacao_uuid}' + logger.info(msgError) + raise ValidationError(msgError) + + logger.info(f'Desmarcando itens da lista de verificação...') + for item in lista_verificacao.itens_de_verificacao.all(): + desmarca_item_verificacao_associacao(associacao_uuid=associacao_uuid, item_verificacao_uuid=item.uuid) + + + return 'OK' if associacao and lista_verificacao else None diff --git a/sme_ptrf_apps/dre/tests/__init__.py b/sme_ptrf_apps/dre/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/sme_ptrf_apps/dre/tests/conftest.py b/sme_ptrf_apps/dre/tests/conftest.py new file mode 100644 index 000000000..1ec1d2625 --- /dev/null +++ b/sme_ptrf_apps/dre/tests/conftest.py @@ -0,0 +1,45 @@ +import pytest +from model_bakery import baker + + +@pytest.fixture +def grupo_verificacao_regularidade_documentos(): + return baker.make('GrupoVerificacaoRegularidade', titulo='Documentos') + + +@pytest.fixture +def lista_verificacao_regularidade_documentos_associacao(grupo_verificacao_regularidade_documentos): + return baker.make( + 'ListaVerificacaoRegularidade', + titulo='Documentos da Associação', + grupo=grupo_verificacao_regularidade_documentos + ) + +@pytest.fixture +def item_verificacao_regularidade_documentos_associacao_cnpj(lista_verificacao_regularidade_documentos_associacao): + return baker.make( + 'ItemVerificacaoRegularidade', + descricao='CNPJ', + lista=lista_verificacao_regularidade_documentos_associacao + ) + +@pytest.fixture +def verificacao_regularidade_associacao_documento_cnpj(grupo_verificacao_regularidade_documentos, lista_verificacao_regularidade_documentos_associacao,item_verificacao_regularidade_documentos_associacao_cnpj, associacao): + return baker.make( + 'VerificacaoRegularidadeAssociacao', + associacao=associacao, + grupo_verificacao=grupo_verificacao_regularidade_documentos, + lista_verificacao=lista_verificacao_regularidade_documentos_associacao, + item_verificacao=item_verificacao_regularidade_documentos_associacao_cnpj, + regular=True + ) + + +@pytest.fixture +def tecnico_dre(dre): + return baker.make( + 'TecnicoDre', + dre=dre, + nome='José Testando', + rf='271170', + ) diff --git a/sme_ptrf_apps/dre/tests/tests_api_tecnicos_dre/__init__.py b/sme_ptrf_apps/dre/tests/tests_api_tecnicos_dre/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/sme_ptrf_apps/dre/tests/tests_api_tecnicos_dre/conftest.py b/sme_ptrf_apps/dre/tests/tests_api_tecnicos_dre/conftest.py new file mode 100644 index 000000000..a11b95468 --- /dev/null +++ b/sme_ptrf_apps/dre/tests/tests_api_tecnicos_dre/conftest.py @@ -0,0 +1,42 @@ +import pytest +from model_bakery import baker + + +@pytest.fixture +def dre_butantan(): + return baker.make( + 'core.Unidade', + codigo_eol='108100', + tipo_unidade='DRE', + nome='BUTANTAN', + sigla='BT' + ) + +@pytest.fixture +def dre_ipiranga(): + return baker.make( + 'core.Unidade', + codigo_eol='108600', + tipo_unidade='DRE', + nome='IPIRANGA', + sigla='IP' + ) + + +@pytest.fixture +def tecnico_jose_dre_ipiranga(dre_ipiranga): + return baker.make( + 'TecnicoDre', + dre=dre_ipiranga, + nome='José', + rf='271170', + ) + +@pytest.fixture +def tecnico_maria_dre_butantan(dre_butantan): + return baker.make( + 'TecnicoDre', + dre=dre_butantan, + nome='Maria', + rf='190889', + ) diff --git a/sme_ptrf_apps/dre/tests/tests_api_tecnicos_dre/test_tecnico_dre_create.py b/sme_ptrf_apps/dre/tests/tests_api_tecnicos_dre/test_tecnico_dre_create.py new file mode 100644 index 000000000..ee95a379d --- /dev/null +++ b/sme_ptrf_apps/dre/tests/tests_api_tecnicos_dre/test_tecnico_dre_create.py @@ -0,0 +1,51 @@ +import json + +import pytest +from rest_framework import status + +from sme_ptrf_apps.dre.models import TecnicoDre + +pytestmark = pytest.mark.django_db + + +@pytest.fixture +def payload_tecnico_dre(dre_butantan): + payload = { + 'dre': f'{dre_butantan.uuid}', + 'rf': '1234567', + 'nome': 'Pedro Antunes' + } + return payload + + +def test_create_tecnico_dre(jwt_authenticated_client, dre, payload_tecnico_dre): + response = jwt_authenticated_client.post( + '/api/tecnicos-dre/', data=json.dumps(payload_tecnico_dre), content_type='application/json') + + assert response.status_code == status.HTTP_201_CREATED + + result = json.loads(response.content) + + assert TecnicoDre.objects.filter(uuid=result['uuid']).exists() + + +@pytest.fixture +def payload_tecnico_dre_rf_ja_existente(dre_butantan, tecnico_maria_dre_butantan): + payload = { + 'dre': f'{dre_butantan.uuid}', + 'rf': tecnico_maria_dre_butantan.rf, + 'nome': 'Pedro Antunes' + } + return payload + + +def test_create_tecnico_dre_repetido(jwt_authenticated_client, dre, tecnico_maria_dre_butantan, + payload_tecnico_dre_rf_ja_existente): + response = jwt_authenticated_client.post( + '/api/tecnicos-dre/', data=json.dumps(payload_tecnico_dre_rf_ja_existente), content_type='application/json') + + assert response.status_code == status.HTTP_400_BAD_REQUEST + + result = json.loads(response.content) + + assert result == {'rf': ['Técnico de DRE with this RF already exists.']} diff --git a/sme_ptrf_apps/dre/tests/tests_api_tecnicos_dre/test_tecnico_dre_delete.py b/sme_ptrf_apps/dre/tests/tests_api_tecnicos_dre/test_tecnico_dre_delete.py new file mode 100644 index 000000000..5b1f923c1 --- /dev/null +++ b/sme_ptrf_apps/dre/tests/tests_api_tecnicos_dre/test_tecnico_dre_delete.py @@ -0,0 +1,16 @@ +import pytest +from rest_framework import status + +from sme_ptrf_apps.dre.models import TecnicoDre + +pytestmark = pytest.mark.django_db + +def test_delete_tecnico_dre(jwt_authenticated_client, tecnico_jose_dre_ipiranga): + assert TecnicoDre.objects.filter(uuid=tecnico_jose_dre_ipiranga.uuid).exists() + + response = jwt_authenticated_client.delete( + f'/api/tecnicos-dre/{tecnico_jose_dre_ipiranga.uuid}/', content_type='application/json') + + assert response.status_code == status.HTTP_204_NO_CONTENT + + assert not TecnicoDre.objects.filter(uuid=tecnico_jose_dre_ipiranga.uuid).exists() diff --git a/sme_ptrf_apps/dre/tests/tests_api_tecnicos_dre/test_tecnico_dre_retrieve.py b/sme_ptrf_apps/dre/tests/tests_api_tecnicos_dre/test_tecnico_dre_retrieve.py new file mode 100644 index 000000000..f543724c0 --- /dev/null +++ b/sme_ptrf_apps/dre/tests/tests_api_tecnicos_dre/test_tecnico_dre_retrieve.py @@ -0,0 +1,29 @@ +import json + +import pytest +from rest_framework import status + +pytestmark = pytest.mark.django_db + + +def test_retrieve_tecnico_dre( + jwt_authenticated_client, + tecnico_jose_dre_ipiranga): + response = jwt_authenticated_client.get( + f'/api/tecnicos-dre/{tecnico_jose_dre_ipiranga.uuid}/', content_type='application/json') + result = json.loads(response.content) + esperado = { + "uuid": f'{tecnico_jose_dre_ipiranga.uuid}', + "nome": tecnico_jose_dre_ipiranga.nome, + "rf": tecnico_jose_dre_ipiranga.rf, + "dre": { + 'uuid': f'{tecnico_jose_dre_ipiranga.dre.uuid}', + 'codigo_eol': f'{tecnico_jose_dre_ipiranga.dre.codigo_eol}', + 'tipo_unidade': f'{tecnico_jose_dre_ipiranga.dre.tipo_unidade}', + 'nome': f'{tecnico_jose_dre_ipiranga.dre.nome}', + 'sigla': f'{tecnico_jose_dre_ipiranga.dre.sigla}', + }, + } + + assert response.status_code == status.HTTP_200_OK + assert result == esperado diff --git a/sme_ptrf_apps/dre/tests/tests_api_tecnicos_dre/test_tecnico_dre_update.py b/sme_ptrf_apps/dre/tests/tests_api_tecnicos_dre/test_tecnico_dre_update.py new file mode 100644 index 000000000..7ae142f63 --- /dev/null +++ b/sme_ptrf_apps/dre/tests/tests_api_tecnicos_dre/test_tecnico_dre_update.py @@ -0,0 +1,33 @@ +import json + +import pytest +from rest_framework import status + +from sme_ptrf_apps.dre.models import TecnicoDre + +pytestmark = pytest.mark.django_db + + +@pytest.fixture +def payload_tecnico_altera_nome_para_pedro(dre_butantan): + payload = { + 'nome': 'Pedro' + } + return payload + + +def test_update_tecnico_dre(jwt_authenticated_client, dre_butantan, tecnico_maria_dre_butantan, + payload_tecnico_altera_nome_para_pedro): + + response = jwt_authenticated_client.put( + f'/api/tecnicos-dre/{tecnico_maria_dre_butantan.uuid}/', + data=json.dumps(payload_tecnico_altera_nome_para_pedro), + content_type='application/json') + + assert response.status_code == status.HTTP_200_OK + + result = json.loads(response.content) + + tecnico = TecnicoDre.objects.get(uuid=result['uuid']) + + assert tecnico.nome == 'Pedro' diff --git a/sme_ptrf_apps/dre/tests/tests_api_tecnicos_dre/test_tecnicos_dre_list.py b/sme_ptrf_apps/dre/tests/tests_api_tecnicos_dre/test_tecnicos_dre_list.py new file mode 100644 index 000000000..08f4ea09f --- /dev/null +++ b/sme_ptrf_apps/dre/tests/tests_api_tecnicos_dre/test_tecnicos_dre_list.py @@ -0,0 +1,90 @@ +import json + +import pytest +from rest_framework import status + +pytestmark = pytest.mark.django_db + + +def test_api_list_tecnicos_dre_todos(client, tecnico_jose_dre_ipiranga, tecnico_maria_dre_butantan): + response = client.get(f'/api/tecnicos-dre/', content_type='application/json') + result = json.loads(response.content) + + result_esperado = [ + { + "uuid": f'{tecnico_jose_dre_ipiranga.uuid}', + "nome": tecnico_jose_dre_ipiranga.nome, + "rf": tecnico_jose_dre_ipiranga.rf, + "dre": { + 'uuid': f'{tecnico_jose_dre_ipiranga.dre.uuid}', + 'codigo_eol': f'{tecnico_jose_dre_ipiranga.dre.codigo_eol}', + 'tipo_unidade': f'{tecnico_jose_dre_ipiranga.dre.tipo_unidade}', + 'nome': f'{tecnico_jose_dre_ipiranga.dre.nome}', + 'sigla': f'{tecnico_jose_dre_ipiranga.dre.sigla}', + }, + }, + { + "uuid": f'{tecnico_maria_dre_butantan.uuid}', + "nome": tecnico_maria_dre_butantan.nome, + "rf": tecnico_maria_dre_butantan.rf, + "dre": { + 'uuid': f'{tecnico_maria_dre_butantan.dre.uuid}', + 'codigo_eol': f'{tecnico_maria_dre_butantan.dre.codigo_eol}', + 'tipo_unidade': f'{tecnico_maria_dre_butantan.dre.tipo_unidade}', + 'nome': f'{tecnico_maria_dre_butantan.dre.nome}', + 'sigla': f'{tecnico_maria_dre_butantan.dre.sigla}', + }, + } + + ] + + assert response.status_code == status.HTTP_200_OK + assert result == result_esperado + + +def test_api_list_tecnicos_dre_ipiranga(client, tecnico_jose_dre_ipiranga, tecnico_maria_dre_butantan, dre_ipiranga): + response = client.get(f'/api/tecnicos-dre/?dre__uuid={dre_ipiranga.uuid}', content_type='application/json') + result = json.loads(response.content) + + result_esperado = [ + { + "uuid": f'{tecnico_jose_dre_ipiranga.uuid}', + "nome": tecnico_jose_dre_ipiranga.nome, + "rf": tecnico_jose_dre_ipiranga.rf, + "dre": { + 'uuid': f'{tecnico_jose_dre_ipiranga.dre.uuid}', + 'codigo_eol': f'{tecnico_jose_dre_ipiranga.dre.codigo_eol}', + 'tipo_unidade': f'{tecnico_jose_dre_ipiranga.dre.tipo_unidade}', + 'nome': f'{tecnico_jose_dre_ipiranga.dre.nome}', + 'sigla': f'{tecnico_jose_dre_ipiranga.dre.sigla}', + }, + } + + ] + + assert response.status_code == status.HTTP_200_OK + assert result == result_esperado + + +def test_api_list_tecnicos_dre_por_rf(client, tecnico_jose_dre_ipiranga, tecnico_maria_dre_butantan, dre_ipiranga): + response = client.get(f'/api/tecnicos-dre/?rf={tecnico_jose_dre_ipiranga.rf}', content_type='application/json') + result = json.loads(response.content) + + result_esperado = [ + { + "uuid": f'{tecnico_jose_dre_ipiranga.uuid}', + "nome": tecnico_jose_dre_ipiranga.nome, + "rf": tecnico_jose_dre_ipiranga.rf, + "dre": { + 'uuid': f'{tecnico_jose_dre_ipiranga.dre.uuid}', + 'codigo_eol': f'{tecnico_jose_dre_ipiranga.dre.codigo_eol}', + 'tipo_unidade': f'{tecnico_jose_dre_ipiranga.dre.tipo_unidade}', + 'nome': f'{tecnico_jose_dre_ipiranga.dre.nome}', + 'sigla': f'{tecnico_jose_dre_ipiranga.dre.sigla}', + }, + } + + ] + + assert response.status_code == status.HTTP_200_OK + assert result == result_esperado diff --git a/sme_ptrf_apps/dre/tests/tests_grupo_verificacao_regularidade/__init__.py b/sme_ptrf_apps/dre/tests/tests_grupo_verificacao_regularidade/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/sme_ptrf_apps/dre/tests/tests_grupo_verificacao_regularidade/test_grupo_verificacao_regularidade_model.py b/sme_ptrf_apps/dre/tests/tests_grupo_verificacao_regularidade/test_grupo_verificacao_regularidade_model.py new file mode 100644 index 000000000..4865026e4 --- /dev/null +++ b/sme_ptrf_apps/dre/tests/tests_grupo_verificacao_regularidade/test_grupo_verificacao_regularidade_model.py @@ -0,0 +1,28 @@ +import pytest +from django.contrib import admin + +from ...models import GrupoVerificacaoRegularidade + +pytestmark = pytest.mark.django_db + +def test_instance_model(grupo_verificacao_regularidade_documentos): + model = grupo_verificacao_regularidade_documentos + assert isinstance(model, GrupoVerificacaoRegularidade) + assert model.titulo + assert model.uuid + assert model.id + assert model.listas_de_verificacao + + +def test_srt_model(grupo_verificacao_regularidade_documentos): + assert grupo_verificacao_regularidade_documentos.__str__() == 'Documentos' + + +def test_meta_modelo(grupo_verificacao_regularidade_documentos): + assert grupo_verificacao_regularidade_documentos._meta.verbose_name == 'Grupo de verificação de regularidade' + assert grupo_verificacao_regularidade_documentos._meta.verbose_name_plural == 'Grupos de verificação de regularidade' + + +def test_admin(): + # pylint: disable=W0212 + assert admin.site._registry[GrupoVerificacaoRegularidade] diff --git a/sme_ptrf_apps/dre/tests/tests_item_verificacao_regularidade/__init__.py b/sme_ptrf_apps/dre/tests/tests_item_verificacao_regularidade/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/sme_ptrf_apps/dre/tests/tests_item_verificacao_regularidade/test_item_verificacao_regularidade_model.py b/sme_ptrf_apps/dre/tests/tests_item_verificacao_regularidade/test_item_verificacao_regularidade_model.py new file mode 100644 index 000000000..bbb70315c --- /dev/null +++ b/sme_ptrf_apps/dre/tests/tests_item_verificacao_regularidade/test_item_verificacao_regularidade_model.py @@ -0,0 +1,28 @@ +import pytest +from django.contrib import admin + +from ...models import ListaVerificacaoRegularidade, ItemVerificacaoRegularidade + +pytestmark = pytest.mark.django_db + +def test_instance_model(item_verificacao_regularidade_documentos_associacao_cnpj): + model = item_verificacao_regularidade_documentos_associacao_cnpj + assert isinstance(model, ItemVerificacaoRegularidade) + assert isinstance(model.lista, ListaVerificacaoRegularidade) + assert model.descricao + assert model.uuid + assert model.id + + +def test_srt_model(item_verificacao_regularidade_documentos_associacao_cnpj): + assert item_verificacao_regularidade_documentos_associacao_cnpj.__str__() == 'CNPJ' + + +def test_meta_modelo(item_verificacao_regularidade_documentos_associacao_cnpj): + assert item_verificacao_regularidade_documentos_associacao_cnpj._meta.verbose_name == 'Item de verificação de regularidade' + assert item_verificacao_regularidade_documentos_associacao_cnpj._meta.verbose_name_plural == 'Itens de verificação de regularidade' + + +def test_admin(): + # pylint: disable=W0212 + assert admin.site._registry[ItemVerificacaoRegularidade] diff --git a/sme_ptrf_apps/dre/tests/tests_lista_verificacao_regularidade/__init__.py b/sme_ptrf_apps/dre/tests/tests_lista_verificacao_regularidade/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/sme_ptrf_apps/dre/tests/tests_lista_verificacao_regularidade/test_lista_verificacao_regularidade_model.py b/sme_ptrf_apps/dre/tests/tests_lista_verificacao_regularidade/test_lista_verificacao_regularidade_model.py new file mode 100644 index 000000000..525edcb28 --- /dev/null +++ b/sme_ptrf_apps/dre/tests/tests_lista_verificacao_regularidade/test_lista_verificacao_regularidade_model.py @@ -0,0 +1,29 @@ +import pytest +from django.contrib import admin + +from ...models import GrupoVerificacaoRegularidade, ListaVerificacaoRegularidade + +pytestmark = pytest.mark.django_db + +def test_instance_model(lista_verificacao_regularidade_documentos_associacao): + model = lista_verificacao_regularidade_documentos_associacao + assert isinstance(model, ListaVerificacaoRegularidade) + assert isinstance(model.grupo, GrupoVerificacaoRegularidade) + assert model.titulo + assert model.uuid + assert model.id + assert model.itens_de_verificacao + + +def test_srt_model(lista_verificacao_regularidade_documentos_associacao): + assert lista_verificacao_regularidade_documentos_associacao.__str__() == 'Documentos da Associação' + + +def test_meta_modelo(lista_verificacao_regularidade_documentos_associacao): + assert lista_verificacao_regularidade_documentos_associacao._meta.verbose_name == 'Lista de verificação de regularidade' + assert lista_verificacao_regularidade_documentos_associacao._meta.verbose_name_plural == 'Listas de verificação de regularidade' + + +def test_admin(): + # pylint: disable=W0212 + assert admin.site._registry[ListaVerificacaoRegularidade] diff --git a/sme_ptrf_apps/dre/tests/tests_services/__init__.py b/sme_ptrf_apps/dre/tests/tests_services/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/sme_ptrf_apps/dre/tests/tests_services/test_verificacao_regularidade_associacao_service.py b/sme_ptrf_apps/dre/tests/tests_services/test_verificacao_regularidade_associacao_service.py new file mode 100644 index 000000000..ca357a7cb --- /dev/null +++ b/sme_ptrf_apps/dre/tests/tests_services/test_verificacao_regularidade_associacao_service.py @@ -0,0 +1,188 @@ +import pytest +from model_bakery import baker + +from ...services import verifica_regularidade_associacao + +pytestmark = pytest.mark.django_db + + +@pytest.fixture +def grupo_verificacao_regularidade_documentos(): + return baker.make('dre.GrupoVerificacaoRegularidade', titulo='Documentos') + + +@pytest.fixture +def lista_verificacao_regularidade_documentos_associacao(grupo_verificacao_regularidade_documentos): + return baker.make( + 'dre.ListaVerificacaoRegularidade', + titulo='Documentos da Associação', + grupo=grupo_verificacao_regularidade_documentos + ) + + +@pytest.fixture +def item_verificacao_regularidade_documentos_associacao_cnpj(lista_verificacao_regularidade_documentos_associacao): + return baker.make( + 'dre.ItemVerificacaoRegularidade', + descricao='CNPJ', + lista=lista_verificacao_regularidade_documentos_associacao + ) + + +@pytest.fixture +def verificacao_regularidade_associacao_documento_cnpj_regular(grupo_verificacao_regularidade_documentos, + lista_verificacao_regularidade_documentos_associacao, + item_verificacao_regularidade_documentos_associacao_cnpj, + associacao): + return baker.make( + 'dre.VerificacaoRegularidadeAssociacao', + associacao=associacao, + grupo_verificacao=grupo_verificacao_regularidade_documentos, + lista_verificacao=lista_verificacao_regularidade_documentos_associacao, + item_verificacao=item_verificacao_regularidade_documentos_associacao_cnpj, + regular=True + ) + + +@pytest.fixture +def item_verificacao_regularidade_documentos_associacao_rais(lista_verificacao_regularidade_documentos_associacao): + return baker.make( + 'dre.ItemVerificacaoRegularidade', + descricao='RAIS', + lista=lista_verificacao_regularidade_documentos_associacao + ) + + +@pytest.fixture +def verificacao_regularidade_associacao_documento_rais_irregular(grupo_verificacao_regularidade_documentos, + lista_verificacao_regularidade_documentos_associacao, + item_verificacao_regularidade_documentos_associacao_rais, + associacao): + return baker.make( + 'dre.VerificacaoRegularidadeAssociacao', + associacao=associacao, + grupo_verificacao=grupo_verificacao_regularidade_documentos, + lista_verificacao=lista_verificacao_regularidade_documentos_associacao, + item_verificacao=item_verificacao_regularidade_documentos_associacao_rais, + regular=False + ) + + +def test_verificacao_regularidade_associacao_regular(client, associacao, + grupo_verificacao_regularidade_documentos, + lista_verificacao_regularidade_documentos_associacao, + item_verificacao_regularidade_documentos_associacao_cnpj, + verificacao_regularidade_associacao_documento_cnpj_regular + ): + result = verifica_regularidade_associacao(associacao.uuid) + + esperado = { + 'uuid': f'{associacao.uuid}', + 'verificacao_regularidade': { + 'grupos_verificacao': [ + { + 'uuid': f'{grupo_verificacao_regularidade_documentos.uuid}', + 'titulo': grupo_verificacao_regularidade_documentos.titulo, + 'listas_verificacao': [ + { + 'uuid': f'{lista_verificacao_regularidade_documentos_associacao.uuid}', + 'titulo': f'{lista_verificacao_regularidade_documentos_associacao.titulo}', + 'itens_verificacao': [ + { + 'uuid': f'{item_verificacao_regularidade_documentos_associacao_cnpj.uuid}', + 'descricao': item_verificacao_regularidade_documentos_associacao_cnpj.descricao, + 'regular': True + }, + ], + 'status_lista_verificacao': 'Regular' + }, + ] + + }, + ] + } + } + + assert result == esperado + + +def test_verificacao_regularidade_associacao_pendente_quando_sem_verificacao(client, associacao, + grupo_verificacao_regularidade_documentos, + lista_verificacao_regularidade_documentos_associacao, + item_verificacao_regularidade_documentos_associacao_cnpj + ): + result = verifica_regularidade_associacao(associacao.uuid) + + esperado = { + 'uuid': f'{associacao.uuid}', + 'verificacao_regularidade': { + 'grupos_verificacao': [ + { + 'uuid': f'{grupo_verificacao_regularidade_documentos.uuid}', + 'titulo': grupo_verificacao_regularidade_documentos.titulo, + 'listas_verificacao': [ + { + 'uuid': f'{lista_verificacao_regularidade_documentos_associacao.uuid}', + 'titulo': f'{lista_verificacao_regularidade_documentos_associacao.titulo}', + 'itens_verificacao': [ + { + 'uuid': f'{item_verificacao_regularidade_documentos_associacao_cnpj.uuid}', + 'descricao': item_verificacao_regularidade_documentos_associacao_cnpj.descricao, + 'regular': False + }, + ], + 'status_lista_verificacao': 'Pendente' + }, + ] + + }, + ] + } + } + + assert result == esperado + + +def test_verificacao_regularidade_associacao_pendente_quando_com_verificacao_irregular(client, associacao, + grupo_verificacao_regularidade_documentos, + lista_verificacao_regularidade_documentos_associacao, + item_verificacao_regularidade_documentos_associacao_cnpj, + item_verificacao_regularidade_documentos_associacao_rais, + verificacao_regularidade_associacao_documento_cnpj_regular, + verificacao_regularidade_associacao_documento_rais_irregular + ): + result = verifica_regularidade_associacao(associacao.uuid) + + esperado = { + 'uuid': f'{associacao.uuid}', + 'verificacao_regularidade': { + 'grupos_verificacao': [ + { + 'uuid': f'{grupo_verificacao_regularidade_documentos.uuid}', + 'titulo': grupo_verificacao_regularidade_documentos.titulo, + 'listas_verificacao': [ + { + 'uuid': f'{lista_verificacao_regularidade_documentos_associacao.uuid}', + 'titulo': f'{lista_verificacao_regularidade_documentos_associacao.titulo}', + 'itens_verificacao': [ + { + 'uuid': f'{item_verificacao_regularidade_documentos_associacao_cnpj.uuid}', + 'descricao': item_verificacao_regularidade_documentos_associacao_cnpj.descricao, + 'regular': True + }, + { + 'uuid': f'{item_verificacao_regularidade_documentos_associacao_rais.uuid}', + 'descricao': item_verificacao_regularidade_documentos_associacao_rais.descricao, + 'regular': False + }, + ], + 'status_lista_verificacao': 'Pendente' + }, + ] + + }, + ] + } + } + + assert result == esperado diff --git a/sme_ptrf_apps/dre/tests/tests_tecnico_dre/__init__.py b/sme_ptrf_apps/dre/tests/tests_tecnico_dre/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/sme_ptrf_apps/dre/tests/tests_tecnico_dre/test_tecnico_dre_model.py b/sme_ptrf_apps/dre/tests/tests_tecnico_dre/test_tecnico_dre_model.py new file mode 100644 index 000000000..5fe741f10 --- /dev/null +++ b/sme_ptrf_apps/dre/tests/tests_tecnico_dre/test_tecnico_dre_model.py @@ -0,0 +1,30 @@ +import pytest +from django.contrib import admin + +from ...models import TecnicoDre +from ....core.models import Unidade + +pytestmark = pytest.mark.django_db + +def test_instance_model(tecnico_dre): + model = tecnico_dre + assert isinstance(model, TecnicoDre) + assert isinstance(model.dre, Unidade) + assert model.nome + assert model.rf + assert model.id + assert model.uuid + + +def test_srt_model(tecnico_dre): + assert tecnico_dre.__str__() == 'Nome: José Testando, RF: 271170' + + +def test_meta_modelo(tecnico_dre): + assert tecnico_dre._meta.verbose_name == 'Técnico de DRE' + assert tecnico_dre._meta.verbose_name_plural == 'Técnicos de DREs' + + +def test_admin(): + # pylint: disable=W0212 + assert admin.site._registry[TecnicoDre] diff --git a/sme_ptrf_apps/dre/tests/tests_tecnico_dre/test_tecnico_dre_serializer.py b/sme_ptrf_apps/dre/tests/tests_tecnico_dre/test_tecnico_dre_serializer.py new file mode 100644 index 000000000..e80bf4249 --- /dev/null +++ b/sme_ptrf_apps/dre/tests/tests_tecnico_dre/test_tecnico_dre_serializer.py @@ -0,0 +1,35 @@ +import pytest + +from ...api.serializers.tecnico_dre_serializer import (TecnicoDreSerializer, TecnicoDreLookUpSerializer, + TecnicoDreCreateSerializer) + +pytestmark = pytest.mark.django_db + + +def test_serializer(tecnico_dre): + serializer = TecnicoDreSerializer(tecnico_dre) + + assert serializer.data is not None + assert serializer.data['uuid'] + assert serializer.data['dre'] + assert serializer.data['rf'] + assert serializer.data['nome'] + + +def test_lookup_serializer(tecnico_dre): + serializer = TecnicoDreLookUpSerializer(tecnico_dre) + + assert serializer.data is not None + assert serializer.data['uuid'] + assert serializer.data['rf'] + assert serializer.data['nome'] + +def test_create_serializer(tecnico_dre): + serializer = TecnicoDreCreateSerializer(tecnico_dre) + + assert serializer.data is not None + assert serializer.data['uuid'] + assert serializer.data['dre'] + assert serializer.data['rf'] + assert serializer.data['nome'] + diff --git a/sme_ptrf_apps/dre/tests/tests_tecnico_dre/test_tecnico_dre_viewset.py b/sme_ptrf_apps/dre/tests/tests_tecnico_dre/test_tecnico_dre_viewset.py new file mode 100644 index 000000000..ddaada060 --- /dev/null +++ b/sme_ptrf_apps/dre/tests/tests_tecnico_dre/test_tecnico_dre_viewset.py @@ -0,0 +1,17 @@ +import pytest +from rest_framework import status +from rest_framework.test import APIRequestFactory +from rest_framework.test import force_authenticate + +from ...api.views.tecnicos_dre_viewset import TecnicosDreViewSet + +pytestmark = pytest.mark.django_db + + +def test_view_set(tecnico_dre, fake_user): + request = APIRequestFactory().get("") + detalhe = TecnicosDreViewSet.as_view({'get': 'retrieve'}) + force_authenticate(request, user=fake_user) + response = detalhe(request, uuid=tecnico_dre.uuid) + + assert response.status_code == status.HTTP_200_OK diff --git a/sme_ptrf_apps/dre/tests/tests_verificacao_regularidade_associacao/__init__.py b/sme_ptrf_apps/dre/tests/tests_verificacao_regularidade_associacao/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/sme_ptrf_apps/dre/tests/tests_verificacao_regularidade_associacao/test_verificacao_regularidade_associacao_model.py b/sme_ptrf_apps/dre/tests/tests_verificacao_regularidade_associacao/test_verificacao_regularidade_associacao_model.py new file mode 100644 index 000000000..8a3c8903e --- /dev/null +++ b/sme_ptrf_apps/dre/tests/tests_verificacao_regularidade_associacao/test_verificacao_regularidade_associacao_model.py @@ -0,0 +1,34 @@ +import pytest +from django.contrib import admin + +from ...models import (GrupoVerificacaoRegularidade, ListaVerificacaoRegularidade, ItemVerificacaoRegularidade, + VerificacaoRegularidadeAssociacao) +from ....core.models import Associacao + +pytestmark = pytest.mark.django_db + + +def test_instance_model(verificacao_regularidade_associacao_documento_cnpj): + model = verificacao_regularidade_associacao_documento_cnpj + assert isinstance(model, VerificacaoRegularidadeAssociacao) + assert isinstance(model.associacao, Associacao) + assert isinstance(model.grupo_verificacao, GrupoVerificacaoRegularidade) + assert isinstance(model.lista_verificacao, ListaVerificacaoRegularidade) + assert isinstance(model.item_verificacao, ItemVerificacaoRegularidade) + assert model.regular + assert model.uuid + assert model.id + + +def test_srt_model(verificacao_regularidade_associacao_documento_cnpj): + assert verificacao_regularidade_associacao_documento_cnpj.__str__() == 'CNPJ - Regular' + + +def test_meta_modelo(verificacao_regularidade_associacao_documento_cnpj): + assert verificacao_regularidade_associacao_documento_cnpj._meta.verbose_name == 'Verificação de regularidade de associação' + assert verificacao_regularidade_associacao_documento_cnpj._meta.verbose_name_plural == 'Verificações de regularidade de associações' + + +def test_admin(): + # pylint: disable=W0212 + assert admin.site._registry[VerificacaoRegularidadeAssociacao] diff --git a/sme_ptrf_apps/receitas/api/serializers/receita_serializer.py b/sme_ptrf_apps/receitas/api/serializers/receita_serializer.py index 8dadf0536..5daad7ad5 100644 --- a/sme_ptrf_apps/receitas/api/serializers/receita_serializer.py +++ b/sme_ptrf_apps/receitas/api/serializers/receita_serializer.py @@ -84,4 +84,5 @@ class Meta: 'detalhe_tipo_receita', 'detalhe_outros', 'referencia_devolucao', + 'notificar_dias_nao_conferido' ) diff --git a/sme_ptrf_apps/receitas/api/views/receita_viewset.py b/sme_ptrf_apps/receitas/api/views/receita_viewset.py index 48f6057dc..019a5d880 100644 --- a/sme_ptrf_apps/receitas/api/views/receita_viewset.py +++ b/sme_ptrf_apps/receitas/api/views/receita_viewset.py @@ -38,9 +38,15 @@ def get_serializer_class(self): return ReceitaCreateSerializer def get_queryset(self): - user = self.request.user + associacao_uuid = self.request.query_params.get('associacao_uuid') or self.request.query_params.get('associacao__uuid') + if associacao_uuid is None: + erro = { + 'erro': 'parametros_requerido', + 'mensagem': 'É necessário enviar o uuid da associação como parâmetro.' + } + return Response(erro, status=status.HTTP_400_BAD_REQUEST) - qs = Receita.objects.filter(associacao=user.associacao).all().order_by('-data') + qs = Receita.objects.filter(associacao__uuid=associacao_uuid).all().order_by('-data') data_inicio = self.request.query_params.get('data_inicio') data_fim = self.request.query_params.get('data_fim') @@ -57,16 +63,25 @@ def get_queryset(self): @action(detail=False, url_path='tabelas') def tabelas(self, request): - def get_valores_from(serializer): - valores = serializer.Meta.model.get_valores(user=request.user) + associacao_uuid = request.query_params.get('associacao_uuid') + + if associacao_uuid is None: + erro = { + 'erro': 'parametros_requerido', + 'mensagem': 'É necessário enviar o uuid da associação (associacao_uuid) como parâmetro.' + } + return Response(erro, status=status.HTTP_400_BAD_REQUEST) + + def get_valores_from(serializer, associacao_uuid): + valores = serializer.Meta.model.get_valores(user=request.user, associacao_uuid=associacao_uuid) return serializer(valores, many=True).data if valores else [] result = { - 'tipos_receita': get_valores_from(TipoReceitaEDetalhesSerializer), + 'tipos_receita': get_valores_from(TipoReceitaEDetalhesSerializer, associacao_uuid=associacao_uuid), 'categorias_receita': aplicacoes_recurso_to_json(), - 'acoes_associacao': get_valores_from(AcaoAssociacaoLookUpSerializer), - 'contas_associacao': get_valores_from(ContaAssociacaoLookUpSerializer), - 'periodos': get_valores_from(PeriodoLookUpSerializer), + 'acoes_associacao': get_valores_from(AcaoAssociacaoLookUpSerializer, associacao_uuid=associacao_uuid), + 'contas_associacao': get_valores_from(ContaAssociacaoLookUpSerializer, associacao_uuid=associacao_uuid), + 'periodos': get_valores_from(PeriodoLookUpSerializer, associacao_uuid=associacao_uuid), } return Response(result) diff --git a/sme_ptrf_apps/receitas/models/receita.py b/sme_ptrf_apps/receitas/models/receita.py index 3dd1b0e57..e8e24e3e8 100644 --- a/sme_ptrf_apps/receitas/models/receita.py +++ b/sme_ptrf_apps/receitas/models/receita.py @@ -1,3 +1,4 @@ +from datetime import date from decimal import Decimal from auditlog.models import AuditlogHistoryField @@ -6,7 +7,7 @@ from django.db.models.signals import pre_save from django.dispatch import receiver -from sme_ptrf_apps.core.models import Associacao, Periodo +from sme_ptrf_apps.core.models import Associacao, Periodo, Parametros from sme_ptrf_apps.core.models_abstracts import ModeloBase from ..tipos_aplicacao_recurso_receitas import APLICACAO_CAPITAL, APLICACAO_CHOICES, APLICACAO_CUSTEIO @@ -50,7 +51,7 @@ class Receita(ModeloBase): detalhe_outros = models.CharField('Detalhe da despesa (outros)', max_length=160, blank=True, default='') referencia_devolucao = models.ForeignKey(Periodo, on_delete=models.PROTECT, - related_name='+', blank=True, null=True) + related_name='+', blank=True, null=True) def __str__(self): return f'RECEITA<{self.detalhamento} - {self.data} - {self.valor}>' @@ -63,6 +64,20 @@ def detalhamento(self): detalhe = self.detalhe_outros return detalhe + @property + def notificar_dias_nao_conferido(self): + """ + Se não conferida, retorna o tempo decorrido desde o lançamento, caso esse tempo seja superior ao parametrizado. + Caso contrário, retorna 0 + :rtype: int + """ + result = 0 + if not self.conferido: + decorrido = (date.today() - self.data).days + limite = Parametros.get().tempo_notificar_nao_demonstrados + result = decorrido if decorrido >= limite else 0 + return result + @classmethod def receitas_da_acao_associacao_no_periodo(cls, acao_associacao, periodo, conferido=None, conta_associacao=None, categoria_receita=None): @@ -141,7 +156,7 @@ def totais_por_acao_associacao_no_periodo(cls, acao_associacao, periodo, conta=N totais['total_receitas_nao_conciliadas_custeio'] += receita.valor else: totais['total_receitas_nao_conciliadas_livre'] += receita.valor - + if receita.tipo_receita.e_devolucao: if receita.categoria_receita == APLICACAO_CAPITAL: totais['total_receitas_devolucao_capital'] += receita.valor diff --git a/sme_ptrf_apps/receitas/tests/conftest.py b/sme_ptrf_apps/receitas/tests/conftest.py index 3e53f5a03..ac67db84c 100644 --- a/sme_ptrf_apps/receitas/tests/conftest.py +++ b/sme_ptrf_apps/receitas/tests/conftest.py @@ -6,7 +6,8 @@ @pytest.fixture def tipo_receita(): - return baker.make('TipoReceita', nome='Estorno', e_repasse=False, aceita_capital=False, aceita_custeio=False, e_devolucao=False) + return baker.make('TipoReceita', nome='Estorno', e_repasse=False, aceita_capital=False, aceita_custeio=False, + e_devolucao=False) @pytest.fixture @@ -45,7 +46,7 @@ def receita(associacao, conta_associacao, acao_associacao, tipo_receita, prestac @pytest.fixture def receita_devolucao(associacao, conta_associacao, acao_associacao, tipo_receita_devolucao, prestacao_conta_iniciada, - detalhe_tipo_receita, periodo): + detalhe_tipo_receita, periodo): return baker.make( 'Receita', associacao=associacao, @@ -96,6 +97,7 @@ def payload_receita(associacao, conta_associacao, acao_associacao, tipo_receita, } return payload + @pytest.fixture def payload_receita_livre_aplicacao(associacao, conta_associacao, acao_associacao, tipo_receita, detalhe_tipo_receita): payload = { @@ -112,7 +114,6 @@ def payload_receita_livre_aplicacao(associacao, conta_associacao, acao_associaca return payload - @pytest.fixture def payload_receita_repasse(associacao, conta_associacao, acao_associacao, tipo_receita_repasse): payload = { @@ -126,6 +127,7 @@ def payload_receita_repasse(associacao, conta_associacao, acao_associacao, tipo_ } return payload + @pytest.fixture def payload_receita_repasse_livre_aplicacao(associacao, conta_associacao, acao_associacao, tipo_receita_repasse): payload = { @@ -171,9 +173,11 @@ def receita_yyy_repasse(associacao, conta_associacao_cartao, acao_associacao_rol detalhe_tipo_receita=detalhe_tipo_receita_repasse ) + @pytest.fixture -def receita_repasse_livre_aplicacao(associacao, conta_associacao_cartao, acao_associacao_role_cultural, tipo_receita_repasse, - repasse_realizado_livre_aplicacao, detalhe_tipo_receita_repasse): +def receita_repasse_livre_aplicacao(associacao, conta_associacao_cartao, acao_associacao_role_cultural, + tipo_receita_repasse, + repasse_realizado_livre_aplicacao, detalhe_tipo_receita_repasse): return baker.make( 'Receita', associacao=associacao, @@ -189,7 +193,6 @@ def receita_repasse_livre_aplicacao(associacao, conta_associacao_cartao, acao_as ) - @pytest.fixture def receita_conferida(receita_xxx_estorno): return receita_xxx_estorno @@ -200,6 +203,24 @@ def receita_nao_conferida(receita_yyy_repasse): return receita_yyy_repasse +@pytest.fixture +def receita_nao_conferida_desde_01_03_2020(associacao, conta_associacao_cartao, acao_associacao_role_cultural, + tipo_receita_repasse, + repasse_realizado, detalhe_tipo_receita_repasse): + return baker.make( + 'Receita', + associacao=associacao, + data=datetime.date(2020, 3, 1), + valor=100.00, + conta_associacao=conta_associacao_cartao, + acao_associacao=acao_associacao_role_cultural, + tipo_receita=tipo_receita_repasse, + conferido=False, + repasse=repasse_realizado, + detalhe_tipo_receita=detalhe_tipo_receita_repasse + ) + + @pytest.fixture def receita_2020_3_10(associacao, conta_associacao_cheque, acao_associacao_ptrf, tipo_receita_estorno): return baker.make( @@ -258,6 +279,7 @@ def repasse_2020_1_capital_pendente(associacao, conta_associacao, acao_associaca realizado_custeio=True ) + @pytest.fixture def repasse_2020_1_custeio_pendente(associacao, conta_associacao, acao_associacao, periodo_2020_1): return baker.make( @@ -273,6 +295,7 @@ def repasse_2020_1_custeio_pendente(associacao, conta_associacao, acao_associaca realizado_custeio=False ) + @pytest.fixture def repasse_2020_1_livre_aplicacao_pendente(associacao, conta_associacao, acao_associacao, periodo_2020_1): return baker.make( @@ -304,6 +327,7 @@ def repasse_2020_1_pendente(associacao, conta_associacao, acao_associacao, perio realizado_custeio=False ) + @pytest.fixture def repasse_2020_1_realizado(associacao, conta_associacao, acao_associacao, periodo_2020_1): return baker.make( @@ -320,7 +344,6 @@ def repasse_2020_1_realizado(associacao, conta_associacao, acao_associacao, peri ) - @pytest.fixture def repasse_realizado(associacao, conta_associacao, acao_associacao_role_cultural, periodo): return baker.make( diff --git a/sme_ptrf_apps/receitas/tests/test_receita/test_model.py b/sme_ptrf_apps/receitas/tests/test_receita/test_model.py index 851c4e5b2..c261bcb18 100644 --- a/sme_ptrf_apps/receitas/tests/test_receita/test_model.py +++ b/sme_ptrf_apps/receitas/tests/test_receita/test_model.py @@ -1,4 +1,5 @@ import pytest +from freezegun import freeze_time from sme_ptrf_apps.receitas.models import Receita from ...tipos_aplicacao_recurso_receitas import APLICACAO_LIVRE @@ -19,6 +20,8 @@ def test_instance(receita): assert model.prestacao_conta assert model.detalhe_tipo_receita assert model.detalhe_outros is not None + assert model.notificar_dias_nao_conferido is not None + def test_instance_receita_devolucao(receita_devolucao): model = receita_devolucao @@ -39,11 +42,13 @@ def test_instance_receita_devolucao(receita_devolucao): def test_str(receita): assert str(receita) == "RECEITA" + def test_marcar_conferido(receita_nao_conferida): receita_nao_conferida.marcar_conferido() receita = Receita.objects.get(id=receita_nao_conferida.id) assert receita.conferido + def test_desmarcar_conferido(receita_conferida): receita_conferida.desmarcar_conferido() receita = Receita.objects.get(id=receita_conferida.id) @@ -54,3 +59,19 @@ def test_receita_livre_utilizacao(receita): receita.categoria_receita = APLICACAO_LIVRE receita.save() assert Receita.by_id(receita.id).categoria_receita == APLICACAO_LIVRE + + +@freeze_time('2020-03-12') +def test_notificar_nao_conferido(receita_nao_conferida_desde_01_03_2020, parametros_tempo_nao_conferido_10_dias): + assert receita_nao_conferida_desde_01_03_2020.notificar_dias_nao_conferido == 11 + + +@freeze_time('2020-03-12') +def test_notificar_nao_conferido_quando_limite_e_superior(receita_nao_conferida_desde_01_03_2020, + parametros_tempo_nao_conferido_60_dias): + assert receita_nao_conferida_desde_01_03_2020.notificar_dias_nao_conferido == 0 + + +@freeze_time('2020-03-12') +def test_notificar_nao_conferido_quando_conferido(receita_conferida, parametros_tempo_nao_conferido_10_dias): + assert receita_conferida.notificar_dias_nao_conferido == 0 diff --git a/sme_ptrf_apps/receitas/tests/test_receita/test_serializer.py b/sme_ptrf_apps/receitas/tests/test_receita/test_serializer.py index 7bdb6ba02..478a0add8 100644 --- a/sme_ptrf_apps/receitas/tests/test_receita/test_serializer.py +++ b/sme_ptrf_apps/receitas/tests/test_receita/test_serializer.py @@ -23,10 +23,11 @@ def test_list_serializer(receita, detalhe_tipo_receita): 'conta_associacao', 'conferido', 'detalhe_tipo_receita', + 'notificar_dias_nao_conferido' ) for field in expected_fields: - assert serializer.data[field], f'Não encontrado o campo {field} no serializer ReceitaLista.' + assert serializer.data[field] is not None, f'Não encontrado o campo {field} no serializer ReceitaLista.' def test_list_serializer_sem_detalhe_tipo_receita(receita_sem_detalhe_tipo_receita): diff --git a/sme_ptrf_apps/receitas/tests/tests_api_receitas/test_api.py b/sme_ptrf_apps/receitas/tests/tests_api_receitas/test_api.py index e7775c9d2..ff2232bcf 100644 --- a/sme_ptrf_apps/receitas/tests/tests_api_receitas/test_api.py +++ b/sme_ptrf_apps/receitas/tests/tests_api_receitas/test_api.py @@ -115,7 +115,7 @@ def test_get_tabelas( conta_associacao, detalhe_tipo_receita ): - response = jwt_authenticated_client.get('/api/receitas/tabelas/', content_type='application/json') + response = jwt_authenticated_client.get(f'/api/receitas/tabelas/?associacao_uuid={associacao.uuid}', content_type='application/json') result = json.loads(response.content) """ @@ -197,7 +197,7 @@ def test_get_receitas( associacao, tipo_conta, conta_associacao): - response = jwt_authenticated_client.get('/api/receitas/', content_type='application/json') + response = jwt_authenticated_client.get(f'/api/receitas/?associacao_uuid={associacao.uuid}', content_type='application/json') result = json.loads(response.content) results = [ @@ -230,7 +230,8 @@ def test_get_receitas( 'id': detalhe_tipo_receita.id, 'nome': detalhe_tipo_receita.nome }, - 'detalhe_outros': receita.detalhe_outros + 'detalhe_outros': receita.detalhe_outros, + 'notificar_dias_nao_conferido': 0 }, ] @@ -252,7 +253,7 @@ def test_update_receita( receita, payload_receita ): - response = jwt_authenticated_client.put(f'/api/receitas/{receita.uuid}/', data=json.dumps(payload_receita), + response = jwt_authenticated_client.put(f'/api/receitas/{receita.uuid}/?associacao_uuid={associacao.uuid}', data=json.dumps(payload_receita), content_type='application/json') assert response.status_code == status.HTTP_200_OK @@ -277,7 +278,7 @@ def test_deleta_receita( payload_receita): assert Receita.objects.filter(uuid=receita.uuid).exists() - response = jwt_authenticated_client.delete(f'/api/receitas/{receita.uuid}/', content_type='application/json') + response = jwt_authenticated_client.delete(f'/api/receitas/{receita.uuid}/?associacao_uuid={associacao.uuid}', content_type='application/json') assert response.status_code == status.HTTP_204_NO_CONTENT @@ -299,7 +300,7 @@ def test_deleta_receita_repasse( assert Receita.objects.filter(uuid=receita_yyy_repasse.uuid).exists() - response = jwt_authenticated_client.delete(f'/api/receitas/{receita_yyy_repasse.uuid}/', + response = jwt_authenticated_client.delete(f'/api/receitas/{receita_yyy_repasse.uuid}/?associacao_uuid={associacao.uuid}', content_type='application/json') assert response.status_code == status.HTTP_204_NO_CONTENT @@ -319,7 +320,7 @@ def test_retrive_receitas( associacao, tipo_conta, conta_associacao): - response = jwt_authenticated_client.get(f'/api/receitas/{receita.uuid}/', content_type='application/json') + response = jwt_authenticated_client.get(f'/api/receitas/{receita.uuid}/?associacao_uuid={associacao.uuid}', content_type='application/json') result = json.loads(response.content) esperado = { @@ -351,7 +352,8 @@ def test_retrive_receitas( 'id': detalhe_tipo_receita.id, 'nome': detalhe_tipo_receita.nome }, - 'detalhe_outros': receita.detalhe_outros + 'detalhe_outros': receita.detalhe_outros, + 'notificar_dias_nao_conferido': 0 } assert response.status_code == status.HTTP_200_OK @@ -452,7 +454,7 @@ def test_deleta_receita_repasse_livre_aplicacao( assert Receita.objects.filter(uuid=receita_repasse_livre_aplicacao.uuid).exists() - response = jwt_authenticated_client.delete(f'/api/receitas/{receita_repasse_livre_aplicacao.uuid}/', + response = jwt_authenticated_client.delete(f'/api/receitas/{receita_repasse_livre_aplicacao.uuid}/?associacao_uuid={associacao.uuid}', content_type='application/json') assert response.status_code == status.HTTP_204_NO_CONTENT diff --git a/sme_ptrf_apps/receitas/tests/tests_api_receitas/test_get_receitas_com_filtros.py b/sme_ptrf_apps/receitas/tests/tests_api_receitas/test_get_receitas_com_filtros.py index 9a5bc9f7f..bdbd69dfc 100644 --- a/sme_ptrf_apps/receitas/tests/tests_api_receitas/test_get_receitas_com_filtros.py +++ b/sme_ptrf_apps/receitas/tests/tests_api_receitas/test_get_receitas_com_filtros.py @@ -16,7 +16,7 @@ def test_api_get_receitas_por_tipo_receita(jwt_authenticated_client, associacao, tipo_conta, conta_associacao): - response = jwt_authenticated_client.get(f'/api/receitas/?tipo_receita={tipo_receita_estorno.id}', + response = jwt_authenticated_client.get(f'/api/receitas/?tipo_receita={tipo_receita_estorno.id}&associacao_uuid={associacao.uuid}', content_type='application/json') result = json.loads(response.content) diff --git a/sme_ptrf_apps/static/cargas/modelo_demonstrativo_financeiro.xlsx b/sme_ptrf_apps/static/cargas/modelo_demonstrativo_financeiro.xlsx index d2f408022..35425f82d 100644 Binary files a/sme_ptrf_apps/static/cargas/modelo_demonstrativo_financeiro.xlsx and b/sme_ptrf_apps/static/cargas/modelo_demonstrativo_financeiro.xlsx differ diff --git a/sme_ptrf_apps/static/modelos/modelo_exportacao_associacao.xlsx b/sme_ptrf_apps/static/modelos/modelo_exportacao_associacao.xlsx new file mode 100644 index 000000000..34b5b998b Binary files /dev/null and b/sme_ptrf_apps/static/modelos/modelo_exportacao_associacao.xlsx differ diff --git a/sme_ptrf_apps/users/admin.py b/sme_ptrf_apps/users/admin.py index 2856cb169..798f31352 100644 --- a/sme_ptrf_apps/users/admin.py +++ b/sme_ptrf_apps/users/admin.py @@ -1,26 +1,36 @@ +from django import forms from django.contrib import admin from django.contrib.auth import admin as auth_admin from django.contrib.auth import get_user_model from sme_ptrf_apps.users.forms import UserChangeForm, UserCreationForm +from sme_ptrf_apps.users.models import Visao +from sme_ptrf_apps.core.models import Unidade User = get_user_model() +class UnidadeChangeListForm(forms.ModelForm): + # here we only need to define the field we want to be editable + unidades = forms.ModelMultipleChoiceField( + queryset=Unidade.objects.all(), required=False) + +class UnidadeInline(admin.TabularInline): + model = Unidade.user_set.through + extra = 1 + +class VisaoInline(admin.TabularInline): + model = Visao.user_set.through + extra = 1 + @admin.register(User) class UserAdmin(auth_admin.UserAdmin): form = UserChangeForm add_form = UserCreationForm - fieldsets = (("User", {"fields": ("name","associacao",)}),) + auth_admin.UserAdmin.fieldsets - list_display = ["uuid", "username", "name", "is_superuser", 'associacao'] + fieldsets = (("User", {"fields": ("name",)}),) + auth_admin.UserAdmin.fieldsets + list_display = ["uuid", "username", "name", "is_superuser",] search_fields = ["name"] + inlines = [UnidadeInline, VisaoInline] - actions = ['importa_usuarios'] - - def importa_usuarios(self, request, queryset): - from sme_ptrf_apps.users.services.carga_usuarios import carrega_usuarios - carrega_usuarios() - self.message_user(request, "Usuários Carregados.") - - importa_usuarios.short_description = "Fazer carga de usuários." +admin.site.register(Visao) \ No newline at end of file diff --git a/sme_ptrf_apps/users/api/serializers/senha_serializer.py b/sme_ptrf_apps/users/api/serializers/senha_serializer.py index a22265c4e..64d3e48a5 100644 --- a/sme_ptrf_apps/users/api/serializers/senha_serializer.py +++ b/sme_ptrf_apps/users/api/serializers/senha_serializer.py @@ -25,8 +25,11 @@ def update(self, instance, validated_data): instance.hash_redefinicao = instance.create_hash instance.save() - enviar_email_redifinicao_senha.delay(email=instance.email, username=instance.username, - nome=instance.name, hash_definicao=instance.hash_redefinicao) + try: + enviar_email_redifinicao_senha(email=instance.email, username=instance.username, + nome=instance.name, hash_definicao=instance.hash_redefinicao) + except Exception as err: + logging.info("Erro ao enviar email: %s ", str(err)) return instance diff --git a/sme_ptrf_apps/users/api/views/login.py b/sme_ptrf_apps/users/api/views/login.py index 0ab130d6e..21cf47bb5 100644 --- a/sme_ptrf_apps/users/api/views/login.py +++ b/sme_ptrf_apps/users/api/views/login.py @@ -41,19 +41,37 @@ def post(self, request, *args, **kwargs): request._full_data = {'username': user_dict['login'], 'password': senha} resp = super().post(request, *args, **kwargs) - associacao = user.associacao - if not associacao: + unidades = [] + for unidade in user.unidades.all(): + associacao = Associacao.objects.filter(unidade__uuid=unidade.uuid).first() + unidades.append({ + 'uuid': unidade.uuid, + 'nome': unidade.nome, + 'tipo_unidade': unidade.tipo_unidade, + 'associacao': { + 'uuid': associacao.uuid if associacao else '', + 'nome': associacao.nome if associacao else '' + } + }) + + if not unidades: associacao = Associacao.objects.first() + else: + associacao = Associacao.objects.filter(unidade__uuid=unidades[0]['uuid']).first() + + # Mantive esse trecho da associação pra não quebrar o front até o mesmo tratar as mudanças de + # visões. Após o front ficar pronto esse trecho deve ser removido. associacao_dict = { 'uuid': associacao.uuid, 'nome': associacao.nome, 'nome_escola': associacao.unidade.nome, 'tipo_escola': associacao.unidade.tipo_unidade} if associacao else { - 'uuid': '', - 'nome': '', - 'nome_escola': '', - 'tipo_escola': ''} + 'uuid': '', + 'nome': '', + 'nome_escola': '', + 'tipo_escola': ''} user_dict['associacao'] = associacao_dict + user_dict['unidades'] = unidades data = {**user_dict, **resp.data} return Response(data) return Response(response.json(), response.status_code) diff --git a/sme_ptrf_apps/users/migrations/0007_user_unidades.py b/sme_ptrf_apps/users/migrations/0007_user_unidades.py new file mode 100644 index 000000000..bd9a8dd97 --- /dev/null +++ b/sme_ptrf_apps/users/migrations/0007_user_unidades.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.10 on 2020-07-31 18:20 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0066_parametros_tempo_notificar_nao_demonstrados'), + ('users', '0006_auto_20200710_1256'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='unidades', + field=models.ManyToManyField(blank=True, to='core.Unidade'), + ), + ] diff --git a/sme_ptrf_apps/users/migrations/0008_remove_user_associacao.py b/sme_ptrf_apps/users/migrations/0008_remove_user_associacao.py new file mode 100644 index 000000000..693e9fa9b --- /dev/null +++ b/sme_ptrf_apps/users/migrations/0008_remove_user_associacao.py @@ -0,0 +1,17 @@ +# Generated by Django 2.2.10 on 2020-08-03 09:38 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0007_user_unidades'), + ] + + operations = [ + migrations.RemoveField( + model_name='user', + name='associacao', + ), + ] diff --git a/sme_ptrf_apps/users/migrations/0009_auto_20200805_1922.py b/sme_ptrf_apps/users/migrations/0009_auto_20200805_1922.py new file mode 100644 index 000000000..658a34aa9 --- /dev/null +++ b/sme_ptrf_apps/users/migrations/0009_auto_20200805_1922.py @@ -0,0 +1,33 @@ +# Generated by Django 2.2.10 on 2020-08-05 19:22 + +from django.db import migrations, models +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0008_remove_user_associacao'), + ] + + operations = [ + migrations.CreateModel( + name='Visao', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('nome', models.CharField(max_length=160, verbose_name='Nome')), + ('criado_em', models.DateTimeField(auto_now_add=True, verbose_name='Criado em')), + ('alterado_em', models.DateTimeField(auto_now=True, verbose_name='Alterado em')), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)), + ], + options={ + 'verbose_name': 'Visão', + 'verbose_name_plural': 'Visões', + }, + ), + migrations.AddField( + model_name='user', + name='visoes', + field=models.ManyToManyField(blank=True, to='users.Visao'), + ), + ] diff --git a/sme_ptrf_apps/users/models.py b/sme_ptrf_apps/users/models.py index b81d646c6..8bc5fcb76 100644 --- a/sme_ptrf_apps/users/models.py +++ b/sme_ptrf_apps/users/models.py @@ -7,17 +7,29 @@ from django.urls import reverse from django.utils.translation import ugettext_lazy as _ -from sme_ptrf_apps.core.models import Associacao +from sme_ptrf_apps.core.models import Associacao, Unidade +from sme_ptrf_apps.core.models_abstracts import ModeloIdNome + + +class Visao(ModeloIdNome): + + def __str__(self): + return self.nome + + class Meta: + verbose_name = 'Visão' + verbose_name_plural = 'Visões' class User(AbstractUser): uuid = models.UUIDField(default=uuid.uuid4, editable=False, null=True) name = CharField(_("Nome do usuário"), blank=True, max_length=255) - associacao = models.ForeignKey(Associacao, on_delete=models.PROTECT, related_name="usuarios", - null=True, blank=True) hash_redefinicao = models.TextField(blank=True, default='', help_text='Campo utilizado para registrar hash na redefinição de senhas.') + unidades = models.ManyToManyField(Unidade, blank=True) + visoes = models.ManyToManyField(Visao, blank=True) + def get_absolute_url(self): return reverse("users:detail", kwargs={"username": self.username}) diff --git a/sme_ptrf_apps/users/services/carga_usuarios.py b/sme_ptrf_apps/users/services/carga_usuarios.py index 6f56c2ba0..a199f5747 100644 --- a/sme_ptrf_apps/users/services/carga_usuarios.py +++ b/sme_ptrf_apps/users/services/carga_usuarios.py @@ -1,11 +1,10 @@ import csv import logging -import os from django.contrib.auth import get_user_model -from django.contrib.staticfiles.storage import staticfiles_storage -from sme_ptrf_apps.core.models import Associacao +from sme_ptrf_apps.core.models import Unidade +from sme_ptrf_apps.users.models import Visao logger = logging.getLogger(__name__) @@ -17,10 +16,23 @@ def processa_importacao_usuarios(reader): for index, row in enumerate(reader): if index != 0: logger.info('Linha %s: %s', index, row) - associacao = Associacao.objects.filter(cnpj=row[1].strip()).first() - if associacao: - u = User.objects.create(username=row[0].strip()) - u.associacao = associacao + + unidade = Unidade.objects.filter(codigo_eol=row[2].strip()).first() + + if unidade: + visao = Visao.objects.filter(nome=row[1].strip()).first() + if not visao: + visao = Visao.objects.create(nome=row[1].strip()) + + u = User.objects.filter(username=row[0].strip()).first() + if not u: + u = User.objects.create(username=row[0].strip()) + + if not u.unidades.filter(codigo_eol=row[2].strip()).first(): + u.unidades.add(unidade) + + if not u.visoes.filter(nome=row[1].strip()).first(): + u.visoes.add(visao) u.save() logger.info('Usuário para o rf %s criado com sucesso.', row[0].strip()) continue @@ -30,9 +42,9 @@ def processa_importacao_usuarios(reader): logging.info('Error: %s', str(e)) -def carrega_usuarios(): +def carrega_usuarios(arquivo): logger.info('Carregando arquivo de usuários.') - with staticfiles_storage.open(os.path.join('cargas', 'usuarios.csv'), 'r') as f: + with open(arquivo.conteudo.path, 'r', encoding="utf-8") as f: reader = csv.reader(f, delimiter=',') processa_importacao_usuarios(reader)