coati-payroll 0.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of coati-payroll might be problematic. Click here for more details.
- coati_payroll/__init__.py +415 -0
- coati_payroll/app.py +95 -0
- coati_payroll/audit_helpers.py +904 -0
- coati_payroll/auth.py +123 -0
- coati_payroll/cli.py +1318 -0
- coati_payroll/config.py +219 -0
- coati_payroll/demo_data.py +813 -0
- coati_payroll/enums.py +278 -0
- coati_payroll/forms.py +1769 -0
- coati_payroll/formula_engine/__init__.py +81 -0
- coati_payroll/formula_engine/ast/__init__.py +110 -0
- coati_payroll/formula_engine/ast/ast_visitor.py +259 -0
- coati_payroll/formula_engine/ast/expression_evaluator.py +228 -0
- coati_payroll/formula_engine/ast/safe_operators.py +131 -0
- coati_payroll/formula_engine/ast/type_converter.py +172 -0
- coati_payroll/formula_engine/data_sources.py +752 -0
- coati_payroll/formula_engine/engine.py +247 -0
- coati_payroll/formula_engine/exceptions.py +52 -0
- coati_payroll/formula_engine/execution/__init__.py +24 -0
- coati_payroll/formula_engine/execution/execution_context.py +52 -0
- coati_payroll/formula_engine/execution/step_executor.py +62 -0
- coati_payroll/formula_engine/execution/variable_store.py +59 -0
- coati_payroll/formula_engine/novelty_codes.py +206 -0
- coati_payroll/formula_engine/results/__init__.py +20 -0
- coati_payroll/formula_engine/results/execution_result.py +59 -0
- coati_payroll/formula_engine/steps/__init__.py +30 -0
- coati_payroll/formula_engine/steps/assignment_step.py +71 -0
- coati_payroll/formula_engine/steps/base_step.py +48 -0
- coati_payroll/formula_engine/steps/calculation_step.py +42 -0
- coati_payroll/formula_engine/steps/conditional_step.py +122 -0
- coati_payroll/formula_engine/steps/step_factory.py +58 -0
- coati_payroll/formula_engine/steps/tax_lookup_step.py +45 -0
- coati_payroll/formula_engine/tables/__init__.py +24 -0
- coati_payroll/formula_engine/tables/bracket_calculator.py +51 -0
- coati_payroll/formula_engine/tables/table_lookup.py +161 -0
- coati_payroll/formula_engine/tables/tax_table.py +32 -0
- coati_payroll/formula_engine/validation/__init__.py +24 -0
- coati_payroll/formula_engine/validation/schema_validator.py +37 -0
- coati_payroll/formula_engine/validation/security_validator.py +52 -0
- coati_payroll/formula_engine/validation/tax_table_validator.py +205 -0
- coati_payroll/formula_engine_examples.py +153 -0
- coati_payroll/i18n.py +54 -0
- coati_payroll/initial_data.py +613 -0
- coati_payroll/interes_engine.py +450 -0
- coati_payroll/liquidacion_engine/__init__.py +25 -0
- coati_payroll/liquidacion_engine/engine.py +267 -0
- coati_payroll/locale_config.py +165 -0
- coati_payroll/log.py +138 -0
- coati_payroll/model.py +2410 -0
- coati_payroll/nomina_engine/__init__.py +87 -0
- coati_payroll/nomina_engine/calculators/__init__.py +30 -0
- coati_payroll/nomina_engine/calculators/benefit_calculator.py +79 -0
- coati_payroll/nomina_engine/calculators/concept_calculator.py +254 -0
- coati_payroll/nomina_engine/calculators/deduction_calculator.py +105 -0
- coati_payroll/nomina_engine/calculators/exchange_rate_calculator.py +51 -0
- coati_payroll/nomina_engine/calculators/perception_calculator.py +75 -0
- coati_payroll/nomina_engine/calculators/salary_calculator.py +86 -0
- coati_payroll/nomina_engine/domain/__init__.py +27 -0
- coati_payroll/nomina_engine/domain/calculation_items.py +52 -0
- coati_payroll/nomina_engine/domain/employee_calculation.py +53 -0
- coati_payroll/nomina_engine/domain/payroll_context.py +44 -0
- coati_payroll/nomina_engine/engine.py +188 -0
- coati_payroll/nomina_engine/processors/__init__.py +28 -0
- coati_payroll/nomina_engine/processors/accounting_processor.py +171 -0
- coati_payroll/nomina_engine/processors/accumulation_processor.py +90 -0
- coati_payroll/nomina_engine/processors/loan_processor.py +227 -0
- coati_payroll/nomina_engine/processors/novelty_processor.py +42 -0
- coati_payroll/nomina_engine/processors/vacation_processor.py +67 -0
- coati_payroll/nomina_engine/repositories/__init__.py +32 -0
- coati_payroll/nomina_engine/repositories/acumulado_repository.py +83 -0
- coati_payroll/nomina_engine/repositories/base_repository.py +40 -0
- coati_payroll/nomina_engine/repositories/config_repository.py +102 -0
- coati_payroll/nomina_engine/repositories/employee_repository.py +34 -0
- coati_payroll/nomina_engine/repositories/exchange_rate_repository.py +58 -0
- coati_payroll/nomina_engine/repositories/novelty_repository.py +54 -0
- coati_payroll/nomina_engine/repositories/planilla_repository.py +52 -0
- coati_payroll/nomina_engine/results/__init__.py +24 -0
- coati_payroll/nomina_engine/results/error_result.py +28 -0
- coati_payroll/nomina_engine/results/payroll_result.py +53 -0
- coati_payroll/nomina_engine/results/validation_result.py +39 -0
- coati_payroll/nomina_engine/services/__init__.py +22 -0
- coati_payroll/nomina_engine/services/accounting_voucher_service.py +708 -0
- coati_payroll/nomina_engine/services/employee_processing_service.py +173 -0
- coati_payroll/nomina_engine/services/payroll_execution_service.py +374 -0
- coati_payroll/nomina_engine/services/snapshot_service.py +295 -0
- coati_payroll/nomina_engine/validators/__init__.py +31 -0
- coati_payroll/nomina_engine/validators/base_validator.py +48 -0
- coati_payroll/nomina_engine/validators/currency_validator.py +50 -0
- coati_payroll/nomina_engine/validators/employee_validator.py +87 -0
- coati_payroll/nomina_engine/validators/period_validator.py +44 -0
- coati_payroll/nomina_engine/validators/planilla_validator.py +136 -0
- coati_payroll/plugin_manager.py +176 -0
- coati_payroll/queue/__init__.py +33 -0
- coati_payroll/queue/driver.py +127 -0
- coati_payroll/queue/drivers/__init__.py +22 -0
- coati_payroll/queue/drivers/dramatiq_driver.py +268 -0
- coati_payroll/queue/drivers/huey_driver.py +390 -0
- coati_payroll/queue/drivers/noop_driver.py +54 -0
- coati_payroll/queue/selector.py +121 -0
- coati_payroll/queue/tasks.py +764 -0
- coati_payroll/rate_limiting.py +83 -0
- coati_payroll/rbac.py +183 -0
- coati_payroll/report_engine.py +512 -0
- coati_payroll/report_export.py +208 -0
- coati_payroll/schema_validator.py +167 -0
- coati_payroll/security.py +77 -0
- coati_payroll/static/styles.css +1044 -0
- coati_payroll/system_reports.py +573 -0
- coati_payroll/templates/auth/login.html +189 -0
- coati_payroll/templates/base.html +283 -0
- coati_payroll/templates/index.html +227 -0
- coati_payroll/templates/macros.html +146 -0
- coati_payroll/templates/modules/calculation_rule/form.html +78 -0
- coati_payroll/templates/modules/calculation_rule/index.html +102 -0
- coati_payroll/templates/modules/calculation_rule/schema_editor.html +1159 -0
- coati_payroll/templates/modules/carga_inicial_prestacion/form.html +170 -0
- coati_payroll/templates/modules/carga_inicial_prestacion/index.html +170 -0
- coati_payroll/templates/modules/carga_inicial_prestacion/reporte.html +193 -0
- coati_payroll/templates/modules/config_calculos/index.html +44 -0
- coati_payroll/templates/modules/configuracion/index.html +90 -0
- coati_payroll/templates/modules/currency/form.html +47 -0
- coati_payroll/templates/modules/currency/index.html +64 -0
- coati_payroll/templates/modules/custom_field/form.html +62 -0
- coati_payroll/templates/modules/custom_field/index.html +78 -0
- coati_payroll/templates/modules/deduccion/form.html +1 -0
- coati_payroll/templates/modules/deduccion/index.html +1 -0
- coati_payroll/templates/modules/employee/form.html +254 -0
- coati_payroll/templates/modules/employee/index.html +76 -0
- coati_payroll/templates/modules/empresa/form.html +74 -0
- coati_payroll/templates/modules/empresa/index.html +71 -0
- coati_payroll/templates/modules/exchange_rate/form.html +47 -0
- coati_payroll/templates/modules/exchange_rate/import.html +93 -0
- coati_payroll/templates/modules/exchange_rate/index.html +114 -0
- coati_payroll/templates/modules/liquidacion/index.html +58 -0
- coati_payroll/templates/modules/liquidacion/nueva.html +51 -0
- coati_payroll/templates/modules/liquidacion/ver.html +91 -0
- coati_payroll/templates/modules/payroll_concepts/audit_log.html +146 -0
- coati_payroll/templates/modules/percepcion/form.html +1 -0
- coati_payroll/templates/modules/percepcion/index.html +1 -0
- coati_payroll/templates/modules/planilla/config.html +190 -0
- coati_payroll/templates/modules/planilla/config_deducciones.html +129 -0
- coati_payroll/templates/modules/planilla/config_empleados.html +116 -0
- coati_payroll/templates/modules/planilla/config_percepciones.html +113 -0
- coati_payroll/templates/modules/planilla/config_prestaciones.html +118 -0
- coati_payroll/templates/modules/planilla/config_reglas.html +120 -0
- coati_payroll/templates/modules/planilla/ejecutar_nomina.html +106 -0
- coati_payroll/templates/modules/planilla/form.html +197 -0
- coati_payroll/templates/modules/planilla/index.html +144 -0
- coati_payroll/templates/modules/planilla/listar_nominas.html +91 -0
- coati_payroll/templates/modules/planilla/log_nomina.html +135 -0
- coati_payroll/templates/modules/planilla/novedades/form.html +177 -0
- coati_payroll/templates/modules/planilla/novedades/index.html +170 -0
- coati_payroll/templates/modules/planilla/ver_nomina.html +477 -0
- coati_payroll/templates/modules/planilla/ver_nomina_empleado.html +231 -0
- coati_payroll/templates/modules/plugins/index.html +71 -0
- coati_payroll/templates/modules/prestacion/form.html +1 -0
- coati_payroll/templates/modules/prestacion/index.html +1 -0
- coati_payroll/templates/modules/prestacion_management/dashboard.html +150 -0
- coati_payroll/templates/modules/prestacion_management/initial_balance_bulk.html +195 -0
- coati_payroll/templates/modules/prestamo/approve.html +156 -0
- coati_payroll/templates/modules/prestamo/condonacion.html +249 -0
- coati_payroll/templates/modules/prestamo/detail.html +443 -0
- coati_payroll/templates/modules/prestamo/form.html +203 -0
- coati_payroll/templates/modules/prestamo/index.html +150 -0
- coati_payroll/templates/modules/prestamo/pago_extraordinario.html +211 -0
- coati_payroll/templates/modules/prestamo/tabla_pago_pdf.html +181 -0
- coati_payroll/templates/modules/report/admin_index.html +125 -0
- coati_payroll/templates/modules/report/detail.html +129 -0
- coati_payroll/templates/modules/report/execute.html +266 -0
- coati_payroll/templates/modules/report/index.html +95 -0
- coati_payroll/templates/modules/report/permissions.html +64 -0
- coati_payroll/templates/modules/settings/index.html +274 -0
- coati_payroll/templates/modules/shared/concept_form.html +201 -0
- coati_payroll/templates/modules/shared/concept_index.html +145 -0
- coati_payroll/templates/modules/tipo_planilla/form.html +70 -0
- coati_payroll/templates/modules/tipo_planilla/index.html +68 -0
- coati_payroll/templates/modules/user/form.html +65 -0
- coati_payroll/templates/modules/user/index.html +76 -0
- coati_payroll/templates/modules/user/profile.html +81 -0
- coati_payroll/templates/modules/vacation/account_detail.html +149 -0
- coati_payroll/templates/modules/vacation/account_form.html +52 -0
- coati_payroll/templates/modules/vacation/account_index.html +68 -0
- coati_payroll/templates/modules/vacation/dashboard.html +156 -0
- coati_payroll/templates/modules/vacation/initial_balance_bulk.html +149 -0
- coati_payroll/templates/modules/vacation/initial_balance_form.html +93 -0
- coati_payroll/templates/modules/vacation/leave_request_detail.html +158 -0
- coati_payroll/templates/modules/vacation/leave_request_form.html +61 -0
- coati_payroll/templates/modules/vacation/leave_request_index.html +98 -0
- coati_payroll/templates/modules/vacation/policy_detail.html +176 -0
- coati_payroll/templates/modules/vacation/policy_form.html +152 -0
- coati_payroll/templates/modules/vacation/policy_index.html +79 -0
- coati_payroll/templates/modules/vacation/register_taken_form.html +178 -0
- coati_payroll/translations/en/LC_MESSAGES/messages.mo +0 -0
- coati_payroll/translations/en/LC_MESSAGES/messages.po +7283 -0
- coati_payroll/translations/es/LC_MESSAGES/messages.mo +0 -0
- coati_payroll/translations/es/LC_MESSAGES/messages.po +7374 -0
- coati_payroll/vacation_service.py +451 -0
- coati_payroll/version.py +18 -0
- coati_payroll/vistas/__init__.py +64 -0
- coati_payroll/vistas/calculation_rule.py +307 -0
- coati_payroll/vistas/carga_inicial_prestacion.py +423 -0
- coati_payroll/vistas/config_calculos.py +72 -0
- coati_payroll/vistas/configuracion.py +87 -0
- coati_payroll/vistas/constants.py +17 -0
- coati_payroll/vistas/currency.py +112 -0
- coati_payroll/vistas/custom_field.py +120 -0
- coati_payroll/vistas/employee.py +305 -0
- coati_payroll/vistas/empresa.py +153 -0
- coati_payroll/vistas/exchange_rate.py +341 -0
- coati_payroll/vistas/liquidacion.py +205 -0
- coati_payroll/vistas/payroll_concepts.py +580 -0
- coati_payroll/vistas/planilla/__init__.py +38 -0
- coati_payroll/vistas/planilla/association_routes.py +238 -0
- coati_payroll/vistas/planilla/config_routes.py +158 -0
- coati_payroll/vistas/planilla/export_routes.py +175 -0
- coati_payroll/vistas/planilla/helpers/__init__.py +34 -0
- coati_payroll/vistas/planilla/helpers/association_helpers.py +161 -0
- coati_payroll/vistas/planilla/helpers/excel_helpers.py +29 -0
- coati_payroll/vistas/planilla/helpers/form_helpers.py +97 -0
- coati_payroll/vistas/planilla/nomina_routes.py +488 -0
- coati_payroll/vistas/planilla/novedad_routes.py +227 -0
- coati_payroll/vistas/planilla/routes.py +145 -0
- coati_payroll/vistas/planilla/services/__init__.py +26 -0
- coati_payroll/vistas/planilla/services/export_service.py +687 -0
- coati_payroll/vistas/planilla/services/nomina_service.py +233 -0
- coati_payroll/vistas/planilla/services/novedad_service.py +126 -0
- coati_payroll/vistas/planilla/services/planilla_service.py +34 -0
- coati_payroll/vistas/planilla/validators/__init__.py +18 -0
- coati_payroll/vistas/planilla/validators/planilla_validators.py +40 -0
- coati_payroll/vistas/plugins.py +45 -0
- coati_payroll/vistas/prestacion.py +272 -0
- coati_payroll/vistas/prestamo.py +808 -0
- coati_payroll/vistas/report.py +432 -0
- coati_payroll/vistas/settings.py +29 -0
- coati_payroll/vistas/tipo_planilla.py +134 -0
- coati_payroll/vistas/user.py +172 -0
- coati_payroll/vistas/vacation.py +1045 -0
- coati_payroll-0.0.2.dist-info/LICENSE +201 -0
- coati_payroll-0.0.2.dist-info/METADATA +581 -0
- coati_payroll-0.0.2.dist-info/RECORD +243 -0
- coati_payroll-0.0.2.dist-info/WHEEL +5 -0
- coati_payroll-0.0.2.dist-info/entry_points.txt +2 -0
- coati_payroll-0.0.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
# Copyright 2025 BMO Soluciones, S.A.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
"""System reports definitions and implementations.
|
|
15
|
+
|
|
16
|
+
This module contains all pre-defined system reports that are optimized
|
|
17
|
+
and built into the application core. Each system report has a unique
|
|
18
|
+
identifier and an implementation function.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
# <-------------------------------------------------------------------------> #
|
|
24
|
+
# Standard library
|
|
25
|
+
# <-------------------------------------------------------------------------> #
|
|
26
|
+
from datetime import datetime
|
|
27
|
+
from typing import Dict, Any, List, Optional, Callable
|
|
28
|
+
|
|
29
|
+
# <-------------------------------------------------------------------------> #
|
|
30
|
+
# Third party libraries
|
|
31
|
+
# <-------------------------------------------------------------------------> #
|
|
32
|
+
from sqlalchemy import func
|
|
33
|
+
|
|
34
|
+
# <-------------------------------------------------------------------------> #
|
|
35
|
+
# Local modules
|
|
36
|
+
# <-------------------------------------------------------------------------> #
|
|
37
|
+
from coati_payroll.model import (
|
|
38
|
+
db,
|
|
39
|
+
Empleado,
|
|
40
|
+
Nomina,
|
|
41
|
+
NominaEmpleado,
|
|
42
|
+
NominaDetalle,
|
|
43
|
+
VacationAccount,
|
|
44
|
+
VacationLedger,
|
|
45
|
+
)
|
|
46
|
+
from coati_payroll.enums import TipoDetalle
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# ============================================================================
|
|
50
|
+
# System Report Registry
|
|
51
|
+
# ============================================================================
|
|
52
|
+
|
|
53
|
+
# Registry of all system reports
|
|
54
|
+
# Key: system_report_id
|
|
55
|
+
# Value: implementation function
|
|
56
|
+
SYSTEM_REPORTS: Dict[str, Callable] = {}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def register_system_report(report_id: str):
|
|
60
|
+
"""Decorator to register a system report implementation.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
report_id: Unique identifier for the system report
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
def decorator(func: Callable):
|
|
67
|
+
SYSTEM_REPORTS[report_id] = func
|
|
68
|
+
return func
|
|
69
|
+
|
|
70
|
+
return decorator
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def get_system_report(report_id: str) -> Optional[Callable]:
|
|
74
|
+
"""Get system report implementation by ID.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
report_id: System report identifier
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
Report implementation function or None
|
|
81
|
+
"""
|
|
82
|
+
return SYSTEM_REPORTS.get(report_id)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# ============================================================================
|
|
86
|
+
# Employee Reports
|
|
87
|
+
# ============================================================================
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@register_system_report("employee_list")
|
|
91
|
+
def employee_list_report(parameters: Dict[str, Any]) -> List[Dict[str, Any]]:
|
|
92
|
+
"""General list of employees.
|
|
93
|
+
|
|
94
|
+
Parameters:
|
|
95
|
+
- activo (optional): Filter by active status
|
|
96
|
+
- empresa_id (optional): Filter by company
|
|
97
|
+
"""
|
|
98
|
+
stmt = db.select(Empleado)
|
|
99
|
+
|
|
100
|
+
# Apply filters
|
|
101
|
+
if "activo" in parameters:
|
|
102
|
+
stmt = stmt.filter(Empleado.activo == parameters["activo"])
|
|
103
|
+
|
|
104
|
+
if "empresa_id" in parameters:
|
|
105
|
+
stmt = stmt.filter(Empleado.empresa_id == parameters["empresa_id"])
|
|
106
|
+
|
|
107
|
+
results = db.session.execute(stmt.order_by(Empleado.primer_apellido, Empleado.primer_nombre)).scalars().all()
|
|
108
|
+
|
|
109
|
+
return [
|
|
110
|
+
{
|
|
111
|
+
"Código": emp.codigo_empleado,
|
|
112
|
+
"Nombres": f"{emp.primer_nombre} {emp.segundo_nombre or ''}".strip(),
|
|
113
|
+
"Apellidos": f"{emp.primer_apellido} {emp.segundo_apellido or ''}".strip(),
|
|
114
|
+
"Identificación": emp.identificacion_personal,
|
|
115
|
+
"Cargo": emp.cargo or "",
|
|
116
|
+
"Área": emp.area or "",
|
|
117
|
+
"Salario Base": float(emp.salario_base) if emp.salario_base else 0.0,
|
|
118
|
+
"Fecha Alta": emp.fecha_alta.isoformat() if emp.fecha_alta else "",
|
|
119
|
+
"Activo": "Sí" if emp.activo else "No",
|
|
120
|
+
}
|
|
121
|
+
for emp in results
|
|
122
|
+
]
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@register_system_report("employee_active_inactive")
|
|
126
|
+
def employee_active_inactive_report(parameters: Dict[str, Any]) -> List[Dict[str, Any]]:
|
|
127
|
+
"""Active and inactive employees report.
|
|
128
|
+
|
|
129
|
+
Shows current status of all employees with relevant dates.
|
|
130
|
+
"""
|
|
131
|
+
results = (
|
|
132
|
+
db.session.execute(db.select(Empleado).order_by(Empleado.activo.desc(), Empleado.primer_apellido))
|
|
133
|
+
.scalars()
|
|
134
|
+
.all()
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
return [
|
|
138
|
+
{
|
|
139
|
+
"Código": emp.codigo_empleado,
|
|
140
|
+
"Nombre Completo": f"{emp.primer_nombre} {emp.primer_apellido}",
|
|
141
|
+
"Identificación": emp.identificacion_personal,
|
|
142
|
+
"Estado": "Activo" if emp.activo else "Inactivo",
|
|
143
|
+
"Fecha Alta": emp.fecha_alta.isoformat() if emp.fecha_alta else "",
|
|
144
|
+
"Fecha Baja": emp.fecha_baja.isoformat() if emp.fecha_baja else "",
|
|
145
|
+
"Cargo": emp.cargo or "",
|
|
146
|
+
}
|
|
147
|
+
for emp in results
|
|
148
|
+
]
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@register_system_report("employee_by_department")
|
|
152
|
+
def employee_by_department_report(parameters: Dict[str, Any]) -> List[Dict[str, Any]]:
|
|
153
|
+
"""Employees by department/area.
|
|
154
|
+
|
|
155
|
+
Groups employees by area and provides summary statistics.
|
|
156
|
+
"""
|
|
157
|
+
results = (
|
|
158
|
+
db.session.execute(
|
|
159
|
+
db.select(Empleado)
|
|
160
|
+
.filter(Empleado.activo == True) # noqa: E712
|
|
161
|
+
.order_by(Empleado.area, Empleado.primer_apellido)
|
|
162
|
+
)
|
|
163
|
+
.scalars()
|
|
164
|
+
.all()
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
return [
|
|
168
|
+
{
|
|
169
|
+
"Área": emp.area or "Sin Asignar",
|
|
170
|
+
"Código": emp.codigo_empleado,
|
|
171
|
+
"Nombre": f"{emp.primer_nombre} {emp.primer_apellido}",
|
|
172
|
+
"Cargo": emp.cargo or "",
|
|
173
|
+
"Centro de Costos": emp.centro_costos or "",
|
|
174
|
+
"Salario Base": float(emp.salario_base) if emp.salario_base else 0.0,
|
|
175
|
+
}
|
|
176
|
+
for emp in results
|
|
177
|
+
]
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@register_system_report("employee_hires_terminations")
|
|
181
|
+
def employee_hires_terminations_report(parameters: Dict[str, Any]) -> List[Dict[str, Any]]:
|
|
182
|
+
"""Hires and terminations by period.
|
|
183
|
+
|
|
184
|
+
Parameters:
|
|
185
|
+
- fecha_inicio: Start date
|
|
186
|
+
- fecha_fin: End date
|
|
187
|
+
"""
|
|
188
|
+
fecha_inicio = parameters.get("fecha_inicio")
|
|
189
|
+
fecha_fin = parameters.get("fecha_fin")
|
|
190
|
+
|
|
191
|
+
if isinstance(fecha_inicio, str):
|
|
192
|
+
fecha_inicio = datetime.fromisoformat(fecha_inicio).date()
|
|
193
|
+
if isinstance(fecha_fin, str):
|
|
194
|
+
fecha_fin = datetime.fromisoformat(fecha_fin).date()
|
|
195
|
+
|
|
196
|
+
# Get hires
|
|
197
|
+
hires_stmt = db.select(Empleado)
|
|
198
|
+
if fecha_inicio:
|
|
199
|
+
hires_stmt = hires_stmt.filter(Empleado.fecha_alta >= fecha_inicio)
|
|
200
|
+
if fecha_fin:
|
|
201
|
+
hires_stmt = hires_stmt.filter(Empleado.fecha_alta <= fecha_fin)
|
|
202
|
+
|
|
203
|
+
hires = db.session.execute(hires_stmt).scalars().all()
|
|
204
|
+
|
|
205
|
+
# Get terminations
|
|
206
|
+
terminations_stmt = db.select(Empleado).filter(Empleado.fecha_baja.isnot(None))
|
|
207
|
+
if fecha_inicio:
|
|
208
|
+
terminations_stmt = terminations_stmt.filter(Empleado.fecha_baja >= fecha_inicio)
|
|
209
|
+
if fecha_fin:
|
|
210
|
+
terminations_stmt = terminations_stmt.filter(Empleado.fecha_baja <= fecha_fin)
|
|
211
|
+
|
|
212
|
+
terminations = db.session.execute(terminations_stmt).scalars().all()
|
|
213
|
+
|
|
214
|
+
results = []
|
|
215
|
+
|
|
216
|
+
# Add hires
|
|
217
|
+
for emp in hires:
|
|
218
|
+
results.append(
|
|
219
|
+
{
|
|
220
|
+
"Tipo": "Alta",
|
|
221
|
+
"Fecha": emp.fecha_alta.isoformat() if emp.fecha_alta else "",
|
|
222
|
+
"Código": emp.codigo_empleado,
|
|
223
|
+
"Nombre": f"{emp.primer_nombre} {emp.primer_apellido}",
|
|
224
|
+
"Cargo": emp.cargo or "",
|
|
225
|
+
"Área": emp.area or "",
|
|
226
|
+
}
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
# Add terminations
|
|
230
|
+
for emp in terminations:
|
|
231
|
+
results.append(
|
|
232
|
+
{
|
|
233
|
+
"Tipo": "Baja",
|
|
234
|
+
"Fecha": emp.fecha_baja.isoformat() if emp.fecha_baja else "",
|
|
235
|
+
"Código": emp.codigo_empleado,
|
|
236
|
+
"Nombre": f"{emp.primer_nombre} {emp.primer_apellido}",
|
|
237
|
+
"Cargo": emp.cargo or "",
|
|
238
|
+
"Área": emp.area or "",
|
|
239
|
+
}
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
# Sort by date
|
|
243
|
+
results.sort(key=lambda x: x["Fecha"])
|
|
244
|
+
|
|
245
|
+
return results
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
# ============================================================================
|
|
249
|
+
# Payroll Reports
|
|
250
|
+
# ============================================================================
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
@register_system_report("payroll_by_period")
|
|
254
|
+
def payroll_by_period_report(parameters: Dict[str, Any]) -> List[Dict[str, Any]]:
|
|
255
|
+
"""Payroll summary by period.
|
|
256
|
+
|
|
257
|
+
Parameters:
|
|
258
|
+
- periodo_inicio: Start date of period
|
|
259
|
+
- periodo_fin: End date of period
|
|
260
|
+
- planilla_id (optional): Filter by payroll template
|
|
261
|
+
"""
|
|
262
|
+
stmt = db.select(Nomina)
|
|
263
|
+
|
|
264
|
+
if "periodo_inicio" in parameters:
|
|
265
|
+
fecha = parameters["periodo_inicio"]
|
|
266
|
+
if isinstance(fecha, str):
|
|
267
|
+
fecha = datetime.fromisoformat(fecha).date()
|
|
268
|
+
stmt = stmt.filter(Nomina.periodo_inicio >= fecha)
|
|
269
|
+
|
|
270
|
+
if "periodo_fin" in parameters:
|
|
271
|
+
fecha = parameters["periodo_fin"]
|
|
272
|
+
if isinstance(fecha, str):
|
|
273
|
+
fecha = datetime.fromisoformat(fecha).date()
|
|
274
|
+
stmt = stmt.filter(Nomina.periodo_fin <= fecha)
|
|
275
|
+
|
|
276
|
+
if "planilla_id" in parameters:
|
|
277
|
+
stmt = stmt.filter(Nomina.planilla_id == parameters["planilla_id"])
|
|
278
|
+
|
|
279
|
+
results = db.session.execute(stmt.order_by(Nomina.periodo_inicio.desc())).scalars().all()
|
|
280
|
+
|
|
281
|
+
return [
|
|
282
|
+
{
|
|
283
|
+
"Código": nomina.codigo_nomina,
|
|
284
|
+
"Descripción": nomina.descripcion or "",
|
|
285
|
+
"Período Inicio": nomina.periodo_inicio.isoformat() if nomina.periodo_inicio else "",
|
|
286
|
+
"Período Fin": nomina.periodo_fin.isoformat() if nomina.periodo_fin else "",
|
|
287
|
+
"Fecha Pago": nomina.fecha_pago.isoformat() if nomina.fecha_pago else "",
|
|
288
|
+
"Estado": nomina.estado,
|
|
289
|
+
"Total Bruto": float(nomina.total_bruto) if nomina.total_bruto else 0.0,
|
|
290
|
+
"Total Deducciones": float(nomina.total_deducciones) if nomina.total_deducciones else 0.0,
|
|
291
|
+
"Total Neto": float(nomina.total_neto) if nomina.total_neto else 0.0,
|
|
292
|
+
}
|
|
293
|
+
for nomina in results
|
|
294
|
+
]
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
@register_system_report("payroll_employee_detail")
|
|
298
|
+
def payroll_employee_detail_report(parameters: Dict[str, Any]) -> List[Dict[str, Any]]:
|
|
299
|
+
"""Detailed payroll by employee.
|
|
300
|
+
|
|
301
|
+
Parameters:
|
|
302
|
+
- nomina_id: Payroll run ID
|
|
303
|
+
"""
|
|
304
|
+
nomina_id = parameters.get("nomina_id")
|
|
305
|
+
|
|
306
|
+
if not nomina_id:
|
|
307
|
+
return []
|
|
308
|
+
|
|
309
|
+
results = db.session.execute(
|
|
310
|
+
db.select(NominaEmpleado, Empleado)
|
|
311
|
+
.join(Empleado, NominaEmpleado.empleado_id == Empleado.id)
|
|
312
|
+
.filter(NominaEmpleado.nomina_id == nomina_id)
|
|
313
|
+
.order_by(Empleado.primer_apellido, Empleado.primer_nombre)
|
|
314
|
+
).all()
|
|
315
|
+
|
|
316
|
+
return [
|
|
317
|
+
{
|
|
318
|
+
"Código Empleado": empleado.codigo_empleado,
|
|
319
|
+
"Nombre": f"{empleado.primer_nombre} {empleado.primer_apellido}",
|
|
320
|
+
"Salario Base": float(nomina_emp.salario_base) if nomina_emp.salario_base else 0.0,
|
|
321
|
+
"Total Percepciones": float(nomina_emp.total_percepciones) if nomina_emp.total_percepciones else 0.0,
|
|
322
|
+
"Salario Bruto": float(nomina_emp.salario_bruto) if nomina_emp.salario_bruto else 0.0,
|
|
323
|
+
"Total Deducciones": float(nomina_emp.total_deducciones) if nomina_emp.total_deducciones else 0.0,
|
|
324
|
+
"Salario Neto": float(nomina_emp.salario_neto) if nomina_emp.salario_neto else 0.0,
|
|
325
|
+
"Total Prestaciones": float(nomina_emp.total_prestaciones) if nomina_emp.total_prestaciones else 0.0,
|
|
326
|
+
}
|
|
327
|
+
for nomina_emp, empleado in results
|
|
328
|
+
]
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
@register_system_report("payroll_perceptions_summary")
|
|
332
|
+
def payroll_perceptions_summary_report(parameters: Dict[str, Any]) -> List[Dict[str, Any]]:
|
|
333
|
+
"""Summary of perceptions by period.
|
|
334
|
+
|
|
335
|
+
Parameters:
|
|
336
|
+
- nomina_id: Payroll run ID
|
|
337
|
+
"""
|
|
338
|
+
nomina_id = parameters.get("nomina_id")
|
|
339
|
+
|
|
340
|
+
if not nomina_id:
|
|
341
|
+
return []
|
|
342
|
+
|
|
343
|
+
results = db.session.execute(
|
|
344
|
+
db.select(
|
|
345
|
+
NominaDetalle.concepto_codigo,
|
|
346
|
+
NominaDetalle.concepto_nombre,
|
|
347
|
+
func.count(NominaDetalle.id).label("cantidad_empleados"),
|
|
348
|
+
func.sum(NominaDetalle.monto).label("total"),
|
|
349
|
+
)
|
|
350
|
+
.filter(NominaDetalle.nomina_id == nomina_id, NominaDetalle.tipo == TipoDetalle.INGRESO)
|
|
351
|
+
.group_by(NominaDetalle.concepto_codigo, NominaDetalle.concepto_nombre)
|
|
352
|
+
.order_by(NominaDetalle.concepto_codigo)
|
|
353
|
+
).all()
|
|
354
|
+
|
|
355
|
+
return [
|
|
356
|
+
{
|
|
357
|
+
"Código Concepto": row.concepto_codigo,
|
|
358
|
+
"Concepto": row.concepto_nombre,
|
|
359
|
+
"Cantidad Empleados": row.cantidad_empleados,
|
|
360
|
+
"Total": float(row.total) if row.total else 0.0,
|
|
361
|
+
}
|
|
362
|
+
for row in results
|
|
363
|
+
]
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
@register_system_report("payroll_deductions_summary")
|
|
367
|
+
def payroll_deductions_summary_report(parameters: Dict[str, Any]) -> List[Dict[str, Any]]:
|
|
368
|
+
"""Summary of deductions by period.
|
|
369
|
+
|
|
370
|
+
Parameters:
|
|
371
|
+
- nomina_id: Payroll run ID
|
|
372
|
+
"""
|
|
373
|
+
nomina_id = parameters.get("nomina_id")
|
|
374
|
+
|
|
375
|
+
if not nomina_id:
|
|
376
|
+
return []
|
|
377
|
+
|
|
378
|
+
results = db.session.execute(
|
|
379
|
+
db.select(
|
|
380
|
+
NominaDetalle.concepto_codigo,
|
|
381
|
+
NominaDetalle.concepto_nombre,
|
|
382
|
+
func.count(NominaDetalle.id).label("cantidad_empleados"),
|
|
383
|
+
func.sum(NominaDetalle.monto).label("total"),
|
|
384
|
+
)
|
|
385
|
+
.filter(NominaDetalle.nomina_id == nomina_id, NominaDetalle.tipo == TipoDetalle.DEDUCCION)
|
|
386
|
+
.group_by(NominaDetalle.concepto_codigo, NominaDetalle.concepto_nombre)
|
|
387
|
+
.order_by(NominaDetalle.concepto_codigo)
|
|
388
|
+
).all()
|
|
389
|
+
|
|
390
|
+
return [
|
|
391
|
+
{
|
|
392
|
+
"Código Concepto": row.concepto_codigo,
|
|
393
|
+
"Concepto": row.concepto_nombre,
|
|
394
|
+
"Cantidad Empleados": row.cantidad_empleados,
|
|
395
|
+
"Total": float(row.total) if row.total else 0.0,
|
|
396
|
+
}
|
|
397
|
+
for row in results
|
|
398
|
+
]
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
# ============================================================================
|
|
402
|
+
# Vacation Reports
|
|
403
|
+
# ============================================================================
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
@register_system_report("vacation_balance_by_employee")
|
|
407
|
+
def vacation_balance_report(parameters: Dict[str, Any]) -> List[Dict[str, Any]]:
|
|
408
|
+
"""Vacation balance by employee.
|
|
409
|
+
|
|
410
|
+
Shows current vacation balances for all employees.
|
|
411
|
+
"""
|
|
412
|
+
results = db.session.execute(
|
|
413
|
+
db.select(VacationAccount, Empleado)
|
|
414
|
+
.join(Empleado, VacationAccount.empleado_id == Empleado.id)
|
|
415
|
+
.filter(Empleado.activo == True) # noqa: E712
|
|
416
|
+
.order_by(Empleado.primer_apellido, Empleado.primer_nombre)
|
|
417
|
+
).all()
|
|
418
|
+
|
|
419
|
+
return [
|
|
420
|
+
{
|
|
421
|
+
"Código Empleado": empleado.codigo_empleado,
|
|
422
|
+
"Nombre": f"{empleado.primer_nombre} {empleado.primer_apellido}",
|
|
423
|
+
"Días Acumulados": float(account.accrued_days) if account.accrued_days else 0.0,
|
|
424
|
+
"Días Usados": float(account.used_days) if account.used_days else 0.0,
|
|
425
|
+
"Balance Días": float(account.balance_days) if account.balance_days else 0.0,
|
|
426
|
+
}
|
|
427
|
+
for account, empleado in results
|
|
428
|
+
]
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
@register_system_report("vacation_taken_by_period")
|
|
432
|
+
def vacation_taken_by_period_report(parameters: Dict[str, Any]) -> List[Dict[str, Any]]:
|
|
433
|
+
"""Vacations taken by period.
|
|
434
|
+
|
|
435
|
+
Parameters:
|
|
436
|
+
- fecha_inicio: Start date
|
|
437
|
+
- fecha_fin: End date
|
|
438
|
+
"""
|
|
439
|
+
fecha_inicio = parameters.get("fecha_inicio")
|
|
440
|
+
fecha_fin = parameters.get("fecha_fin")
|
|
441
|
+
|
|
442
|
+
if isinstance(fecha_inicio, str):
|
|
443
|
+
fecha_inicio = datetime.fromisoformat(fecha_inicio).date()
|
|
444
|
+
if isinstance(fecha_fin, str):
|
|
445
|
+
fecha_fin = datetime.fromisoformat(fecha_fin).date()
|
|
446
|
+
|
|
447
|
+
stmt = (
|
|
448
|
+
db.select(VacationLedger, Empleado)
|
|
449
|
+
.join(Empleado, VacationLedger.empleado_id == Empleado.id)
|
|
450
|
+
.filter(VacationLedger.entry_type == "usage")
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
if fecha_inicio:
|
|
454
|
+
stmt = stmt.filter(VacationLedger.entry_date >= fecha_inicio)
|
|
455
|
+
if fecha_fin:
|
|
456
|
+
stmt = stmt.filter(VacationLedger.entry_date <= fecha_fin)
|
|
457
|
+
|
|
458
|
+
results = db.session.execute(stmt.order_by(VacationLedger.entry_date.desc())).all()
|
|
459
|
+
|
|
460
|
+
return [
|
|
461
|
+
{
|
|
462
|
+
"Fecha": entry.entry_date.isoformat() if entry.entry_date else "",
|
|
463
|
+
"Código Empleado": empleado.codigo_empleado,
|
|
464
|
+
"Nombre": f"{empleado.primer_nombre} {empleado.primer_apellido}",
|
|
465
|
+
"Días Usados": float(abs(entry.days_change)) if entry.days_change else 0.0,
|
|
466
|
+
"Descripción": entry.description or "",
|
|
467
|
+
}
|
|
468
|
+
for entry, empleado in results
|
|
469
|
+
]
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
# ============================================================================
|
|
473
|
+
# Report Metadata
|
|
474
|
+
# ============================================================================
|
|
475
|
+
|
|
476
|
+
# Metadata for each system report
|
|
477
|
+
SYSTEM_REPORT_METADATA = {
|
|
478
|
+
"employee_list": {
|
|
479
|
+
"name": "Listado General de Empleados",
|
|
480
|
+
"description": "Lista completa de empleados con información básica",
|
|
481
|
+
"category": "employee",
|
|
482
|
+
"base_entity": "Employee",
|
|
483
|
+
"parameters": [
|
|
484
|
+
{"name": "activo", "type": "boolean", "required": False},
|
|
485
|
+
{"name": "empresa_id", "type": "string", "required": False},
|
|
486
|
+
],
|
|
487
|
+
},
|
|
488
|
+
"employee_active_inactive": {
|
|
489
|
+
"name": "Empleados Activos e Inactivos",
|
|
490
|
+
"description": "Reporte de estado de empleados activos e inactivos",
|
|
491
|
+
"category": "employee",
|
|
492
|
+
"base_entity": "Employee",
|
|
493
|
+
"parameters": [],
|
|
494
|
+
},
|
|
495
|
+
"employee_by_department": {
|
|
496
|
+
"name": "Empleados por Departamento",
|
|
497
|
+
"description": "Listado de empleados agrupados por área o departamento",
|
|
498
|
+
"category": "employee",
|
|
499
|
+
"base_entity": "Employee",
|
|
500
|
+
"parameters": [],
|
|
501
|
+
},
|
|
502
|
+
"employee_hires_terminations": {
|
|
503
|
+
"name": "Altas y Bajas de Empleados",
|
|
504
|
+
"description": "Reporte de contrataciones y terminaciones por período",
|
|
505
|
+
"category": "employee",
|
|
506
|
+
"base_entity": "Employee",
|
|
507
|
+
"parameters": [
|
|
508
|
+
{"name": "fecha_inicio", "type": "date", "required": True},
|
|
509
|
+
{"name": "fecha_fin", "type": "date", "required": True},
|
|
510
|
+
],
|
|
511
|
+
},
|
|
512
|
+
"payroll_by_period": {
|
|
513
|
+
"name": "Nómina por Período",
|
|
514
|
+
"description": "Resumen de nóminas por período de pago",
|
|
515
|
+
"category": "payroll",
|
|
516
|
+
"base_entity": "Nomina",
|
|
517
|
+
"parameters": [
|
|
518
|
+
{"name": "periodo_inicio", "type": "date", "required": False},
|
|
519
|
+
{"name": "periodo_fin", "type": "date", "required": False},
|
|
520
|
+
{"name": "planilla_id", "type": "string", "required": False},
|
|
521
|
+
],
|
|
522
|
+
},
|
|
523
|
+
"payroll_employee_detail": {
|
|
524
|
+
"name": "Detalle de Nómina por Empleado",
|
|
525
|
+
"description": "Detalle completo de nómina desglosado por empleado",
|
|
526
|
+
"category": "payroll",
|
|
527
|
+
"base_entity": "NominaEmpleado",
|
|
528
|
+
"parameters": [{"name": "nomina_id", "type": "string", "required": True}],
|
|
529
|
+
},
|
|
530
|
+
"payroll_perceptions_summary": {
|
|
531
|
+
"name": "Resumen de Percepciones",
|
|
532
|
+
"description": "Resumen agregado de percepciones por concepto",
|
|
533
|
+
"category": "payroll",
|
|
534
|
+
"base_entity": "NominaDetalle",
|
|
535
|
+
"parameters": [{"name": "nomina_id", "type": "string", "required": True}],
|
|
536
|
+
},
|
|
537
|
+
"payroll_deductions_summary": {
|
|
538
|
+
"name": "Resumen de Deducciones",
|
|
539
|
+
"description": "Resumen agregado de deducciones por concepto",
|
|
540
|
+
"category": "payroll",
|
|
541
|
+
"base_entity": "NominaDetalle",
|
|
542
|
+
"parameters": [{"name": "nomina_id", "type": "string", "required": True}],
|
|
543
|
+
},
|
|
544
|
+
"vacation_balance_by_employee": {
|
|
545
|
+
"name": "Balance de Vacaciones por Empleado",
|
|
546
|
+
"description": "Saldo actual de vacaciones para cada empleado",
|
|
547
|
+
"category": "vacation",
|
|
548
|
+
"base_entity": "VacationAccount",
|
|
549
|
+
"parameters": [],
|
|
550
|
+
},
|
|
551
|
+
"vacation_taken_by_period": {
|
|
552
|
+
"name": "Vacaciones Tomadas por Período",
|
|
553
|
+
"description": "Registro de vacaciones utilizadas en un período",
|
|
554
|
+
"category": "vacation",
|
|
555
|
+
"base_entity": "VacationLedger",
|
|
556
|
+
"parameters": [
|
|
557
|
+
{"name": "fecha_inicio", "type": "date", "required": True},
|
|
558
|
+
{"name": "fecha_fin", "type": "date", "required": True},
|
|
559
|
+
],
|
|
560
|
+
},
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
def get_system_report_metadata(report_id: str) -> Optional[Dict[str, Any]]:
|
|
565
|
+
"""Get metadata for a system report.
|
|
566
|
+
|
|
567
|
+
Args:
|
|
568
|
+
report_id: System report identifier
|
|
569
|
+
|
|
570
|
+
Returns:
|
|
571
|
+
Report metadata or None
|
|
572
|
+
"""
|
|
573
|
+
return SYSTEM_REPORT_METADATA.get(report_id)
|