# SGMP_PROD/solicitacoes/api_views.py """ APIs REST para integração com o frontend Next.js. Usado na migração gradual de telas do Django para o frontend. """ import json import logging import time from datetime import date from django.core.exceptions import ObjectDoesNotExist from django.contrib.auth import login, logout, get_user_model from django.shortcuts import get_object_or_404 from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_http_methods from .models import ( UsuarioSistema, Solicitacao, StatusSolicitacao, EtapaAprovacao, DecisaoAprovacao, PessoaRM, matriculas_gestores_do_head, ) from . import services from .intf_sqlserver import ( listar_cargos_ativos_rm, listar_secoes_ativas_rm, listar_coligadas_rm, buscar_colaboradores_rm, ) from .intf_winthor import autenticar_usuario, buscar_colaborador_oracle from .views import get_usuario_sistema from .debug_status_log import log_dashboard_row User = get_user_model() logger = logging.getLogger(__name__) # #region agent log _API_DEBUG_LOG = "/home/f3lipe/dev/.cursor/debug-ca90b5.log" def _api_debug_log(hypothesis_id: str, location: str, message: str, data: dict) -> None: try: with open(_API_DEBUG_LOG, "a", encoding="utf-8") as f: f.write( json.dumps( { "sessionId": "ca90b5", "hypothesisId": hypothesis_id, "location": location, "message": message, "data": data, "timestamp": int(time.time() * 1000), }, ensure_ascii=False, ) + "\n" ) except Exception: pass # #endregion def _parse_json_body(request): """Extrai body JSON do request.""" try: return json.loads(request.body) if request.body else {} except json.JSONDecodeError: return {} def _coerce_date(value, field_name): if not value: raise services.ValidacaoError(f"Campo obrigatório ausente: {field_name}.") try: return date.fromisoformat(str(value)) except ValueError as exc: raise services.ValidacaoError(f"Data inválida em {field_name}. Use AAAA-MM-DD.") from exc def _coerce_bool(value): if isinstance(value, bool): return value return str(value).strip().lower() in {"1", "true", "on", "sim", "yes"} def _qs_dashboard_unir_fila_com_minhas(qs_fila, usuario): """Fila do papel OR solicitações em que o usuário é o solicitante (distinct).""" qs_minhas = Solicitacao.objects.filter(solicitante=usuario) return (qs_fila | qs_minhas).distinct() def _qs_dashboard_for_usuario(usuario): """Retorna queryset base conforme regras de visibilidade do dashboard.""" if usuario.tem_perfil(UsuarioSistema.Perfil.ADMIN): return Solicitacao.objects.all() if usuario.tem_perfil(UsuarioSistema.Perfil.GESTOR): return Solicitacao.objects.filter(solicitante=usuario) if usuario.tem_perfil(UsuarioSistema.Perfil.HEAD): matriculas = matriculas_gestores_do_head(usuario) qs_fila = Solicitacao.objects.filter(status=StatusSolicitacao.AGUARDANDO_HEAD) if matriculas: qs_fila = qs_fila.filter(solicitante__matricula__in=matriculas) return _qs_dashboard_unir_fila_com_minhas(qs_fila, usuario) if ( usuario.tem_perfil(UsuarioSistema.Perfil.GG) or usuario.tem_perfil(UsuarioSistema.Perfil.CONTROLADORIA) ): qs_fila = Solicitacao.objects.filter(status=StatusSolicitacao.ENVIADA) if usuario.tem_perfil(UsuarioSistema.Perfil.GG) and not usuario.tem_perfil(UsuarioSistema.Perfil.CONTROLADORIA): qs_fila = qs_fila.exclude(pareceres__etapa=EtapaAprovacao.GG, pareceres__usuario=usuario) elif usuario.tem_perfil(UsuarioSistema.Perfil.CONTROLADORIA) and not usuario.tem_perfil(UsuarioSistema.Perfil.GG): qs_fila = qs_fila.exclude(pareceres__etapa=EtapaAprovacao.CONTROLADORIA, pareceres__usuario=usuario) return _qs_dashboard_unir_fila_com_minhas(qs_fila, usuario) if usuario.tem_perfil(UsuarioSistema.Perfil.DIRETORIA): qs_fila = Solicitacao.objects.filter(status=StatusSolicitacao.AGUARDANDO_DIRETORIA) return _qs_dashboard_unir_fila_com_minhas(qs_fila, usuario) return Solicitacao.objects.none() def _serialize_aprovacao(aprovacao): return { "id": str(aprovacao.id), "etapa": aprovacao.etapa, "etapa_display": aprovacao.get_etapa_display(), "decisao": aprovacao.decisao, "decisao_display": aprovacao.get_decisao_display(), "justificativa": aprovacao.justificativa, "decidido_em": aprovacao.decidido_em.isoformat() if aprovacao.decidido_em else None, "usuario_nome": aprovacao.usuario.nome if aprovacao.usuario else None, } def _serialize_parecer(parecer): return { "id": str(parecer.id), "etapa": parecer.etapa, "etapa_display": parecer.get_etapa_display(), "texto": parecer.texto, "criado_em": parecer.criado_em.isoformat() if parecer.criado_em else None, "usuario_nome": parecer.usuario.nome if parecer.usuario else None, "anexo_url": parecer.anexo.url if getattr(parecer, "anexo", None) else None, } def _json_value(value): if value is None: return None if hasattr(value, "isoformat"): return value.isoformat() return value def _serialize_funcionario_rm(funcionario): if not funcionario: return None return { "id": str(funcionario.id), "id_rm": funcionario.id_rm, "matricula": funcionario.matricula, "nome": funcionario.nome, "cpf": funcionario.cpf, "data_admissao": _json_value(funcionario.data_admissao), "situacao": funcionario.situacao, "cargo": funcionario.cargo, "setor": funcionario.setor, "centro_custo": funcionario.centro_custo, "cod_funcao": funcionario.cod_funcao, "salario": str(funcionario.salario) if funcionario.salario is not None else None, "cod_sindicato": funcionario.cod_sindicato, "saldo_banco_horas_minutos": funcionario.saldo_banco_horas_minutos, "inicio_periodo_banco_horas": _json_value(funcionario.inicio_periodo_banco_horas), "fim_periodo_banco_horas": _json_value(funcionario.fim_periodo_banco_horas), "sincronizado_em": _json_value(funcionario.sincronizado_em), "matricula_winthor": funcionario.matricula_winthor, } def _serialize_dados_winthor(funcionario): if not funcionario or not funcionario.cpf: return None try: dados = buscar_colaborador_oracle(funcionario.cpf) except Exception: logger.exception("Erro ao buscar dados do Winthor no detalhe API") return None if not dados: return None return { "basicos": { "matricula": _json_value(dados.get("matricula")), "nome": _json_value(dados.get("nome")), "cpf": _json_value(dados.get("cpf")), }, "admissao": { "admissao": _json_value(dados.get("admissao")), "situacao": _json_value(dados.get("situacao")), "dt_exclusao": _json_value(dados.get("dt_exclusao")), }, "endereco": { "endereco": _json_value(dados.get("endereco")), "bairro": _json_value(dados.get("bairro")), "cidade": _json_value(dados.get("cidade")), "estado": _json_value(dados.get("estado")), }, } def _serialize_detalhes_tipo(solicitacao): try: if solicitacao.tipo == "DESLIGAMENTO": desligamento = solicitacao.desligamento return { "tipo": "DESLIGAMENTO", "tipo_desligamento": desligamento.tipo_desligamento, "tipo_desligamento_display": desligamento.get_tipo_desligamento_display() if desligamento.tipo_desligamento else None, "aviso_previo": desligamento.aviso_previo, "aviso_previo_display": desligamento.get_aviso_previo_display() if desligamento.aviso_previo else None, "data_prevista_desligamento": _json_value(desligamento.data_prevista_desligamento), "motivo": desligamento.motivo, "observacoes": desligamento.observacoes, "arquivo_pedido_url": desligamento.arquivo_pedido.url if desligamento.arquivo_pedido else None, "arquivo_pedido_nome": desligamento.arquivo_pedido.name if desligamento.arquivo_pedido else None, } if solicitacao.tipo == "MOVIMENTACAO": mov = solicitacao.movimentacao return { "tipo": "MOVIMENTACAO", "altera_funcao": mov.altera_funcao, "altera_centro_custo": mov.altera_centro_custo, "novo_cod_secao": mov.novo_cod_secao, "novo_cod_funcao": mov.novo_cod_funcao, "novo_salario": str(mov.novo_salario) if mov.novo_salario is not None else None, "data_efetivacao": _json_value(mov.data_efetivacao), "justificativa": mov.justificativa, } if solicitacao.tipo == "ADM_SUBSTITUICAO": adm_sub = solicitacao.admissao_substituicao return { "tipo": "ADM_SUBSTITUICAO", "data_previsao_contratacao": _json_value(adm_sub.data_previsao_contratacao), "cod_coligada_destino": adm_sub.cod_coligada_destino, "cod_filial_destino": adm_sub.cod_filial_destino, "cod_secao_destino": adm_sub.cod_secao_destino, "cod_funcao_destino": adm_sub.cod_funcao_destino, "justificativa": adm_sub.justificativa, } if solicitacao.tipo == "ADM_AUMENTO": adm_aumento = solicitacao.admissao_aumento return { "tipo": "ADM_AUMENTO", "data_previsao_contratacao": _json_value(adm_aumento.data_previsao_contratacao), "justificativa_estrategica": adm_aumento.justificativa_estrategica, "cod_coligada_destino": adm_aumento.cod_coligada_destino, "cod_filial_destino": adm_aumento.cod_filial_destino, "cod_secao_destino": adm_aumento.cod_secao_destino, "cod_funcao_destino": adm_aumento.cod_funcao_destino, "requer_suplementacao_orcamentaria": adm_aumento.requer_suplementacao_orcamentaria, } except ObjectDoesNotExist: return None return None @csrf_exempt @require_http_methods(["POST"]) def api_login(request): """ Autentica usuário via credenciais Winthor. Aceita JSON: { "username": "...", "password": "...", "next": "?" } Retorna: { "success": bool, "message": str, "redirect": str } """ if request.user.is_authenticated: return JsonResponse({ "success": True, "message": "Já autenticado.", "redirect": request.GET.get("next", "/dashboard") or "/dashboard", }) data = _parse_json_body(request) if not data: # Fallback para form-urlencoded (compatibilidade) login_input = request.POST.get("username", "").strip() senha = request.POST.get("password", "").strip() next_url = request.POST.get("next", "").strip() else: login_input = data.get("username", "").strip() senha = data.get("password", "").strip() next_url = data.get("next", "").strip() if not login_input or not senha: return JsonResponse( {"success": False, "message": "Informe usuário e senha."}, status=400, ) dados = autenticar_usuario(login_input, senha) if not dados: return JsonResponse( {"success": False, "message": "Usuário ou senha inválidos no Winthor."}, status=401, ) user, _ = User.objects.get_or_create( username=str(dados["matricula"]), defaults={"first_name": dados.get("nome", "Usuario").split(" ")[0]}, ) login(request, user) usuario_sistema, created = UsuarioSistema.objects.get_or_create( matricula=str(dados["matricula"]), defaults={ "nome": dados["nome"], "ativo": True, "perfil": UsuarioSistema.Perfil.GESTOR, }, ) if not created: usuario_sistema.nome = dados["nome"] usuario_sistema.ativo = True usuario_sistema.save(update_fields=["nome", "ativo"]) redirect_to = next_url or "/dashboard" # #region agent log _api_debug_log( "H5", "api_views.api_login:success", "api_login_json_response", { "redirect_to": redirect_to[:300], "session_key": getattr(request.session, "session_key", None), "user_id": getattr(request.user, "id", None), "matricula": str(dados.get("matricula", ""))[:20], }, ) # #endregion return JsonResponse({ "success": True, "message": f"Bem-vindo, {dados['nome']}!", "redirect": redirect_to, }) @csrf_exempt @require_http_methods(["POST"]) def api_logout(request): """Encerra a sessão do usuário.""" logout(request) return JsonResponse({ "success": True, "message": "Você saiu do sistema.", "redirect": "/tela_login", }) @require_http_methods(["GET"]) def api_me(request): """ Retorna dados do usuário autenticado. Usado para verificar sessão e exibir dados na UI. """ if not request.user.is_authenticated: return JsonResponse({"authenticated": False}, status=401) try: usuario = get_usuario_sistema(request) return JsonResponse({ "authenticated": True, "usuario": { "matricula": usuario.matricula, "nome": usuario.nome, "perfil": usuario.perfil, "perfil_display": usuario.get_perfil_display(), }, }) except Exception: return JsonResponse({"authenticated": False}, status=401) @require_http_methods(["GET"]) def api_dashboard(request): """ Retorna dados do dashboard: métricas e lista de solicitações. Mesma lógica da dashboard_view, em formato JSON. """ if not request.user.is_authenticated: return JsonResponse({"error": "Não autenticado"}, status=401) try: usuario = get_usuario_sistema(request) except Exception: return JsonResponse({"error": "Usuário não encontrado"}, status=401) # #region agent log log_dashboard_row( location="api_views.api_dashboard:viewer", message="dashboard_api_viewer", hypothesis_id="H2", run_id="status-ambiguity", data={ "viewer_perfil": usuario.perfil, "tem_gg": usuario.tem_perfil(UsuarioSistema.Perfil.GG), "tem_controladoria": usuario.tem_perfil(UsuarioSistema.Perfil.CONTROLADORIA), "tem_diretoria": usuario.tem_perfil(UsuarioSistema.Perfil.DIRETORIA), }, ) # #endregion qs_base = _qs_dashboard_for_usuario(usuario) solicitacoes_qs = qs_base.order_by("-criado_em").prefetch_related("pareceres") finalizados = [StatusSolicitacao.FINALIZADA, StatusSolicitacao.REPROVADA] total = qs_base.count() pendentes = qs_base.exclude(status__in=finalizados).count() # Paginação from django.core.paginator import Paginator page = int(request.GET.get("page", 1)) per_page = int(request.GET.get("per_page", 10)) paginator = Paginator(solicitacoes_qs, per_page) page_obj = paginator.get_page(page) # Serializa solicitações solicitacoes_list = [] for s in page_obj: # #region agent log _parecer_etapas = [p.etapa for p in s.pareceres.all()] log_dashboard_row( location="api_views.api_dashboard:row", message="status_vs_fila_gg", hypothesis_id="H1+H4+H5", run_id="status-ambiguity", data={ "solicitacao_id": str(s.id), "status": s.status, "status_display_base": s.get_status_display(), "status_display_para": str(s.get_status_display_para_usuario(usuario)), "etapa_atual": s.etapa_atual(), "solicitante_tem_diretoria": s.solicitante.tem_perfil( UsuarioSistema.Perfil.DIRETORIA ), "parecer_etapas": _parecer_etapas, "pode_dar_parecer": s.pode_dar_parecer(usuario), }, ) # #endregion func_nome = s.funcionario.nome if s.funcionario else None solicitacoes_list.append({ "id": str(s.id), "tipo": s.tipo, "tipo_display": s.get_tipo_display(), "colaborador": func_nome, "status": s.status, "status_display": str(s.get_status_display_para_usuario(usuario)), "criado_em": s.criado_em.isoformat() if s.criado_em else None, "enviada_em": s.enviada_em.isoformat() if s.enviada_em else None, "solicitante_nome": s.solicitante.nome if s.solicitante else None, "pode_aprovar": s.pode_aprovar(usuario), "pode_dar_parecer": s.pode_dar_parecer(usuario), }) return JsonResponse({ "usuario": { "matricula": usuario.matricula, "nome": usuario.nome, "perfil": usuario.perfil, "perfil_display": usuario.get_perfil_display(), }, "total": total, "pendentes": pendentes, "solicitacoes": solicitacoes_list, "pagination": { "page": page_obj.number, "per_page": per_page, "total_pages": paginator.num_pages, "total_count": paginator.count, }, }) @require_http_methods(["GET"]) def api_solicitacao_detalhe(request, solicitacao_id): """ Retorna detalhes de uma solicitação visível ao usuário conforme regras do dashboard. """ if not request.user.is_authenticated: return JsonResponse({"error": "Não autenticado"}, status=401) try: usuario = get_usuario_sistema(request) except Exception: return JsonResponse({"error": "Usuário não encontrado"}, status=401) qs_base = _qs_dashboard_for_usuario(usuario) solicitacao = get_object_or_404( qs_base.select_related( "solicitante", "funcionario", "desligamento", "movimentacao", "admissao_substituicao", "admissao_aumento", ) .prefetch_related("pareceres__usuario", "aprovacoes__usuario"), id=solicitacao_id, ) funcionario = solicitacao.funcionario payload = { "id": str(solicitacao.id), "tipo": solicitacao.tipo, "tipo_display": solicitacao.get_tipo_display(), "status": solicitacao.status, "status_display": str(solicitacao.get_status_display_para_usuario(usuario)), "criado_em": solicitacao.criado_em.isoformat() if solicitacao.criado_em else None, "enviada_em": solicitacao.enviada_em.isoformat() if solicitacao.enviada_em else None, "finalizada_em": solicitacao.finalizada_em.isoformat() if solicitacao.finalizada_em else None, "solicitante": { "matricula": solicitacao.solicitante.matricula if solicitacao.solicitante else None, "nome": solicitacao.solicitante.nome if solicitacao.solicitante else None, }, "funcionario": _serialize_funcionario_rm(funcionario), "dados_winthor": _serialize_dados_winthor(funcionario), "detalhes_tipo": _serialize_detalhes_tipo(solicitacao), "acoes": { "pode_aprovar": solicitacao.pode_aprovar(usuario), "pode_dar_parecer": solicitacao.pode_dar_parecer(usuario), "pode_enviar": solicitacao.pode_enviar(), "is_solicitante": solicitacao.solicitante_id == usuario.id, }, "pareceres": [ _serialize_parecer(parecer) for parecer in solicitacao.pareceres.all().order_by("-criado_em") ], "aprovacoes": [ _serialize_aprovacao(aprovacao) for aprovacao in solicitacao.aprovacoes.all().order_by("-decidido_em") ], } return JsonResponse(payload) @require_http_methods(["GET"]) def api_colaboradores(request): if not request.user.is_authenticated: return JsonResponse({"error": "Não autenticado"}, status=401) tipo = (request.GET.get("tipo") or "").strip() busca = (request.GET.get("q") or "").strip() if tipo == "substituicao": resultados_rm = buscar_colaboradores_rm(nome=busca if busca else None) colaboradores = [] for row in resultados_rm: id_rm = f"{row['CODCOLIGADA']}-{row['CHAPA']}" local_id = None local_obj = PessoaRM.objects.filter(id_rm=id_rm).only("id").first() if local_obj: local_id = str(local_obj.id) colaboradores.append( { "id_rm": id_rm, "matricula": row.get("CHAPA"), "nome": row.get("NOME"), "cpf": row.get("CPF"), "cargo": row.get("FUNCAO"), "setor": row.get("SECAO"), "situacao": row.get("CODSITUACAO"), "id": local_id, } ) return JsonResponse({"colaboradores": colaboradores}) qs = PessoaRM.objects.exclude(situacao="D").order_by("nome") if busca: qs = qs.filter(nome__icontains=busca) | qs.filter(matricula__icontains=busca) colaboradores = [ { "id": str(c.id), "id_rm": c.id_rm, "matricula": c.matricula, "nome": c.nome, "cpf": c.cpf, "cargo": c.cargo, "setor": c.setor, "situacao": c.situacao, } for c in qs[:60] ] return JsonResponse({"colaboradores": colaboradores}) @require_http_methods(["GET"]) def api_nova_solicitacao_metadata(request): if not request.user.is_authenticated: return JsonResponse({"error": "Não autenticado"}, status=401) return JsonResponse( { "cargos": listar_cargos_ativos_rm(), "secoes": listar_secoes_ativas_rm(), "coligadas": listar_coligadas_rm(), "tipos": [ {"value": "DESLIGAMENTO", "label": "Desligamento", "precisa_colaborador": True}, {"value": "MOVIMENTACAO", "label": "Movimentação", "precisa_colaborador": True}, {"value": "ADM_SUBSTITUICAO", "label": "Admissão por Substituição", "precisa_colaborador": True}, {"value": "ADM_AUMENTO", "label": "Admissão por Aumento de Quadro", "precisa_colaborador": False}, ], } ) @csrf_exempt @require_http_methods(["POST"]) def api_solicitacao_criar(request): if not request.user.is_authenticated: return JsonResponse({"error": "Não autenticado"}, status=401) try: usuario = get_usuario_sistema(request) except Exception: return JsonResponse({"error": "Usuário não encontrado"}, status=401) content_type = (request.content_type or "").lower() if "multipart/form-data" in content_type: payload = request.POST else: payload = _parse_json_body(request) tipo = (payload.get("tipo") or "").strip() try: if tipo == "DESLIGAMENTO": funcionario = get_object_or_404(PessoaRM, id=payload.get("funcionario_id")) solicitacao = services.criar_solicitacao_desligamento( solicitante=usuario, funcionario=funcionario, tipo_desligamento=(payload.get("tipo_desligamento") or "").strip(), aviso_previo=(payload.get("aviso_previo") or "").strip(), motivo=(payload.get("motivo") or "").strip(), data_prevista_desligamento=_coerce_date(payload.get("data_prevista_desligamento"), "data_prevista_desligamento"), observacoes=(payload.get("observacoes") or "").strip(), arquivo_pedido=request.FILES.get("arquivo_pedido"), ) elif tipo == "MOVIMENTACAO": funcionario = get_object_or_404(PessoaRM, id=payload.get("funcionario_id")) solicitacao = services.criar_solicitacao_movimentacao( solicitante=usuario, funcionario=funcionario, dados_movimentacao={ "altera_funcao": _coerce_bool(payload.get("altera_funcao")), "altera_centro_custo": _coerce_bool(payload.get("altera_centro_custo")), "novo_cod_funcao": payload.get("novo_cod_funcao") or None, "novo_cod_secao": payload.get("novo_cod_secao") or None, "novo_salario": payload.get("novo_salario") or None, "data_efetivacao": _coerce_date(payload.get("data_efetivacao"), "data_efetivacao"), "justificativa": (payload.get("justificativa") or "").strip(), }, ) elif tipo == "ADM_SUBSTITUICAO": funcionario = get_object_or_404(PessoaRM, id=payload.get("funcionario_id")) solicitacao = services.criar_solicitacao_substituicao( solicitante=usuario, funcionario_substituido=funcionario, dados_admissao={ "data_previsao_contratacao": _coerce_date(payload.get("data_previsao_contratacao"), "data_previsao_contratacao"), "cod_coligada_destino": (payload.get("cod_coligada_destino") or "").strip(), "cod_filial_destino": (payload.get("cod_filial_destino") or "").strip(), "cod_secao_destino": (payload.get("cod_secao_destino") or "").strip(), "cod_funcao_destino": (payload.get("cod_funcao_destino") or "").strip(), "justificativa": (payload.get("justificativa") or "").strip(), }, ) elif tipo == "ADM_AUMENTO": solicitacao = services.criar_solicitacao_aumento_quadro( solicitante=usuario, dados_admissao={ "data_previsao_contratacao": _coerce_date(payload.get("data_previsao_contratacao"), "data_previsao_contratacao"), "cod_coligada_destino": (payload.get("cod_coligada_destino") or "").strip(), "cod_filial_destino": (payload.get("cod_filial_destino") or "").strip(), "cod_secao_destino": (payload.get("cod_secao_destino") or "").strip(), "cod_funcao_destino": (payload.get("cod_funcao_destino") or "").strip(), "justificativa_estrategica": (payload.get("justificativa_estrategica") or "").strip(), }, ) else: return JsonResponse({"error": "Tipo de solicitação inválido."}, status=400) except services.SolicitacaoError as exc: return JsonResponse({"error": str(exc)}, status=400) except Exception: logger.exception("Erro ao criar solicitação via API") return JsonResponse({"error": "Erro interno ao criar solicitação."}, status=500) return JsonResponse({"success": True, "solicitacao_id": str(solicitacao.id), "redirect": f"/solicitacoes/{solicitacao.id}"}) @csrf_exempt @require_http_methods(["POST"]) def api_solicitacao_enviar(request, solicitacao_id): if not request.user.is_authenticated: return JsonResponse({"error": "Não autenticado"}, status=401) try: usuario = get_usuario_sistema(request) except Exception: return JsonResponse({"error": "Usuário não encontrado"}, status=401) solicitacao = get_object_or_404(Solicitacao, id=solicitacao_id) try: services.enviar_solicitacao(solicitacao, usuario) solicitacao.refresh_from_db() return JsonResponse({ "success": True, "status": solicitacao.status, "status_display": str(solicitacao.get_status_display_para_usuario(usuario)), }) except services.SolicitacaoError as exc: return JsonResponse({"error": str(exc)}, status=400) except Exception: logger.exception("Erro ao enviar solicitação via API") return JsonResponse({"error": "Erro interno ao enviar solicitação."}, status=500) @csrf_exempt @require_http_methods(["POST"]) def api_solicitacao_decidir(request, solicitacao_id): if not request.user.is_authenticated: return JsonResponse({"error": "Não autenticado"}, status=401) try: usuario = get_usuario_sistema(request) except Exception: return JsonResponse({"error": "Usuário não encontrado"}, status=401) data = _parse_json_body(request) decisao = (data.get("decisao") or "").strip() justificativa = (data.get("justificativa") or "").strip() if decisao not in {DecisaoAprovacao.APROVADO, DecisaoAprovacao.REPROVADO}: return JsonResponse({"error": "Decisão inválida."}, status=400) solicitacao = get_object_or_404(Solicitacao, id=solicitacao_id) try: if solicitacao.status == StatusSolicitacao.AGUARDANDO_HEAD: services.aprovar_reprovar_por_head( solicitacao=solicitacao, aprovador=usuario, decisao=decisao, justificativa=justificativa, ) elif solicitacao.status == StatusSolicitacao.AGUARDANDO_DIRETORIA: services.aprovar_reprovar_solicitacao( solicitacao=solicitacao, aprovador=usuario, decisao=decisao, justificativa=justificativa, ) else: return JsonResponse({"error": "Solicitação não está aguardando decisão."}, status=400) except services.SolicitacaoError as exc: return JsonResponse({"error": str(exc)}, status=400) except Exception: logger.exception("Erro ao decidir solicitação via API") return JsonResponse({"error": "Erro interno ao decidir solicitação."}, status=500) solicitacao.refresh_from_db() return JsonResponse({ "success": True, "status": solicitacao.status, "status_display": str(solicitacao.get_status_display_para_usuario(usuario)), }) @csrf_exempt @require_http_methods(["POST"]) def api_solicitacao_parecer(request, solicitacao_id): if not request.user.is_authenticated: return JsonResponse({"error": "Não autenticado"}, status=401) try: usuario = get_usuario_sistema(request) except Exception: return JsonResponse({"error": "Usuário não encontrado"}, status=401) solicitacao = get_object_or_404(Solicitacao, id=solicitacao_id) texto = (request.POST.get("texto") or "").strip() anexo = request.FILES.get("anexo") if not texto: return JsonResponse({"error": "Texto do parecer é obrigatório."}, status=400) try: parecer = services.registrar_parecer(solicitacao=solicitacao, usuario=usuario, texto=texto, anexo=anexo) except services.SolicitacaoError as exc: return JsonResponse({"error": str(exc)}, status=400) except Exception: logger.exception("Erro ao registrar parecer via API") return JsonResponse({"error": "Erro interno ao registrar parecer."}, status=500) solicitacao.refresh_from_db() return JsonResponse({ "success": True, "parecer": _serialize_parecer(parecer), "status": solicitacao.status, "status_display": str(solicitacao.get_status_display_para_usuario(usuario)), })