804 lines
31 KiB
Python
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)),
|
|
})
|