655 lines
22 KiB
Python
655 lines
22 KiB
Python
"""
|
|
Arcádia People - Motor de RH (HRM)
|
|
Serviço FastAPI para gestão de recursos humanos
|
|
"""
|
|
|
|
from fastapi import FastAPI, HTTPException
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from pydantic import BaseModel
|
|
from typing import Optional, List, Dict, Any
|
|
from datetime import datetime, date
|
|
from decimal import Decimal
|
|
import json
|
|
import os
|
|
|
|
app = FastAPI(
|
|
title="Arcádia People",
|
|
description="Motor de RH - Folha de Pagamento, Ponto, Férias, eSocial",
|
|
version="1.0.0"
|
|
)
|
|
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=[os.getenv("APP_URL", "http://localhost:5000")],
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
# ========== Tabelas de Cálculo (2024) ==========
|
|
|
|
TABELA_INSS_2024 = [
|
|
{"faixaInicio": 0, "faixaFim": 1412.00, "aliquota": 7.5},
|
|
{"faixaInicio": 1412.01, "faixaFim": 2666.68, "aliquota": 9.0},
|
|
{"faixaInicio": 2666.69, "faixaFim": 4000.03, "aliquota": 12.0},
|
|
{"faixaInicio": 4000.04, "faixaFim": 7786.02, "aliquota": 14.0},
|
|
]
|
|
|
|
TETO_INSS_2024 = 7786.02
|
|
DEDUCAO_DEPENDENTE_IRRF = 189.59
|
|
|
|
TABELA_IRRF_2024 = [
|
|
{"faixaInicio": 0, "faixaFim": 2259.20, "aliquota": 0, "deducao": 0},
|
|
{"faixaInicio": 2259.21, "faixaFim": 2826.65, "aliquota": 7.5, "deducao": 169.44},
|
|
{"faixaInicio": 2826.66, "faixaFim": 3751.05, "aliquota": 15.0, "deducao": 381.44},
|
|
{"faixaInicio": 3751.06, "faixaFim": 4664.68, "aliquota": 22.5, "deducao": 662.77},
|
|
{"faixaInicio": 4664.69, "faixaFim": 999999999, "aliquota": 27.5, "deducao": 896.00},
|
|
]
|
|
|
|
SALARIO_FAMILIA_2024 = [
|
|
{"faixaFim": 1819.26, "valor": 62.04},
|
|
]
|
|
|
|
# ========== Models ==========
|
|
|
|
class Funcionario(BaseModel):
|
|
nome: str
|
|
cpf: str
|
|
salario: float
|
|
dataAdmissao: str
|
|
cargoId: Optional[int] = None
|
|
departamentoId: Optional[int] = None
|
|
tipoContrato: str = "clt"
|
|
jornadaTrabalho: str = "44h"
|
|
|
|
class CalculoFolhaRequest(BaseModel):
|
|
funcionarioId: int
|
|
competencia: str # YYYY-MM
|
|
salarioBase: float
|
|
diasTrabalhados: int = 30
|
|
horasExtras50: float = 0
|
|
horasExtras100: float = 0
|
|
horasNoturnas: float = 0
|
|
faltas: int = 0
|
|
atrasos: float = 0
|
|
dependentesIrrf: int = 0
|
|
descontosAdicionais: List[Dict[str, Any]] = []
|
|
proventosAdicionais: List[Dict[str, Any]] = []
|
|
|
|
class CalculoFeriasRequest(BaseModel):
|
|
funcionarioId: int
|
|
salarioBase: float
|
|
diasFerias: int = 30
|
|
diasAbono: int = 0
|
|
mediaHorasExtras: float = 0
|
|
dependentesIrrf: int = 0
|
|
|
|
class CalculoRescisaoRequest(BaseModel):
|
|
funcionarioId: int
|
|
salarioBase: float
|
|
dataAdmissao: str
|
|
dataDemissao: str
|
|
tipoRescisao: str # sem_justa_causa, justa_causa, pedido_demissao
|
|
saldoFerias: int = 0
|
|
avisoPrevio: str = "trabalhado" # trabalhado, indenizado, dispensado
|
|
dependentesIrrf: int = 0
|
|
|
|
class PontoRequest(BaseModel):
|
|
funcionarioId: int
|
|
data: str
|
|
entrada1: str
|
|
saida1: str
|
|
entrada2: Optional[str] = None
|
|
saida2: Optional[str] = None
|
|
jornadaDiaria: float = 8.0
|
|
|
|
# ========== Funções de Cálculo ==========
|
|
|
|
def calcular_inss(salario_bruto: float) -> dict:
|
|
"""
|
|
Calcula INSS progressivo (2024) conforme Portaria MPS
|
|
|
|
Tabela INSS 2024:
|
|
Faixa 1: Até R$ 1.412,00 = 7,5%
|
|
Faixa 2: De R$ 1.412,01 a R$ 2.666,68 = 9%
|
|
Faixa 3: De R$ 2.666,69 a R$ 4.000,03 = 12%
|
|
Faixa 4: De R$ 4.000,04 a R$ 7.786,02 = 14%
|
|
|
|
Cálculo progressivo: cada faixa incide apenas sobre o valor dentro dela.
|
|
"""
|
|
if salario_bruto > TETO_INSS_2024:
|
|
base_calculo = TETO_INSS_2024
|
|
else:
|
|
base_calculo = salario_bruto
|
|
|
|
inss_total = 0.0
|
|
detalhamento = []
|
|
|
|
# Limites exatos das faixas para cálculo progressivo
|
|
faixas = [
|
|
{"limite_inferior": 0, "limite_superior": 1412.00, "aliquota": 7.5},
|
|
{"limite_inferior": 1412.00, "limite_superior": 2666.68, "aliquota": 9.0},
|
|
{"limite_inferior": 2666.68, "limite_superior": 4000.03, "aliquota": 12.0},
|
|
{"limite_inferior": 4000.03, "limite_superior": 7786.02, "aliquota": 14.0},
|
|
]
|
|
|
|
for i, faixa in enumerate(faixas):
|
|
limite_inferior = faixa["limite_inferior"]
|
|
limite_superior = faixa["limite_superior"]
|
|
aliquota = faixa["aliquota"]
|
|
|
|
if base_calculo <= limite_inferior:
|
|
break
|
|
|
|
# Base da faixa: min(salário, limite_superior) - limite_inferior
|
|
teto_faixa = min(base_calculo, limite_superior)
|
|
base_faixa = teto_faixa - limite_inferior
|
|
|
|
if base_faixa > 0:
|
|
valor_faixa = base_faixa * (aliquota / 100)
|
|
inss_total += valor_faixa
|
|
detalhamento.append({
|
|
"faixa": i + 1,
|
|
"limiteInferior": limite_inferior,
|
|
"limiteSuperior": limite_superior,
|
|
"base": round(base_faixa, 2),
|
|
"aliquota": aliquota,
|
|
"valor": round(valor_faixa, 2)
|
|
})
|
|
|
|
return {
|
|
"baseCalculo": round(base_calculo, 2),
|
|
"valorInss": round(inss_total, 2),
|
|
"tetoAplicado": salario_bruto > TETO_INSS_2024,
|
|
"detalhamento": detalhamento
|
|
}
|
|
|
|
def calcular_irrf(base_irrf: float, dependentes: int = 0) -> dict:
|
|
"""Calcula IRRF (2024)"""
|
|
deducao_dependentes = dependentes * DEDUCAO_DEPENDENTE_IRRF
|
|
base_calculo = base_irrf - deducao_dependentes
|
|
|
|
if base_calculo < 0:
|
|
base_calculo = 0
|
|
|
|
irrf = 0
|
|
aliquota_efetiva = 0
|
|
faixa_aplicada = 0
|
|
|
|
for i, faixa in enumerate(TABELA_IRRF_2024):
|
|
if base_calculo >= faixa["faixaInicio"] and base_calculo <= faixa["faixaFim"]:
|
|
irrf = (base_calculo * faixa["aliquota"] / 100) - faixa["deducao"]
|
|
aliquota_efetiva = faixa["aliquota"]
|
|
faixa_aplicada = i + 1
|
|
break
|
|
|
|
if irrf < 0:
|
|
irrf = 0
|
|
|
|
return {
|
|
"baseCalculo": round(base_calculo, 2),
|
|
"deducaoDependentes": round(deducao_dependentes, 2),
|
|
"aliquotaEfetiva": aliquota_efetiva,
|
|
"faixaAplicada": faixa_aplicada,
|
|
"valorIrrf": round(irrf, 2)
|
|
}
|
|
|
|
def calcular_fgts(base_fgts: float) -> dict:
|
|
"""Calcula FGTS (8%)"""
|
|
valor_fgts = base_fgts * 0.08
|
|
return {
|
|
"baseCalculo": round(base_fgts, 2),
|
|
"aliquota": 8.0,
|
|
"valorFgts": round(valor_fgts, 2)
|
|
}
|
|
|
|
def calcular_salario_familia(salario: float, filhos_menores: int) -> dict:
|
|
"""Calcula Salário Família"""
|
|
valor_cota = 0
|
|
for faixa in SALARIO_FAMILIA_2024:
|
|
if salario <= faixa["faixaFim"]:
|
|
valor_cota = faixa["valor"]
|
|
break
|
|
|
|
valor_total = valor_cota * filhos_menores
|
|
|
|
return {
|
|
"salarioBase": round(salario, 2),
|
|
"filhosMenores": filhos_menores,
|
|
"valorCota": valor_cota,
|
|
"valorTotal": round(valor_total, 2),
|
|
"temDireito": valor_cota > 0
|
|
}
|
|
|
|
def calcular_horas_trabalhadas(entrada1: str, saida1: str, entrada2: str = None, saida2: str = None) -> dict:
|
|
"""Calcula horas trabalhadas a partir dos registros de ponto"""
|
|
def time_to_minutes(time_str: str) -> int:
|
|
h, m = map(int, time_str.split(":"))
|
|
return h * 60 + m
|
|
|
|
minutos_periodo1 = time_to_minutes(saida1) - time_to_minutes(entrada1)
|
|
minutos_periodo2 = 0
|
|
|
|
if entrada2 and saida2:
|
|
minutos_periodo2 = time_to_minutes(saida2) - time_to_minutes(entrada2)
|
|
|
|
total_minutos = minutos_periodo1 + minutos_periodo2
|
|
total_horas = total_minutos / 60
|
|
|
|
return {
|
|
"periodo1": round(minutos_periodo1 / 60, 2),
|
|
"periodo2": round(minutos_periodo2 / 60, 2),
|
|
"totalHoras": round(total_horas, 2),
|
|
"totalMinutos": total_minutos
|
|
}
|
|
|
|
# ========== Endpoints ==========
|
|
|
|
@app.get("/")
|
|
async def root():
|
|
return {
|
|
"service": "Arcádia People",
|
|
"version": "1.0.0",
|
|
"status": "running",
|
|
"modules": ["funcionarios", "folha", "ferias", "ponto", "beneficios", "esocial"]
|
|
}
|
|
|
|
@app.get("/health")
|
|
async def health():
|
|
return {"status": "healthy", "timestamp": datetime.now().isoformat()}
|
|
|
|
@app.get("/tabelas/inss")
|
|
async def get_tabela_inss():
|
|
"""Retorna tabela INSS atual"""
|
|
return {
|
|
"vigencia": "2024",
|
|
"teto": TETO_INSS_2024,
|
|
"faixas": TABELA_INSS_2024
|
|
}
|
|
|
|
@app.get("/tabelas/irrf")
|
|
async def get_tabela_irrf():
|
|
"""Retorna tabela IRRF atual"""
|
|
return {
|
|
"vigencia": "2024",
|
|
"deducaoDependente": DEDUCAO_DEPENDENTE_IRRF,
|
|
"faixas": TABELA_IRRF_2024
|
|
}
|
|
|
|
@app.get("/tabelas/salario-familia")
|
|
async def get_tabela_salario_familia():
|
|
"""Retorna tabela Salário Família atual"""
|
|
return {
|
|
"vigencia": "2024",
|
|
"faixas": SALARIO_FAMILIA_2024
|
|
}
|
|
|
|
@app.post("/calcular/inss")
|
|
async def calcular_inss_endpoint(salario: float):
|
|
"""Calcula INSS para um salário"""
|
|
return calcular_inss(salario)
|
|
|
|
@app.post("/calcular/irrf")
|
|
async def calcular_irrf_endpoint(base: float, dependentes: int = 0):
|
|
"""Calcula IRRF para uma base"""
|
|
return calcular_irrf(base, dependentes)
|
|
|
|
@app.post("/calcular/folha")
|
|
async def calcular_folha(request: CalculoFolhaRequest):
|
|
"""Calcula folha de pagamento completa para um funcionário"""
|
|
|
|
# Cálculo do salário proporcional
|
|
salario_proporcional = request.salarioBase * (request.diasTrabalhados / 30)
|
|
|
|
# Desconto de faltas
|
|
valor_dia = request.salarioBase / 30
|
|
desconto_faltas = valor_dia * request.faltas
|
|
|
|
# Horas extras
|
|
valor_hora = request.salarioBase / 220 # 220 horas mensais padrão
|
|
valor_he_50 = request.horasExtras50 * (valor_hora * 1.5)
|
|
valor_he_100 = request.horasExtras100 * (valor_hora * 2.0)
|
|
|
|
# Adicional noturno (20%)
|
|
valor_adicional_noturno = request.horasNoturnas * (valor_hora * 0.2)
|
|
|
|
# Total de proventos
|
|
proventos_adicionais = sum(p.get("valor", 0) for p in request.proventosAdicionais)
|
|
total_proventos = (
|
|
salario_proporcional +
|
|
valor_he_50 +
|
|
valor_he_100 +
|
|
valor_adicional_noturno +
|
|
proventos_adicionais
|
|
)
|
|
|
|
# INSS
|
|
inss_calc = calcular_inss(total_proventos)
|
|
valor_inss = inss_calc["valorInss"]
|
|
|
|
# IRRF (base = bruto - INSS)
|
|
base_irrf = total_proventos - valor_inss
|
|
irrf_calc = calcular_irrf(base_irrf, request.dependentesIrrf)
|
|
valor_irrf = irrf_calc["valorIrrf"]
|
|
|
|
# FGTS
|
|
fgts_calc = calcular_fgts(total_proventos)
|
|
valor_fgts = fgts_calc["valorFgts"]
|
|
|
|
# Descontos adicionais
|
|
descontos_adicionais = sum(d.get("valor", 0) for d in request.descontosAdicionais)
|
|
|
|
# Total de descontos
|
|
total_descontos = valor_inss + valor_irrf + desconto_faltas + descontos_adicionais
|
|
|
|
# Líquido
|
|
total_liquido = total_proventos - total_descontos
|
|
|
|
return {
|
|
"funcionarioId": request.funcionarioId,
|
|
"competencia": request.competencia,
|
|
"proventos": {
|
|
"salarioBase": round(request.salarioBase, 2),
|
|
"salarioProporcional": round(salario_proporcional, 2),
|
|
"horasExtras50": {
|
|
"horas": request.horasExtras50,
|
|
"valor": round(valor_he_50, 2)
|
|
},
|
|
"horasExtras100": {
|
|
"horas": request.horasExtras100,
|
|
"valor": round(valor_he_100, 2)
|
|
},
|
|
"adicionalNoturno": {
|
|
"horas": request.horasNoturnas,
|
|
"valor": round(valor_adicional_noturno, 2)
|
|
},
|
|
"adicionais": request.proventosAdicionais,
|
|
"total": round(total_proventos, 2)
|
|
},
|
|
"descontos": {
|
|
"inss": inss_calc,
|
|
"irrf": irrf_calc,
|
|
"faltas": {
|
|
"dias": request.faltas,
|
|
"valor": round(desconto_faltas, 2)
|
|
},
|
|
"adicionais": request.descontosAdicionais,
|
|
"total": round(total_descontos, 2)
|
|
},
|
|
"encargos": {
|
|
"fgts": fgts_calc,
|
|
"inssPatronal": {
|
|
"aliquota": 20.0,
|
|
"valor": round(total_proventos * 0.20, 2)
|
|
}
|
|
},
|
|
"resumo": {
|
|
"totalProventos": round(total_proventos, 2),
|
|
"totalDescontos": round(total_descontos, 2),
|
|
"totalLiquido": round(total_liquido, 2),
|
|
"custoEmpresa": round(total_proventos + valor_fgts + (total_proventos * 0.20), 2)
|
|
}
|
|
}
|
|
|
|
@app.post("/calcular/ferias")
|
|
async def calcular_ferias(request: CalculoFeriasRequest):
|
|
"""Calcula férias"""
|
|
|
|
# Valor base das férias
|
|
valor_ferias = request.salarioBase * (request.diasFerias / 30)
|
|
|
|
# Adicional de 1/3
|
|
terco_constitucional = valor_ferias / 3
|
|
|
|
# Abono pecuniário (venda de dias)
|
|
valor_abono = 0
|
|
terco_abono = 0
|
|
if request.diasAbono > 0:
|
|
valor_abono = request.salarioBase * (request.diasAbono / 30)
|
|
terco_abono = valor_abono / 3
|
|
|
|
# Média de horas extras
|
|
media_he = request.mediaHorasExtras
|
|
|
|
# Total bruto
|
|
total_bruto = valor_ferias + terco_constitucional + valor_abono + terco_abono + media_he
|
|
|
|
# INSS sobre férias
|
|
inss_calc = calcular_inss(total_bruto)
|
|
valor_inss = inss_calc["valorInss"]
|
|
|
|
# IRRF
|
|
base_irrf = total_bruto - valor_inss
|
|
irrf_calc = calcular_irrf(base_irrf, request.dependentesIrrf)
|
|
valor_irrf = irrf_calc["valorIrrf"]
|
|
|
|
# Líquido
|
|
total_liquido = total_bruto - valor_inss - valor_irrf
|
|
|
|
return {
|
|
"funcionarioId": request.funcionarioId,
|
|
"diasFerias": request.diasFerias,
|
|
"diasAbono": request.diasAbono,
|
|
"proventos": {
|
|
"ferias": round(valor_ferias, 2),
|
|
"tercoConstitucional": round(terco_constitucional, 2),
|
|
"abonoPecuniario": round(valor_abono, 2),
|
|
"tercoAbono": round(terco_abono, 2),
|
|
"mediaHorasExtras": round(media_he, 2),
|
|
"total": round(total_bruto, 2)
|
|
},
|
|
"descontos": {
|
|
"inss": inss_calc,
|
|
"irrf": irrf_calc,
|
|
"total": round(valor_inss + valor_irrf, 2)
|
|
},
|
|
"resumo": {
|
|
"totalBruto": round(total_bruto, 2),
|
|
"totalDescontos": round(valor_inss + valor_irrf, 2),
|
|
"totalLiquido": round(total_liquido, 2)
|
|
}
|
|
}
|
|
|
|
@app.post("/calcular/rescisao")
|
|
async def calcular_rescisao(request: CalculoRescisaoRequest):
|
|
"""Calcula rescisão de contrato de trabalho"""
|
|
from datetime import datetime
|
|
|
|
data_admissao = datetime.strptime(request.dataAdmissao, "%Y-%m-%d")
|
|
data_demissao = datetime.strptime(request.dataDemissao, "%Y-%m-%d")
|
|
|
|
# Tempo de serviço
|
|
dias_trabalhados = (data_demissao - data_admissao).days
|
|
meses_trabalhados = dias_trabalhados / 30
|
|
anos_trabalhados = dias_trabalhados / 365
|
|
|
|
# Saldo de salário (dias do mês da demissão)
|
|
dia_demissao = data_demissao.day
|
|
saldo_salario = request.salarioBase * (dia_demissao / 30)
|
|
|
|
# 13º proporcional
|
|
meses_13 = data_demissao.month
|
|
decimo_terceiro = (request.salarioBase / 12) * meses_13
|
|
|
|
# Férias proporcionais + 1/3
|
|
meses_ferias = int(meses_trabalhados % 12)
|
|
if meses_ferias == 0:
|
|
meses_ferias = int(meses_trabalhados)
|
|
ferias_proporcionais = (request.salarioBase / 12) * meses_ferias
|
|
terco_ferias = ferias_proporcionais / 3
|
|
|
|
# Férias vencidas
|
|
ferias_vencidas = request.salarioBase * (request.saldoFerias / 30)
|
|
terco_vencidas = ferias_vencidas / 3
|
|
|
|
# Aviso prévio
|
|
aviso_previo = 0
|
|
dias_aviso = 30 + (3 * int(anos_trabalhados))
|
|
if dias_aviso > 90:
|
|
dias_aviso = 90
|
|
|
|
if request.tipoRescisao == "sem_justa_causa" and request.avisoPrevio == "indenizado":
|
|
aviso_previo = request.salarioBase * (dias_aviso / 30)
|
|
|
|
# Multa FGTS (40% ou 20%)
|
|
# Estimativa do saldo FGTS (8% do salário * meses)
|
|
saldo_fgts_estimado = (request.salarioBase * 0.08) * meses_trabalhados
|
|
multa_fgts = 0
|
|
|
|
if request.tipoRescisao == "sem_justa_causa":
|
|
multa_fgts = saldo_fgts_estimado * 0.40
|
|
elif request.tipoRescisao == "acordo":
|
|
multa_fgts = saldo_fgts_estimado * 0.20
|
|
|
|
# Total bruto
|
|
total_bruto = (
|
|
saldo_salario +
|
|
decimo_terceiro +
|
|
ferias_proporcionais +
|
|
terco_ferias +
|
|
ferias_vencidas +
|
|
terco_vencidas +
|
|
aviso_previo
|
|
)
|
|
|
|
# Descontos
|
|
inss_calc = calcular_inss(saldo_salario)
|
|
irrf_calc = calcular_irrf(total_bruto - inss_calc["valorInss"], request.dependentesIrrf)
|
|
|
|
total_descontos = inss_calc["valorInss"] + irrf_calc["valorIrrf"]
|
|
total_liquido = total_bruto - total_descontos + multa_fgts
|
|
|
|
return {
|
|
"funcionarioId": request.funcionarioId,
|
|
"tipoRescisao": request.tipoRescisao,
|
|
"tempoServico": {
|
|
"dias": dias_trabalhados,
|
|
"meses": round(meses_trabalhados, 1),
|
|
"anos": round(anos_trabalhados, 1)
|
|
},
|
|
"verbas": {
|
|
"saldoSalario": round(saldo_salario, 2),
|
|
"decimoTerceiro": round(decimo_terceiro, 2),
|
|
"feriasProporcionais": round(ferias_proporcionais, 2),
|
|
"tercoFerias": round(terco_ferias, 2),
|
|
"feriasVencidas": round(ferias_vencidas, 2),
|
|
"tercoVencidas": round(terco_vencidas, 2),
|
|
"avisoPrevio": {
|
|
"tipo": request.avisoPrevio,
|
|
"dias": dias_aviso,
|
|
"valor": round(aviso_previo, 2)
|
|
},
|
|
"totalBruto": round(total_bruto, 2)
|
|
},
|
|
"descontos": {
|
|
"inss": inss_calc,
|
|
"irrf": irrf_calc,
|
|
"total": round(total_descontos, 2)
|
|
},
|
|
"fgts": {
|
|
"saldoEstimado": round(saldo_fgts_estimado, 2),
|
|
"multa": round(multa_fgts, 2),
|
|
"percentualMulta": 40 if request.tipoRescisao == "sem_justa_causa" else 20 if request.tipoRescisao == "acordo" else 0
|
|
},
|
|
"resumo": {
|
|
"totalBruto": round(total_bruto, 2),
|
|
"totalDescontos": round(total_descontos, 2),
|
|
"multaFgts": round(multa_fgts, 2),
|
|
"totalLiquido": round(total_liquido, 2)
|
|
}
|
|
}
|
|
|
|
@app.post("/calcular/ponto")
|
|
async def calcular_ponto(request: PontoRequest):
|
|
"""Calcula horas trabalhadas e extras"""
|
|
|
|
horas = calcular_horas_trabalhadas(
|
|
request.entrada1,
|
|
request.saida1,
|
|
request.entrada2,
|
|
request.saida2
|
|
)
|
|
|
|
jornada_diaria = request.jornadaDiaria
|
|
horas_trabalhadas = horas["totalHoras"]
|
|
|
|
horas_extras = max(0, horas_trabalhadas - jornada_diaria)
|
|
horas_faltantes = max(0, jornada_diaria - horas_trabalhadas)
|
|
|
|
# Verificar adicional noturno (22h às 5h)
|
|
horas_noturnas = 0 # Simplificado - precisaria de lógica mais complexa
|
|
|
|
return {
|
|
"funcionarioId": request.funcionarioId,
|
|
"data": request.data,
|
|
"registros": {
|
|
"entrada1": request.entrada1,
|
|
"saida1": request.saida1,
|
|
"entrada2": request.entrada2,
|
|
"saida2": request.saida2
|
|
},
|
|
"calculo": {
|
|
"jornadaDiaria": jornada_diaria,
|
|
"horasTrabalhadas": round(horas_trabalhadas, 2),
|
|
"horasExtras": round(horas_extras, 2),
|
|
"horasFaltantes": round(horas_faltantes, 2),
|
|
"horasNoturnas": round(horas_noturnas, 2)
|
|
},
|
|
"detalhamento": horas
|
|
}
|
|
|
|
@app.get("/esocial/eventos")
|
|
async def get_eventos_esocial():
|
|
"""Lista eventos do eSocial suportados"""
|
|
eventos = [
|
|
{"codigo": "S-1000", "descricao": "Informações do Empregador", "tipo": "tabela"},
|
|
{"codigo": "S-1005", "descricao": "Tabela de Estabelecimentos", "tipo": "tabela"},
|
|
{"codigo": "S-1010", "descricao": "Tabela de Rubricas", "tipo": "tabela"},
|
|
{"codigo": "S-1020", "descricao": "Tabela de Lotações Tributárias", "tipo": "tabela"},
|
|
{"codigo": "S-1030", "descricao": "Tabela de Cargos/Empregos Públicos", "tipo": "tabela"},
|
|
{"codigo": "S-1040", "descricao": "Tabela de Funções/Cargos em Comissão", "tipo": "tabela"},
|
|
{"codigo": "S-1050", "descricao": "Tabela de Horários/Turnos de Trabalho", "tipo": "tabela"},
|
|
{"codigo": "S-1070", "descricao": "Tabela de Processos Administrativos/Judiciais", "tipo": "tabela"},
|
|
{"codigo": "S-2190", "descricao": "Registro Preliminar de Trabalhador", "tipo": "nao_periodico"},
|
|
{"codigo": "S-2200", "descricao": "Cadastramento Inicial / Admissão", "tipo": "nao_periodico"},
|
|
{"codigo": "S-2205", "descricao": "Alteração de Dados Cadastrais", "tipo": "nao_periodico"},
|
|
{"codigo": "S-2206", "descricao": "Alteração de Contrato de Trabalho", "tipo": "nao_periodico"},
|
|
{"codigo": "S-2230", "descricao": "Afastamento Temporário", "tipo": "nao_periodico"},
|
|
{"codigo": "S-2299", "descricao": "Desligamento", "tipo": "nao_periodico"},
|
|
{"codigo": "S-2300", "descricao": "Trabalhador Sem Vínculo - Início", "tipo": "nao_periodico"},
|
|
{"codigo": "S-2306", "descricao": "Trabalhador Sem Vínculo - Alteração", "tipo": "nao_periodico"},
|
|
{"codigo": "S-2399", "descricao": "Trabalhador Sem Vínculo - Término", "tipo": "nao_periodico"},
|
|
{"codigo": "S-1200", "descricao": "Remuneração de Trabalhador", "tipo": "periodico"},
|
|
{"codigo": "S-1210", "descricao": "Pagamentos de Rendimentos", "tipo": "periodico"},
|
|
{"codigo": "S-1260", "descricao": "Comercialização da Produção Rural", "tipo": "periodico"},
|
|
{"codigo": "S-1270", "descricao": "Contratação de Trabalhadores Avulsos", "tipo": "periodico"},
|
|
{"codigo": "S-1280", "descricao": "Informações Complementares aos Eventos Periódicos", "tipo": "periodico"},
|
|
{"codigo": "S-1298", "descricao": "Reabertura dos Eventos Periódicos", "tipo": "periodico"},
|
|
{"codigo": "S-1299", "descricao": "Fechamento dos Eventos Periódicos", "tipo": "periodico"},
|
|
]
|
|
|
|
return {
|
|
"eventos": eventos,
|
|
"total": len(eventos)
|
|
}
|
|
|
|
@app.post("/esocial/gerar-xml/{evento}")
|
|
async def gerar_xml_esocial(evento: str, dados: dict):
|
|
"""Gera XML para evento do eSocial"""
|
|
# Estrutura básica do XML do eSocial
|
|
xml_estrutura = {
|
|
"evento": evento,
|
|
"versao": "S-1.2",
|
|
"ambiente": dados.get("ambiente", "2"), # 1=Produção, 2=Homologação
|
|
"dados": dados,
|
|
"status": "estrutura_gerada",
|
|
"observacao": "XML será gerado conforme leiaute oficial do eSocial"
|
|
}
|
|
|
|
return xml_estrutura
|
|
|
|
if __name__ == "__main__":
|
|
import uvicorn
|
|
port = int(os.environ.get("PEOPLE_PORT", 8004))
|
|
uvicorn.run(app, host="0.0.0.0", port=port)
|