sgmp/solicitacoes/api_views.py

804 lines
31 KiB
Python

# 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)),
})