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
coati_payroll/model.py
ADDED
|
@@ -0,0 +1,2410 @@
|
|
|
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
|
+
"""Data model for the payroll module."""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
# <-------------------------------------------------------------------------> #
|
|
19
|
+
# Standard library
|
|
20
|
+
# <-------------------------------------------------------------------------> #
|
|
21
|
+
from decimal import Decimal
|
|
22
|
+
from datetime import date, datetime, timezone
|
|
23
|
+
|
|
24
|
+
# <-------------------------------------------------------------------------> #
|
|
25
|
+
# Third party libraries
|
|
26
|
+
# <-------------------------------------------------------------------------> #
|
|
27
|
+
import orjson
|
|
28
|
+
from flask_login import UserMixin
|
|
29
|
+
from flask_sqlalchemy import SQLAlchemy
|
|
30
|
+
from sqlalchemy import TypeDecorator, JSON
|
|
31
|
+
from sqlalchemy.ext.mutable import MutableDict
|
|
32
|
+
from ulid import ULID
|
|
33
|
+
|
|
34
|
+
# <-------------------------------------------------------------------------> #
|
|
35
|
+
# Local modules
|
|
36
|
+
# <-------------------------------------------------------------------------> #
|
|
37
|
+
|
|
38
|
+
db = SQLAlchemy()
|
|
39
|
+
database = db
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# Funciones de ayuda
|
|
43
|
+
def generador_de_codigos_unicos() -> str:
|
|
44
|
+
"""Genera codigo unicos basados en ULID."""
|
|
45
|
+
|
|
46
|
+
codigo_aleatorio = ULID()
|
|
47
|
+
id_unico = str(codigo_aleatorio)
|
|
48
|
+
|
|
49
|
+
return id_unico
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def generador_codigo_empleado() -> str:
|
|
53
|
+
"""Genera código único de empleado.
|
|
54
|
+
|
|
55
|
+
Formato: EMP-XXXXXX donde X es alfanumérico.
|
|
56
|
+
Usa los últimos 6 caracteres del ULID para unicidad.
|
|
57
|
+
"""
|
|
58
|
+
codigo_aleatorio = ULID()
|
|
59
|
+
sufijo = str(codigo_aleatorio)[-6:].upper()
|
|
60
|
+
return f"EMP-{sufijo}"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def utc_now() -> datetime:
|
|
64
|
+
"""Generate timezone-aware UTC datetime.
|
|
65
|
+
|
|
66
|
+
Replacement for deprecated datetime.utcnow() with timezone-aware alternative.
|
|
67
|
+
"""
|
|
68
|
+
return datetime.now(timezone.utc)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# Utiliza orjon para serializar/deserializar JSON
|
|
72
|
+
class OrjsonType(TypeDecorator):
|
|
73
|
+
impl = JSON
|
|
74
|
+
cache_ok = True
|
|
75
|
+
|
|
76
|
+
def process_bind_param(self, value, dialect):
|
|
77
|
+
if value is not None:
|
|
78
|
+
return orjson.dumps(value).decode("utf-8")
|
|
79
|
+
return value
|
|
80
|
+
|
|
81
|
+
def process_result_value(self, value, dialect):
|
|
82
|
+
if value is not None:
|
|
83
|
+
if isinstance(value, (dict, list)):
|
|
84
|
+
return value # PostgreSQL ya lo deserializó
|
|
85
|
+
return orjson.loads(value)
|
|
86
|
+
return value
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# Clase base para todas las tablas
|
|
90
|
+
class BaseTabla:
|
|
91
|
+
"""Columnas estandar para todas las tablas de la base de datos."""
|
|
92
|
+
|
|
93
|
+
# Pistas de auditoria comunes a todas las tablas.
|
|
94
|
+
id = database.Column(
|
|
95
|
+
database.String(26),
|
|
96
|
+
primary_key=True,
|
|
97
|
+
nullable=False,
|
|
98
|
+
index=True,
|
|
99
|
+
default=generador_de_codigos_unicos,
|
|
100
|
+
)
|
|
101
|
+
timestamp = database.Column(database.DateTime, default=utc_now, nullable=False)
|
|
102
|
+
creado = database.Column(database.Date, default=date.today, nullable=False)
|
|
103
|
+
creado_por = database.Column(database.String(150), nullable=True)
|
|
104
|
+
modificado = database.Column(database.DateTime, onupdate=utc_now, nullable=True)
|
|
105
|
+
modificado_por = database.Column(database.String(150), nullable=True)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class PluginRegistry(database.Model, BaseTabla):
|
|
109
|
+
__tablename__ = "plugin_registry"
|
|
110
|
+
__table_args__ = (database.UniqueConstraint("distribution_name", name="uq_plugin_distribution_name"),)
|
|
111
|
+
|
|
112
|
+
distribution_name = database.Column(database.String(200), nullable=False, unique=True, index=True)
|
|
113
|
+
plugin_id = database.Column(database.String(200), nullable=False, index=True)
|
|
114
|
+
version = database.Column(database.String(50), nullable=True)
|
|
115
|
+
active = database.Column(database.Boolean(), default=False, nullable=False)
|
|
116
|
+
installed = database.Column(database.Boolean(), default=True, nullable=False)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# Gestión de usuarios con acceso a la aplicación
|
|
120
|
+
class Usuario(database.Model, BaseTabla, UserMixin):
|
|
121
|
+
__tablename__ = "usuario"
|
|
122
|
+
__table_args__ = (
|
|
123
|
+
database.UniqueConstraint("usuario", name="id_usuario_unico"),
|
|
124
|
+
database.UniqueConstraint("correo_electronico", name="correo_usuario_unico"),
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
usuario = database.Column(database.String(150), nullable=False, index=True, unique=True)
|
|
128
|
+
acceso = database.Column(database.LargeBinary(), nullable=False)
|
|
129
|
+
nombre = database.Column(database.String(100))
|
|
130
|
+
apellido = database.Column(database.String(100))
|
|
131
|
+
correo_electronico = database.Column(database.String(150))
|
|
132
|
+
tipo = database.Column(database.String(20))
|
|
133
|
+
activo = database.Column(database.Boolean(), default=True)
|
|
134
|
+
ultimo_acceso = database.Column(database.DateTime, nullable=True)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
# Gestión de empresas/entidades
|
|
138
|
+
class Empresa(database.Model, BaseTabla):
|
|
139
|
+
"""Company/Entity model for multi-company support.
|
|
140
|
+
|
|
141
|
+
Allows the payroll system to handle multiple companies/entities.
|
|
142
|
+
Employees and Payrolls are associated with a company.
|
|
143
|
+
Deductions, Benefits, and Perceptions remain independent and can be used
|
|
144
|
+
across multiple companies.
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
__tablename__ = "empresa"
|
|
148
|
+
__table_args__ = (
|
|
149
|
+
database.UniqueConstraint("codigo", name="uq_empresa_codigo"),
|
|
150
|
+
database.UniqueConstraint("ruc", name="uq_empresa_ruc"),
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
# Unique company code
|
|
154
|
+
codigo = database.Column(database.String(50), unique=True, nullable=False, index=True)
|
|
155
|
+
|
|
156
|
+
# Company legal name
|
|
157
|
+
razon_social = database.Column(database.String(200), nullable=False)
|
|
158
|
+
|
|
159
|
+
# Commercial/trade name
|
|
160
|
+
nombre_comercial = database.Column(database.String(200), nullable=True)
|
|
161
|
+
|
|
162
|
+
# Tax identification number (jurisdiction-specific format)
|
|
163
|
+
ruc = database.Column(database.String(50), unique=True, nullable=False)
|
|
164
|
+
|
|
165
|
+
# Contact information
|
|
166
|
+
direccion = database.Column(database.String(255), nullable=True)
|
|
167
|
+
telefono = database.Column(database.String(50), nullable=True)
|
|
168
|
+
correo = database.Column(database.String(150), nullable=True)
|
|
169
|
+
sitio_web = database.Column(database.String(200), nullable=True)
|
|
170
|
+
|
|
171
|
+
# Legal representative
|
|
172
|
+
representante_legal = database.Column(database.String(150), nullable=True)
|
|
173
|
+
|
|
174
|
+
# Status
|
|
175
|
+
activo = database.Column(database.Boolean(), default=True, nullable=False)
|
|
176
|
+
|
|
177
|
+
# Relationships
|
|
178
|
+
empleados = database.relationship("Empleado", back_populates="empresa")
|
|
179
|
+
planillas = database.relationship("Planilla", back_populates="empresa")
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
# Gestión de monedas y tipos de cambio
|
|
183
|
+
class Moneda(database.Model, BaseTabla):
|
|
184
|
+
__tablename__ = "moneda"
|
|
185
|
+
|
|
186
|
+
codigo = database.Column(database.String(10), unique=True, nullable=False, index=True)
|
|
187
|
+
nombre = database.Column(database.String(100), nullable=False)
|
|
188
|
+
simbolo = database.Column(database.String(10), nullable=True)
|
|
189
|
+
activo = database.Column(database.Boolean(), default=True)
|
|
190
|
+
|
|
191
|
+
# relaciones
|
|
192
|
+
planillas = database.relationship("Planilla", back_populates="moneda")
|
|
193
|
+
empleados = database.relationship("Empleado", back_populates="moneda")
|
|
194
|
+
tipo_cambio_origen = database.relationship(
|
|
195
|
+
"TipoCambio",
|
|
196
|
+
back_populates="moneda_origen",
|
|
197
|
+
foreign_keys="TipoCambio.moneda_origen_id",
|
|
198
|
+
)
|
|
199
|
+
tipo_cambio_destino = database.relationship(
|
|
200
|
+
"TipoCambio",
|
|
201
|
+
back_populates="moneda_destino",
|
|
202
|
+
foreign_keys="TipoCambio.moneda_destino_id",
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
class TipoCambio(database.Model, BaseTabla):
|
|
207
|
+
__tablename__ = "tipo_cambio"
|
|
208
|
+
__table_args__ = (
|
|
209
|
+
database.UniqueConstraint(
|
|
210
|
+
"moneda_origen_id",
|
|
211
|
+
"moneda_destino_id",
|
|
212
|
+
"fecha",
|
|
213
|
+
name="uq_tc_origen_destino_fecha",
|
|
214
|
+
),
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
fecha = database.Column(database.Date, nullable=False, default=date.today, index=True)
|
|
218
|
+
moneda_origen_id = database.Column(database.String(26), database.ForeignKey("moneda.id"), nullable=False)
|
|
219
|
+
moneda_destino_id = database.Column(database.String(26), database.ForeignKey("moneda.id"), nullable=False)
|
|
220
|
+
tasa = database.Column(database.Numeric(24, 10), nullable=False)
|
|
221
|
+
|
|
222
|
+
moneda_origen = database.relationship(
|
|
223
|
+
"Moneda", back_populates="tipo_cambio_origen", foreign_keys=[moneda_origen_id]
|
|
224
|
+
)
|
|
225
|
+
moneda_destino = database.relationship(
|
|
226
|
+
"Moneda", back_populates="tipo_cambio_destino", foreign_keys=[moneda_destino_id]
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
# Registro maestro de empleados
|
|
231
|
+
class Empleado(database.Model, BaseTabla):
|
|
232
|
+
__tablename__ = "empleado"
|
|
233
|
+
__table_args__ = (
|
|
234
|
+
database.UniqueConstraint("identificacion_personal", name="uq_empleado_identificacion"),
|
|
235
|
+
database.UniqueConstraint("codigo_empleado", name="uq_empleado_codigo"),
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
# Código único de empleado (auto-generado si no se proporciona)
|
|
239
|
+
codigo_empleado = database.Column(
|
|
240
|
+
database.String(20),
|
|
241
|
+
unique=True,
|
|
242
|
+
nullable=False,
|
|
243
|
+
index=True,
|
|
244
|
+
default=generador_codigo_empleado,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
primer_nombre = database.Column(database.String(100), nullable=False)
|
|
248
|
+
segundo_nombre = database.Column(database.String(100), nullable=True)
|
|
249
|
+
primer_apellido = database.Column(database.String(100), nullable=False)
|
|
250
|
+
segundo_apellido = database.Column(database.String(100), nullable=True)
|
|
251
|
+
|
|
252
|
+
genero = database.Column(database.String(20), nullable=True)
|
|
253
|
+
nacionalidad = database.Column(database.String(100), nullable=True)
|
|
254
|
+
tipo_identificacion = database.Column(database.String(50), nullable=True)
|
|
255
|
+
identificacion_personal = database.Column(database.String(50), unique=True, nullable=False)
|
|
256
|
+
id_seguridad_social = database.Column(database.String(50), nullable=True)
|
|
257
|
+
id_fiscal = database.Column(database.String(50), nullable=True)
|
|
258
|
+
tipo_sangre = database.Column(database.String(10), nullable=True)
|
|
259
|
+
fecha_nacimiento = database.Column(database.Date, nullable=True)
|
|
260
|
+
|
|
261
|
+
fecha_alta = database.Column(database.Date, nullable=False, default=date.today)
|
|
262
|
+
fecha_baja = database.Column(database.Date, nullable=True)
|
|
263
|
+
activo = database.Column(database.Boolean(), default=True, nullable=False)
|
|
264
|
+
|
|
265
|
+
cargo = database.Column(database.String(150), nullable=True)
|
|
266
|
+
area = database.Column(database.String(150), nullable=True)
|
|
267
|
+
centro_costos = database.Column(database.String(150), nullable=True)
|
|
268
|
+
|
|
269
|
+
salario_base = database.Column(database.Numeric(14, 2), nullable=False, default=Decimal("0.00"))
|
|
270
|
+
|
|
271
|
+
# Moneda del sueldo: FK hacia moneda.id (consistencia)
|
|
272
|
+
moneda_id = database.Column(database.String(26), database.ForeignKey("moneda.id"), nullable=True)
|
|
273
|
+
moneda = database.relationship("Moneda", back_populates="empleados")
|
|
274
|
+
|
|
275
|
+
# Empresa a la que pertenece el empleado
|
|
276
|
+
empresa_id = database.Column(database.String(26), database.ForeignKey("empresa.id"), nullable=True)
|
|
277
|
+
empresa = database.relationship("Empresa", back_populates="empleados")
|
|
278
|
+
|
|
279
|
+
correo = database.Column(database.String(150), nullable=True, index=True)
|
|
280
|
+
telefono = database.Column(database.String(50), nullable=True)
|
|
281
|
+
direccion = database.Column(database.String(255), nullable=True)
|
|
282
|
+
estado_civil = database.Column(database.String(50), nullable=True)
|
|
283
|
+
banco = database.Column(database.String(100), nullable=True)
|
|
284
|
+
numero_cuenta_bancaria = database.Column(database.String(100), nullable=True)
|
|
285
|
+
|
|
286
|
+
tipo_contrato = database.Column(database.String(50), nullable=True)
|
|
287
|
+
fecha_ultimo_aumento = database.Column(database.Date, nullable=True)
|
|
288
|
+
|
|
289
|
+
# Datos iniciales de implementación
|
|
290
|
+
# Estos campos almacenan saldos acumulados cuando el sistema se implementa
|
|
291
|
+
# a mitad de un período fiscal
|
|
292
|
+
anio_implementacion_inicial = database.Column(database.Integer, nullable=True)
|
|
293
|
+
mes_ultimo_cierre = database.Column(database.Integer, nullable=True)
|
|
294
|
+
salario_acumulado = database.Column(database.Numeric(14, 2), nullable=True, default=Decimal("0.00"))
|
|
295
|
+
impuesto_acumulado = database.Column(database.Numeric(14, 2), nullable=True, default=Decimal("0.00"))
|
|
296
|
+
ultimos_tres_salarios = database.Column(MutableDict.as_mutable(OrjsonType), nullable=True, default=dict)
|
|
297
|
+
|
|
298
|
+
# relaciones
|
|
299
|
+
planilla_asociaciones = database.relationship(
|
|
300
|
+
"PlanillaEmpleado",
|
|
301
|
+
back_populates="empleado",
|
|
302
|
+
)
|
|
303
|
+
nominas = database.relationship(
|
|
304
|
+
"NominaEmpleado",
|
|
305
|
+
back_populates="empleado",
|
|
306
|
+
)
|
|
307
|
+
novedades_registradas = database.relationship(
|
|
308
|
+
"NominaNovedad", back_populates="empleado", cascade="all,delete-orphan"
|
|
309
|
+
)
|
|
310
|
+
historial_salarios = database.relationship(
|
|
311
|
+
"HistorialSalario", back_populates="empleado", cascade="all,delete-orphan"
|
|
312
|
+
)
|
|
313
|
+
vacaciones = database.relationship("VacacionEmpleado", back_populates="empleado", cascade="all,delete-orphan")
|
|
314
|
+
vacaciones_descansadas = database.relationship(
|
|
315
|
+
"VacacionDescansada", back_populates="empleado", cascade="all,delete-orphan"
|
|
316
|
+
)
|
|
317
|
+
adelantos = database.relationship("Adelanto", back_populates="empleado", cascade="all,delete-orphan")
|
|
318
|
+
|
|
319
|
+
# Datos adicionales (JSON)
|
|
320
|
+
datos_adicionales = database.Column(MutableDict.as_mutable(OrjsonType), nullable=True, default=dict)
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
# Gestión de planillas
|
|
324
|
+
class TipoPlanilla(database.Model, BaseTabla):
|
|
325
|
+
"""Payroll type configuration.
|
|
326
|
+
|
|
327
|
+
Defines the type of payroll (monthly, biweekly, weekly, etc.) and its
|
|
328
|
+
fiscal period parameters. The fiscal period can be different from the
|
|
329
|
+
calendar year (Jan-Dec) and is defined here to support various accounting
|
|
330
|
+
requirements.
|
|
331
|
+
"""
|
|
332
|
+
|
|
333
|
+
__tablename__ = "tipo_planilla"
|
|
334
|
+
|
|
335
|
+
codigo = database.Column(database.String(20), unique=True, nullable=False)
|
|
336
|
+
descripcion = database.Column(database.String(150), nullable=True)
|
|
337
|
+
dias = database.Column(database.Integer, nullable=False, default=30) # días usados para prorrateos
|
|
338
|
+
periodicidad = database.Column(
|
|
339
|
+
database.String(20), nullable=False, default="mensual"
|
|
340
|
+
) # ej. mensual, quincenal, semanal
|
|
341
|
+
|
|
342
|
+
# Fiscal period configuration
|
|
343
|
+
# mes_inicio_fiscal: Month when the fiscal year starts (1-12)
|
|
344
|
+
mes_inicio_fiscal = database.Column(database.Integer, nullable=False, default=1) # 1 = January
|
|
345
|
+
# dia_inicio_fiscal: Day of month when fiscal year starts
|
|
346
|
+
dia_inicio_fiscal = database.Column(database.Integer, nullable=False, default=1)
|
|
347
|
+
|
|
348
|
+
# Accumulated calculation settings
|
|
349
|
+
# acumula_anual: Whether this payroll type accumulates values annually
|
|
350
|
+
acumula_anual = database.Column(database.Boolean(), default=True, nullable=False)
|
|
351
|
+
# periodos_por_anio: Number of payroll periods per fiscal year
|
|
352
|
+
periodos_por_anio = database.Column(database.Integer, nullable=False, default=12)
|
|
353
|
+
|
|
354
|
+
# Tax calculation parameters (stored as JSON for flexibility)
|
|
355
|
+
parametros_calculo = database.Column(MutableDict.as_mutable(OrjsonType), nullable=True, default=dict)
|
|
356
|
+
|
|
357
|
+
activo = database.Column(database.Boolean(), default=True, nullable=False)
|
|
358
|
+
|
|
359
|
+
planillas = database.relationship("Planilla", back_populates="tipo_planilla")
|
|
360
|
+
acumulados = database.relationship("AcumuladoAnual", back_populates="tipo_planilla")
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
class Planilla(database.Model, BaseTabla):
|
|
364
|
+
"""Master payroll record that connects employees, perceptions, deductions, and benefits.
|
|
365
|
+
|
|
366
|
+
The Planilla acts as the central hub for payroll configuration:
|
|
367
|
+
- Employees assigned via PlanillaEmpleado
|
|
368
|
+
- Perceptions via PlanillaIngreso
|
|
369
|
+
- Deductions via PlanillaDeduccion (with priority order)
|
|
370
|
+
- Benefits via PlanillaPrestacion
|
|
371
|
+
- Calculation rules via PlanillaReglaCalculo
|
|
372
|
+
|
|
373
|
+
Automatic Deductions:
|
|
374
|
+
The payroll engine automatically applies loan installments and salary advances
|
|
375
|
+
from the Adelanto table for employees with active loans, regardless of whether
|
|
376
|
+
those deductions are explicitly configured in planilla_deducciones.
|
|
377
|
+
The priority for these automatic deductions is controlled by:
|
|
378
|
+
- prioridad_prestamos: Priority for loan installments
|
|
379
|
+
- prioridad_adelantos: Priority for salary advances
|
|
380
|
+
"""
|
|
381
|
+
|
|
382
|
+
__tablename__ = "planilla"
|
|
383
|
+
|
|
384
|
+
nombre = database.Column(database.String(150), nullable=False, unique=True)
|
|
385
|
+
descripcion = database.Column(database.String(255), nullable=True)
|
|
386
|
+
activo = database.Column(database.Boolean(), default=True, nullable=False)
|
|
387
|
+
|
|
388
|
+
parametros = database.Column(MutableDict.as_mutable(OrjsonType), nullable=True, default=dict)
|
|
389
|
+
|
|
390
|
+
tipo_planilla_id = database.Column(database.String(26), database.ForeignKey("tipo_planilla.id"), nullable=False)
|
|
391
|
+
tipo_planilla = database.relationship("TipoPlanilla", back_populates="planillas")
|
|
392
|
+
|
|
393
|
+
moneda_id = database.Column(database.String(26), database.ForeignKey("moneda.id"), nullable=False)
|
|
394
|
+
moneda = database.relationship("Moneda", back_populates="planillas")
|
|
395
|
+
|
|
396
|
+
# Empresa a la que pertenece la planilla
|
|
397
|
+
empresa_id = database.Column(database.String(26), database.ForeignKey("empresa.id"), nullable=True)
|
|
398
|
+
empresa = database.relationship("Empresa", back_populates="planillas")
|
|
399
|
+
|
|
400
|
+
# Período Fiscal
|
|
401
|
+
periodo_fiscal_inicio = database.Column(database.Date, nullable=True)
|
|
402
|
+
periodo_fiscal_fin = database.Column(database.Date, nullable=True)
|
|
403
|
+
|
|
404
|
+
# Ultima ejecución
|
|
405
|
+
ultima_ejecucion = database.Column(database.DateTime, nullable=True)
|
|
406
|
+
|
|
407
|
+
# Automatic deduction priorities (applied even if not in planilla_deducciones)
|
|
408
|
+
# Loans and advances from Adelanto table are automatically deducted
|
|
409
|
+
# Lower number = higher priority (applied first)
|
|
410
|
+
prioridad_prestamos = database.Column(
|
|
411
|
+
database.Integer, nullable=False, default=250
|
|
412
|
+
) # Default: after mandatory deductions
|
|
413
|
+
prioridad_adelantos = database.Column(database.Integer, nullable=False, default=251) # Default: right after loans
|
|
414
|
+
|
|
415
|
+
# Whether to apply automatic loan/advance deductions
|
|
416
|
+
aplicar_prestamos_automatico = database.Column(database.Boolean(), default=True, nullable=False)
|
|
417
|
+
aplicar_adelantos_automatico = database.Column(database.Boolean(), default=True, nullable=False)
|
|
418
|
+
|
|
419
|
+
# Accounting control for base salary
|
|
420
|
+
# Base salary is the foundation of payroll calculation and needs its own
|
|
421
|
+
# accounting accounts to generate proper accounting vouchers
|
|
422
|
+
codigo_cuenta_debe_salario = database.Column(database.String(64), nullable=True)
|
|
423
|
+
descripcion_cuenta_debe_salario = database.Column(database.String(255), nullable=True)
|
|
424
|
+
codigo_cuenta_haber_salario = database.Column(database.String(64), nullable=True)
|
|
425
|
+
descripcion_cuenta_haber_salario = database.Column(database.String(255), nullable=True)
|
|
426
|
+
|
|
427
|
+
# relaciones con componentes configurados
|
|
428
|
+
planilla_percepciones = database.relationship(
|
|
429
|
+
"PlanillaIngreso",
|
|
430
|
+
back_populates="planilla",
|
|
431
|
+
)
|
|
432
|
+
planilla_deducciones = database.relationship(
|
|
433
|
+
"PlanillaDeduccion",
|
|
434
|
+
back_populates="planilla",
|
|
435
|
+
)
|
|
436
|
+
planilla_prestaciones = database.relationship(
|
|
437
|
+
"PlanillaPrestacion",
|
|
438
|
+
back_populates="planilla",
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
# reglas de cálculo asociadas (impuestos, fórmulas complejas)
|
|
442
|
+
planilla_reglas_calculo = database.relationship(
|
|
443
|
+
"PlanillaReglaCalculo",
|
|
444
|
+
back_populates="planilla",
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
# empleados asignados a la planilla (config)
|
|
448
|
+
planilla_empleados = database.relationship(
|
|
449
|
+
"PlanillaEmpleado",
|
|
450
|
+
back_populates="planilla",
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
# Audit and governance fields
|
|
454
|
+
estado_aprobacion = database.Column(database.String(20), nullable=False, default="borrador", index=True)
|
|
455
|
+
aprobado_por = database.Column(database.String(150), nullable=True)
|
|
456
|
+
aprobado_en = database.Column(database.DateTime, nullable=True)
|
|
457
|
+
creado_por_plugin = database.Column(database.Boolean(), default=False, nullable=False)
|
|
458
|
+
plugin_source = database.Column(database.String(200), nullable=True)
|
|
459
|
+
|
|
460
|
+
# ejecuciones históricas (nominas)
|
|
461
|
+
nominas = database.relationship(
|
|
462
|
+
"Nomina",
|
|
463
|
+
back_populates="planilla",
|
|
464
|
+
)
|
|
465
|
+
audit_logs = database.relationship(
|
|
466
|
+
"PlanillaAuditLog",
|
|
467
|
+
back_populates="planilla",
|
|
468
|
+
cascade="all, delete-orphan",
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
# Percepciones y deducciones - THESE AFFECT EMPLOYEE'S NET PAY
|
|
473
|
+
# Percepciones (ingresos) se SUMAN al salario
|
|
474
|
+
# Deducciones se RESTAN del salario
|
|
475
|
+
class Percepcion(database.Model, BaseTabla):
|
|
476
|
+
"""Income items that ADD to employee's pay.
|
|
477
|
+
|
|
478
|
+
Percepciones are income items that increase the employee's gross salary.
|
|
479
|
+
Examples: base salary, overtime, bonuses, commissions, allowances.
|
|
480
|
+
|
|
481
|
+
Together with Deducciones, these determine the employee's net pay.
|
|
482
|
+
(Prestaciones do NOT affect employee pay - they are employer costs.)
|
|
483
|
+
"""
|
|
484
|
+
|
|
485
|
+
__tablename__ = "percepcion"
|
|
486
|
+
|
|
487
|
+
codigo = database.Column(database.String(50), unique=True, nullable=False, index=True)
|
|
488
|
+
nombre = database.Column(database.String(150), nullable=False)
|
|
489
|
+
descripcion = database.Column(database.String(255), nullable=True)
|
|
490
|
+
unidad_calculo = database.Column(database.String(20), nullable=True) # ej. 'hora', 'dia', 'mes', etc.
|
|
491
|
+
|
|
492
|
+
# tipo de cálculo: 'fijo', 'porcentaje_salario', 'porcentaje_bruto', 'formula', 'horas', etc.
|
|
493
|
+
formula_tipo = database.Column(database.String(50), nullable=False, default="fijo")
|
|
494
|
+
monto_default = database.Column(database.Numeric(14, 2), nullable=True, default=Decimal("0.00"))
|
|
495
|
+
formula = database.Column(MutableDict.as_mutable(OrjsonType), nullable=True, default=dict)
|
|
496
|
+
condicion = database.Column(MutableDict.as_mutable(OrjsonType), nullable=True, default=dict)
|
|
497
|
+
porcentaje = database.Column(database.Numeric(5, 2), nullable=True)
|
|
498
|
+
gravable = database.Column(database.Boolean(), default=True)
|
|
499
|
+
recurrente = database.Column(database.Boolean(), default=False)
|
|
500
|
+
activo = database.Column(database.Boolean(), default=True)
|
|
501
|
+
|
|
502
|
+
# Vigencia: hasta cuándo es válida esta percepción (opcional)
|
|
503
|
+
vigente_desde = database.Column(database.Date, nullable=True) # opcional, si quieres rango
|
|
504
|
+
valido_hasta = database.Column(database.Date, nullable=True)
|
|
505
|
+
|
|
506
|
+
# Especificidad de cálculo
|
|
507
|
+
base_calculo = database.Column( # ej: 'salario_base', 'gravable', 'bruto', 'neto'
|
|
508
|
+
database.String(50), nullable=True
|
|
509
|
+
)
|
|
510
|
+
unidad_calculo = database.Column(database.String(20), nullable=True) # ej: 'horas', 'dias', None
|
|
511
|
+
|
|
512
|
+
# Control contable
|
|
513
|
+
contabilizable = database.Column(database.Boolean(), default=True, nullable=False)
|
|
514
|
+
codigo_cuenta_debe = database.Column(database.String(64), nullable=True)
|
|
515
|
+
descripcion_cuenta_debe = database.Column(database.String(255), nullable=True)
|
|
516
|
+
codigo_cuenta_haber = database.Column(database.String(64), nullable=True)
|
|
517
|
+
descripcion_cuenta_haber = database.Column(database.String(255), nullable=True)
|
|
518
|
+
|
|
519
|
+
# Control edición en nómina
|
|
520
|
+
editable_en_nomina = database.Column(database.Boolean(), default=False, nullable=False)
|
|
521
|
+
|
|
522
|
+
# Audit and governance fields
|
|
523
|
+
estado_aprobacion = database.Column(database.String(20), nullable=False, default="borrador", index=True)
|
|
524
|
+
aprobado_por = database.Column(database.String(150), nullable=True)
|
|
525
|
+
aprobado_en = database.Column(database.DateTime, nullable=True)
|
|
526
|
+
creado_por_plugin = database.Column(database.Boolean(), default=False, nullable=False)
|
|
527
|
+
plugin_source = database.Column(database.String(200), nullable=True)
|
|
528
|
+
|
|
529
|
+
planillas = database.relationship(
|
|
530
|
+
"PlanillaIngreso",
|
|
531
|
+
back_populates="percepcion",
|
|
532
|
+
)
|
|
533
|
+
nomina_detalles = database.relationship("NominaDetalle", back_populates="percepcion")
|
|
534
|
+
audit_logs = database.relationship(
|
|
535
|
+
"ConceptoAuditLog",
|
|
536
|
+
back_populates="percepcion",
|
|
537
|
+
foreign_keys="ConceptoAuditLog.percepcion_id",
|
|
538
|
+
cascade="all, delete-orphan",
|
|
539
|
+
)
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
class Deduccion(database.Model, BaseTabla):
|
|
543
|
+
__tablename__ = "deduccion"
|
|
544
|
+
|
|
545
|
+
codigo = database.Column(database.String(50), unique=True, nullable=False, index=True)
|
|
546
|
+
nombre = database.Column(database.String(150), nullable=False)
|
|
547
|
+
descripcion = database.Column(database.String(255), nullable=True)
|
|
548
|
+
|
|
549
|
+
tipo = database.Column(database.String(30), nullable=False, default="general")
|
|
550
|
+
es_impuesto = database.Column(database.Boolean(), default=False)
|
|
551
|
+
|
|
552
|
+
formula_tipo = database.Column(database.String(50), nullable=False, default="fijo")
|
|
553
|
+
monto_default = database.Column(database.Numeric(14, 2), nullable=True, default=Decimal("0.00"))
|
|
554
|
+
formula = database.Column(MutableDict.as_mutable(OrjsonType), nullable=True, default=dict)
|
|
555
|
+
condicion = database.Column(MutableDict.as_mutable(OrjsonType), nullable=True, default=dict)
|
|
556
|
+
porcentaje = database.Column(database.Numeric(5, 2), nullable=True)
|
|
557
|
+
antes_impuesto = database.Column(database.Boolean(), default=True)
|
|
558
|
+
recurrente = database.Column(database.Boolean(), default=False)
|
|
559
|
+
activo = database.Column(database.Boolean(), default=True)
|
|
560
|
+
|
|
561
|
+
# Vigencia
|
|
562
|
+
vigente_desde = database.Column(database.Date, nullable=True)
|
|
563
|
+
valido_hasta = database.Column(database.Date, nullable=True)
|
|
564
|
+
|
|
565
|
+
# Base y unidad de cálculo
|
|
566
|
+
base_calculo = database.Column(database.String(50), nullable=True)
|
|
567
|
+
unidad_calculo = database.Column(database.String(20), nullable=True)
|
|
568
|
+
|
|
569
|
+
# Control contable
|
|
570
|
+
contabilizable = database.Column(database.Boolean(), default=True, nullable=False)
|
|
571
|
+
codigo_cuenta_debe = database.Column(database.String(64), nullable=True)
|
|
572
|
+
descripcion_cuenta_debe = database.Column(database.String(255), nullable=True)
|
|
573
|
+
codigo_cuenta_haber = database.Column(database.String(64), nullable=True)
|
|
574
|
+
descripcion_cuenta_haber = database.Column(database.String(255), nullable=True)
|
|
575
|
+
|
|
576
|
+
# Control edición en nómina
|
|
577
|
+
editable_en_nomina = database.Column(database.Boolean(), default=False, nullable=False)
|
|
578
|
+
|
|
579
|
+
# Audit and governance fields
|
|
580
|
+
estado_aprobacion = database.Column(database.String(20), nullable=False, default="borrador", index=True)
|
|
581
|
+
aprobado_por = database.Column(database.String(150), nullable=True)
|
|
582
|
+
aprobado_en = database.Column(database.DateTime, nullable=True)
|
|
583
|
+
creado_por_plugin = database.Column(database.Boolean(), default=False, nullable=False)
|
|
584
|
+
plugin_source = database.Column(database.String(200), nullable=True)
|
|
585
|
+
|
|
586
|
+
planillas = database.relationship(
|
|
587
|
+
"PlanillaDeduccion",
|
|
588
|
+
back_populates="deduccion",
|
|
589
|
+
)
|
|
590
|
+
nomina_detalles = database.relationship("NominaDetalle", back_populates="deduccion")
|
|
591
|
+
tablas_impuesto = database.relationship("TablaImpuesto", back_populates="deduccion")
|
|
592
|
+
adelantos = database.relationship("Adelanto", back_populates="deduccion")
|
|
593
|
+
audit_logs = database.relationship(
|
|
594
|
+
"ConceptoAuditLog",
|
|
595
|
+
back_populates="deduccion",
|
|
596
|
+
foreign_keys="ConceptoAuditLog.deduccion_id",
|
|
597
|
+
cascade="all, delete-orphan",
|
|
598
|
+
)
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
# Prestaciones (aportes del empleador: seguridad social, etc.)
|
|
602
|
+
# NOTA: Las prestaciones son costos patronales que NO afectan el pago al empleado.
|
|
603
|
+
# Solo las percepciones y deducciones afectan el salario neto del empleado.
|
|
604
|
+
# Ejemplos de prestaciones: INSS patronal, provisión de vacaciones, aguinaldo, indemnización.
|
|
605
|
+
class Prestacion(database.Model, BaseTabla):
|
|
606
|
+
"""Employer contributions and provisions.
|
|
607
|
+
|
|
608
|
+
Prestaciones are employer costs that do NOT affect the employee's net pay.
|
|
609
|
+
They represent the company's obligations such as:
|
|
610
|
+
- Social security employer contributions (INSS patronal)
|
|
611
|
+
- Vacation provisions
|
|
612
|
+
- 13th month (aguinaldo) provisions
|
|
613
|
+
- Severance provisions (indemnización)
|
|
614
|
+
|
|
615
|
+
Only Percepciones (income) and Deducciones affect the employee's net salary.
|
|
616
|
+
"""
|
|
617
|
+
|
|
618
|
+
__tablename__ = "prestacion"
|
|
619
|
+
|
|
620
|
+
codigo = database.Column(database.String(50), unique=True, nullable=False, index=True)
|
|
621
|
+
nombre = database.Column(database.String(150), nullable=False)
|
|
622
|
+
descripcion = database.Column(database.String(255), nullable=True)
|
|
623
|
+
|
|
624
|
+
tipo = database.Column(database.String(30), nullable=False, default="patronal")
|
|
625
|
+
|
|
626
|
+
formula_tipo = database.Column(database.String(50), nullable=False, default="fijo")
|
|
627
|
+
monto_default = database.Column(database.Numeric(14, 2), nullable=True, default=Decimal("0.00"))
|
|
628
|
+
formula = database.Column(MutableDict.as_mutable(OrjsonType), nullable=True, default=dict)
|
|
629
|
+
condicion = database.Column(MutableDict.as_mutable(OrjsonType), nullable=True, default=dict)
|
|
630
|
+
porcentaje = database.Column(database.Numeric(5, 2), nullable=True)
|
|
631
|
+
recurrente = database.Column(database.Boolean(), default=False)
|
|
632
|
+
activo = database.Column(database.Boolean(), default=True)
|
|
633
|
+
|
|
634
|
+
vigente_desde = database.Column(database.Date, nullable=True)
|
|
635
|
+
valido_hasta = database.Column(database.Date, nullable=True)
|
|
636
|
+
|
|
637
|
+
base_calculo = database.Column(database.String(50), nullable=True)
|
|
638
|
+
unidad_calculo = database.Column(database.String(20), nullable=True)
|
|
639
|
+
|
|
640
|
+
tope_aplicacion = database.Column(database.Numeric(14, 2), nullable=True)
|
|
641
|
+
|
|
642
|
+
contabilizable = database.Column(database.Boolean(), default=True, nullable=False)
|
|
643
|
+
codigo_cuenta_debe = database.Column(database.String(64), nullable=True)
|
|
644
|
+
descripcion_cuenta_debe = database.Column(database.String(255), nullable=True)
|
|
645
|
+
codigo_cuenta_haber = database.Column(database.String(64), nullable=True)
|
|
646
|
+
descripcion_cuenta_haber = database.Column(database.String(255), nullable=True)
|
|
647
|
+
|
|
648
|
+
editable_en_nomina = database.Column(database.Boolean(), default=False, nullable=False)
|
|
649
|
+
|
|
650
|
+
# Accumulation configuration
|
|
651
|
+
# Defines how this benefit accumulates: monthly settlement, annually, or lifetime
|
|
652
|
+
tipo_acumulacion = database.Column(
|
|
653
|
+
database.String(20), nullable=False, default="mensual"
|
|
654
|
+
) # mensual | anual | vida_laboral
|
|
655
|
+
|
|
656
|
+
# Audit and governance fields
|
|
657
|
+
estado_aprobacion = database.Column(database.String(20), nullable=False, default="borrador", index=True)
|
|
658
|
+
aprobado_por = database.Column(database.String(150), nullable=True)
|
|
659
|
+
aprobado_en = database.Column(database.DateTime, nullable=True)
|
|
660
|
+
creado_por_plugin = database.Column(database.Boolean(), default=False, nullable=False)
|
|
661
|
+
plugin_source = database.Column(database.String(200), nullable=True)
|
|
662
|
+
|
|
663
|
+
planillas = database.relationship(
|
|
664
|
+
"PlanillaPrestacion",
|
|
665
|
+
back_populates="prestacion",
|
|
666
|
+
)
|
|
667
|
+
nomina_detalles = database.relationship("NominaDetalle", back_populates="prestacion")
|
|
668
|
+
prestaciones_acumuladas = database.relationship(
|
|
669
|
+
"PrestacionAcumulada", back_populates="prestacion", cascade="all,delete-orphan"
|
|
670
|
+
)
|
|
671
|
+
cargas_iniciales = database.relationship(
|
|
672
|
+
"CargaInicialPrestacion", back_populates="prestacion", cascade="all,delete-orphan"
|
|
673
|
+
)
|
|
674
|
+
audit_logs = database.relationship(
|
|
675
|
+
"ConceptoAuditLog",
|
|
676
|
+
back_populates="prestacion",
|
|
677
|
+
foreign_keys="ConceptoAuditLog.prestacion_id",
|
|
678
|
+
cascade="all, delete-orphan",
|
|
679
|
+
)
|
|
680
|
+
|
|
681
|
+
|
|
682
|
+
# Definición de componentes de planilla
|
|
683
|
+
class PlanillaIngreso(database.Model, BaseTabla):
|
|
684
|
+
__tablename__ = "planilla_ingreso"
|
|
685
|
+
__table_args__ = (database.UniqueConstraint("planilla_id", "percepcion_id", name="uq_planilla_percepcion"),)
|
|
686
|
+
|
|
687
|
+
planilla_id = database.Column(database.String(26), database.ForeignKey("planilla.id"), nullable=False)
|
|
688
|
+
percepcion_id = database.Column(database.String(26), database.ForeignKey("percepcion.id"), nullable=False)
|
|
689
|
+
|
|
690
|
+
orden = database.Column(database.Integer, nullable=True, default=0)
|
|
691
|
+
editable = database.Column(database.Boolean(), default=True)
|
|
692
|
+
monto_predeterminado = database.Column(database.Numeric(14, 2), nullable=True)
|
|
693
|
+
porcentaje = database.Column(database.Numeric(5, 2), nullable=True)
|
|
694
|
+
activo = database.Column(database.Boolean(), default=True)
|
|
695
|
+
|
|
696
|
+
planilla = database.relationship("Planilla", back_populates="planilla_percepciones")
|
|
697
|
+
percepcion = database.relationship("Percepcion", back_populates="planillas")
|
|
698
|
+
|
|
699
|
+
|
|
700
|
+
class PlanillaDeduccion(database.Model, BaseTabla):
|
|
701
|
+
"""Association between Planilla and Deduccion with priority ordering.
|
|
702
|
+
|
|
703
|
+
The 'prioridad' field determines the order in which deductions are applied.
|
|
704
|
+
This is critical when the net salary doesn't cover all deductions.
|
|
705
|
+
|
|
706
|
+
Priority guidelines:
|
|
707
|
+
- 1-100: Legal/mandatory deductions (taxes, social security)
|
|
708
|
+
- 101-200: Court-ordered deductions (alimony, garnishments)
|
|
709
|
+
- 201-300: Company loans and salary advances
|
|
710
|
+
- 301-400: Voluntary deductions (savings, insurance)
|
|
711
|
+
- 401+: Other deductions
|
|
712
|
+
|
|
713
|
+
Note: Loan installments from the Adelanto table are handled automatically
|
|
714
|
+
by the payroll engine, not through JSON calculation rules.
|
|
715
|
+
"""
|
|
716
|
+
|
|
717
|
+
__tablename__ = "planilla_deduccion"
|
|
718
|
+
__table_args__ = (database.UniqueConstraint("planilla_id", "deduccion_id", name="uq_planilla_deduccion"),)
|
|
719
|
+
|
|
720
|
+
planilla_id = database.Column(database.String(26), database.ForeignKey("planilla.id"), nullable=False)
|
|
721
|
+
deduccion_id = database.Column(database.String(26), database.ForeignKey("deduccion.id"), nullable=False)
|
|
722
|
+
|
|
723
|
+
# Priority order for applying deductions (lower = higher priority)
|
|
724
|
+
prioridad = database.Column(database.Integer, nullable=False, default=100)
|
|
725
|
+
|
|
726
|
+
# Legacy field kept for backward compatibility, use 'prioridad' instead
|
|
727
|
+
orden = database.Column(database.Integer, nullable=True, default=0)
|
|
728
|
+
|
|
729
|
+
editable = database.Column(database.Boolean(), default=True)
|
|
730
|
+
monto_predeterminado = database.Column(database.Numeric(14, 2), nullable=True)
|
|
731
|
+
porcentaje = database.Column(database.Numeric(5, 2), nullable=True)
|
|
732
|
+
activo = database.Column(database.Boolean(), default=True)
|
|
733
|
+
|
|
734
|
+
# Whether this deduction is mandatory (cannot be skipped if salary insufficient)
|
|
735
|
+
es_obligatoria = database.Column(database.Boolean(), default=False)
|
|
736
|
+
|
|
737
|
+
# Whether to stop processing if salary is insufficient for this deduction
|
|
738
|
+
detener_si_insuficiente = database.Column(database.Boolean(), default=False)
|
|
739
|
+
|
|
740
|
+
planilla = database.relationship("Planilla", back_populates="planilla_deducciones")
|
|
741
|
+
deduccion = database.relationship("Deduccion", back_populates="planillas")
|
|
742
|
+
|
|
743
|
+
|
|
744
|
+
class PlanillaPrestacion(database.Model, BaseTabla):
|
|
745
|
+
__tablename__ = "planilla_prestacion"
|
|
746
|
+
__table_args__ = (database.UniqueConstraint("planilla_id", "prestacion_id", name="uq_planilla_prestacion"),)
|
|
747
|
+
|
|
748
|
+
planilla_id = database.Column(database.String(26), database.ForeignKey("planilla.id"), nullable=False)
|
|
749
|
+
prestacion_id = database.Column(database.String(26), database.ForeignKey("prestacion.id"), nullable=False)
|
|
750
|
+
|
|
751
|
+
orden = database.Column(database.Integer, nullable=True, default=0)
|
|
752
|
+
editable = database.Column(database.Boolean(), default=True)
|
|
753
|
+
monto_predeterminado = database.Column(database.Numeric(14, 2), nullable=True)
|
|
754
|
+
porcentaje = database.Column(database.Numeric(5, 2), nullable=True)
|
|
755
|
+
activo = database.Column(database.Boolean(), default=True)
|
|
756
|
+
|
|
757
|
+
planilla = database.relationship("Planilla", back_populates="planilla_prestaciones")
|
|
758
|
+
prestacion = database.relationship("Prestacion", back_populates="planillas")
|
|
759
|
+
|
|
760
|
+
|
|
761
|
+
class PlanillaReglaCalculo(database.Model, BaseTabla):
|
|
762
|
+
"""Association between Planilla and ReglaCalculo (calculation rules/tax tables).
|
|
763
|
+
|
|
764
|
+
This allows a payroll to have multiple calculation rules associated,
|
|
765
|
+
such as income tax rules, social security rules, etc.
|
|
766
|
+
"""
|
|
767
|
+
|
|
768
|
+
__tablename__ = "planilla_regla_calculo"
|
|
769
|
+
__table_args__ = (database.UniqueConstraint("planilla_id", "regla_calculo_id", name="uq_planilla_regla"),)
|
|
770
|
+
|
|
771
|
+
planilla_id = database.Column(database.String(26), database.ForeignKey("planilla.id"), nullable=False)
|
|
772
|
+
regla_calculo_id = database.Column(database.String(26), database.ForeignKey("regla_calculo.id"), nullable=False)
|
|
773
|
+
|
|
774
|
+
# Order of execution (important for dependent calculations)
|
|
775
|
+
orden = database.Column(database.Integer, nullable=False, default=0)
|
|
776
|
+
|
|
777
|
+
# Whether this rule is active for this payroll
|
|
778
|
+
activo = database.Column(database.Boolean(), default=True)
|
|
779
|
+
|
|
780
|
+
# Optional: override parameters for this specific payroll
|
|
781
|
+
parametros_override = database.Column(MutableDict.as_mutable(OrjsonType), nullable=True, default=dict)
|
|
782
|
+
|
|
783
|
+
planilla = database.relationship("Planilla", back_populates="planilla_reglas_calculo")
|
|
784
|
+
regla_calculo = database.relationship("ReglaCalculo", back_populates="planillas")
|
|
785
|
+
|
|
786
|
+
|
|
787
|
+
class PlanillaEmpleado(database.Model, BaseTabla):
|
|
788
|
+
__tablename__ = "planilla_empleado"
|
|
789
|
+
__table_args__ = (database.UniqueConstraint("planilla_id", "empleado_id", name="uq_planilla_empleado"),)
|
|
790
|
+
|
|
791
|
+
planilla_id = database.Column(database.String(26), database.ForeignKey("planilla.id"), nullable=False)
|
|
792
|
+
empleado_id = database.Column(database.String(26), database.ForeignKey("empleado.id"), nullable=False)
|
|
793
|
+
|
|
794
|
+
activo = database.Column(database.Boolean(), default=True)
|
|
795
|
+
fecha_inicio = database.Column(database.Date, nullable=False, default=date.today)
|
|
796
|
+
fecha_fin = database.Column(database.Date, nullable=True) # si deja de estar en la planilla
|
|
797
|
+
|
|
798
|
+
planilla = database.relationship("Planilla", back_populates="planilla_empleados")
|
|
799
|
+
empleado = database.relationship("Empleado", back_populates="planilla_asociaciones")
|
|
800
|
+
|
|
801
|
+
|
|
802
|
+
# Nominas (ejecuciones de planillas)
|
|
803
|
+
class Nomina(database.Model, BaseTabla):
|
|
804
|
+
__tablename__ = "nomina"
|
|
805
|
+
|
|
806
|
+
planilla_id = database.Column(database.String(26), database.ForeignKey("planilla.id"), nullable=False)
|
|
807
|
+
fecha_generacion = database.Column(database.DateTime, nullable=False, default=utc_now)
|
|
808
|
+
periodo_inicio = database.Column(database.Date, nullable=False)
|
|
809
|
+
periodo_fin = database.Column(database.Date, nullable=False)
|
|
810
|
+
generado_por = database.Column(database.String(150), nullable=True)
|
|
811
|
+
estado = database.Column(
|
|
812
|
+
database.String(30), nullable=False, default="generado"
|
|
813
|
+
) # calculando, generado, aprobado, aplicado, pagado, anulado, error (all are valid permanent states)
|
|
814
|
+
|
|
815
|
+
total_bruto = database.Column(database.Numeric(14, 2), nullable=True, default=Decimal("0.00"))
|
|
816
|
+
total_deducciones = database.Column(database.Numeric(14, 2), nullable=True, default=Decimal("0.00"))
|
|
817
|
+
total_neto = database.Column(database.Numeric(14, 2), nullable=True, default=Decimal("0.00"))
|
|
818
|
+
|
|
819
|
+
# Progress tracking for background processing
|
|
820
|
+
total_empleados = database.Column(database.Integer, nullable=True, default=0)
|
|
821
|
+
empleados_procesados = database.Column(database.Integer, nullable=True, default=0)
|
|
822
|
+
empleados_con_error = database.Column(database.Integer, nullable=True, default=0)
|
|
823
|
+
errores_calculo = database.Column(MutableDict.as_mutable(OrjsonType), nullable=True, default=dict)
|
|
824
|
+
procesamiento_en_background = database.Column(database.Boolean, nullable=False, default=False)
|
|
825
|
+
log_procesamiento = database.Column(JSON, nullable=True) # Stores list of log entries as JSON
|
|
826
|
+
empleado_actual = database.Column(database.String(255), nullable=True)
|
|
827
|
+
|
|
828
|
+
# Audit fields for state changes
|
|
829
|
+
aprobado_por = database.Column(database.String(150), nullable=True)
|
|
830
|
+
aprobado_en = database.Column(database.DateTime, nullable=True)
|
|
831
|
+
aplicado_por = database.Column(database.String(150), nullable=True)
|
|
832
|
+
aplicado_en = database.Column(database.DateTime, nullable=True)
|
|
833
|
+
anulado_por = database.Column(database.String(150), nullable=True)
|
|
834
|
+
anulado_en = database.Column(database.DateTime, nullable=True)
|
|
835
|
+
razon_anulacion = database.Column(database.String(500), nullable=True)
|
|
836
|
+
|
|
837
|
+
# Recalculation consistency: Snapshot of calculation context
|
|
838
|
+
# Stores immutable copy of all data needed to reproduce exact same calculation
|
|
839
|
+
fecha_calculo_original = database.Column(database.Date, nullable=True) # Original calculation date
|
|
840
|
+
configuracion_snapshot = database.Column(JSON, nullable=True) # Company config at calculation time
|
|
841
|
+
tipos_cambio_snapshot = database.Column(JSON, nullable=True) # Exchange rates used
|
|
842
|
+
catalogos_snapshot = database.Column(JSON, nullable=True) # Percepciones/Deducciones/Prestaciones formulas
|
|
843
|
+
es_recalculo = database.Column(database.Boolean, nullable=False, default=False) # Flag if this is a recalculation
|
|
844
|
+
nomina_original_id = database.Column(database.String(26), nullable=True) # Reference to original if recalculated
|
|
845
|
+
|
|
846
|
+
planilla = database.relationship("Planilla", back_populates="nominas")
|
|
847
|
+
nomina_empleados = database.relationship(
|
|
848
|
+
"NominaEmpleado",
|
|
849
|
+
back_populates="nomina",
|
|
850
|
+
)
|
|
851
|
+
novedades = database.relationship(
|
|
852
|
+
"NominaNovedad",
|
|
853
|
+
back_populates="nomina",
|
|
854
|
+
)
|
|
855
|
+
comprobante_contable = database.relationship(
|
|
856
|
+
"ComprobanteContable",
|
|
857
|
+
back_populates="nomina",
|
|
858
|
+
uselist=False,
|
|
859
|
+
)
|
|
860
|
+
audit_logs = database.relationship(
|
|
861
|
+
"NominaAuditLog",
|
|
862
|
+
back_populates="nomina",
|
|
863
|
+
cascade="all, delete-orphan",
|
|
864
|
+
)
|
|
865
|
+
|
|
866
|
+
|
|
867
|
+
class NominaEmpleado(database.Model, BaseTabla):
|
|
868
|
+
__tablename__ = "nomina_empleado"
|
|
869
|
+
|
|
870
|
+
nomina_id = database.Column(database.String(26), database.ForeignKey("nomina.id"), nullable=False)
|
|
871
|
+
empleado_id = database.Column(database.String(26), database.ForeignKey("empleado.id"), nullable=False)
|
|
872
|
+
|
|
873
|
+
salario_bruto = database.Column(database.Numeric(14, 2), nullable=True, default=Decimal("0.00"))
|
|
874
|
+
total_ingresos = database.Column(database.Numeric(14, 2), nullable=True, default=Decimal("0.00"))
|
|
875
|
+
total_deducciones = database.Column(database.Numeric(14, 2), nullable=True, default=Decimal("0.00"))
|
|
876
|
+
salario_neto = database.Column(database.Numeric(14, 2), nullable=True, default=Decimal("0.00"))
|
|
877
|
+
|
|
878
|
+
# datos para auditoria/moneda
|
|
879
|
+
moneda_origen_id = database.Column(database.String(26), database.ForeignKey("moneda.id"), nullable=True)
|
|
880
|
+
tipo_cambio_aplicado = database.Column(database.Numeric(24, 10), nullable=True)
|
|
881
|
+
|
|
882
|
+
nomina = database.relationship("Nomina", back_populates="nomina_empleados")
|
|
883
|
+
empleado = database.relationship("Empleado", back_populates="nominas")
|
|
884
|
+
|
|
885
|
+
nomina_detalles = database.relationship(
|
|
886
|
+
"NominaDetalle",
|
|
887
|
+
back_populates="nomina_empleado",
|
|
888
|
+
)
|
|
889
|
+
|
|
890
|
+
# Backup de datos del empleado al momento de la generación de la nómina
|
|
891
|
+
cargo_snapshot = database.Column(database.String(150), nullable=True)
|
|
892
|
+
area_snapshot = database.Column(database.String(150), nullable=True)
|
|
893
|
+
centro_costos_snapshot = database.Column(database.String(150), nullable=True)
|
|
894
|
+
sueldo_base_historico = database.Column(database.Numeric(14, 2), nullable=False, default=Decimal("0.00"))
|
|
895
|
+
|
|
896
|
+
|
|
897
|
+
class NominaDetalle(database.Model, BaseTabla):
|
|
898
|
+
__tablename__ = "nomina_detalle"
|
|
899
|
+
|
|
900
|
+
nomina_empleado_id = database.Column(database.String(26), database.ForeignKey("nomina_empleado.id"), nullable=False)
|
|
901
|
+
tipo = database.Column(database.String(15), nullable=False) # 'ingreso' | 'deduccion' | 'prestacion'
|
|
902
|
+
codigo = database.Column(database.String(50), nullable=False)
|
|
903
|
+
descripcion = database.Column(database.String(255), nullable=True)
|
|
904
|
+
monto = database.Column(database.Numeric(14, 2), nullable=False, default=Decimal("0.00"))
|
|
905
|
+
orden = database.Column(database.Integer, nullable=True, default=0)
|
|
906
|
+
|
|
907
|
+
# referencias opcionales a catálogo original (si aplica)
|
|
908
|
+
percepcion_id = database.Column(database.String(26), database.ForeignKey("percepcion.id"), nullable=True)
|
|
909
|
+
deduccion_id = database.Column(database.String(26), database.ForeignKey("deduccion.id"), nullable=True)
|
|
910
|
+
prestacion_id = database.Column(database.String(26), database.ForeignKey("prestacion.id"), nullable=True)
|
|
911
|
+
|
|
912
|
+
nomina_empleado = database.relationship("NominaEmpleado", back_populates="nomina_detalles")
|
|
913
|
+
percepcion = database.relationship("Percepcion", back_populates="nomina_detalles", foreign_keys=[percepcion_id])
|
|
914
|
+
deduccion = database.relationship("Deduccion", back_populates="nomina_detalles", foreign_keys=[deduccion_id])
|
|
915
|
+
prestacion = database.relationship("Prestacion", back_populates="nomina_detalles", foreign_keys=[prestacion_id])
|
|
916
|
+
|
|
917
|
+
|
|
918
|
+
# Liquidaciones (terminaciones laborales)
|
|
919
|
+
class LiquidacionConcepto(database.Model, BaseTabla):
|
|
920
|
+
__tablename__ = "liquidacion_concepto"
|
|
921
|
+
|
|
922
|
+
codigo = database.Column(database.String(50), unique=True, nullable=False, index=True)
|
|
923
|
+
nombre = database.Column(database.String(150), nullable=False)
|
|
924
|
+
descripcion = database.Column(database.String(255), nullable=True)
|
|
925
|
+
activo = database.Column(database.Boolean(), default=True, nullable=False)
|
|
926
|
+
|
|
927
|
+
|
|
928
|
+
class Liquidacion(database.Model, BaseTabla):
|
|
929
|
+
__tablename__ = "liquidacion"
|
|
930
|
+
|
|
931
|
+
empleado_id = database.Column(database.String(26), database.ForeignKey("empleado.id"), nullable=False, index=True)
|
|
932
|
+
concepto_id = database.Column(
|
|
933
|
+
database.String(26), database.ForeignKey("liquidacion_concepto.id"), nullable=True, index=True
|
|
934
|
+
)
|
|
935
|
+
|
|
936
|
+
fecha_calculo = database.Column(database.Date, nullable=False, default=date.today)
|
|
937
|
+
ultimo_dia_pagado = database.Column(database.Date, nullable=True)
|
|
938
|
+
dias_por_pagar = database.Column(database.Integer, nullable=False, default=0)
|
|
939
|
+
|
|
940
|
+
estado = database.Column(database.String(30), nullable=False, default="borrador") # borrador, aplicada, pagada
|
|
941
|
+
|
|
942
|
+
total_bruto = database.Column(database.Numeric(14, 2), nullable=True, default=Decimal("0.00"))
|
|
943
|
+
total_deducciones = database.Column(database.Numeric(14, 2), nullable=True, default=Decimal("0.00"))
|
|
944
|
+
total_neto = database.Column(database.Numeric(14, 2), nullable=True, default=Decimal("0.00"))
|
|
945
|
+
|
|
946
|
+
errores_calculo = database.Column(MutableDict.as_mutable(OrjsonType), nullable=True, default=dict)
|
|
947
|
+
advertencias_calculo = database.Column(JSON, nullable=True, default=list)
|
|
948
|
+
|
|
949
|
+
empleado = database.relationship("Empleado")
|
|
950
|
+
concepto = database.relationship("LiquidacionConcepto")
|
|
951
|
+
detalles = database.relationship("LiquidacionDetalle", back_populates="liquidacion", cascade="all,delete-orphan")
|
|
952
|
+
|
|
953
|
+
|
|
954
|
+
class LiquidacionDetalle(database.Model, BaseTabla):
|
|
955
|
+
__tablename__ = "liquidacion_detalle"
|
|
956
|
+
|
|
957
|
+
liquidacion_id = database.Column(
|
|
958
|
+
database.String(26), database.ForeignKey("liquidacion.id"), nullable=False, index=True
|
|
959
|
+
)
|
|
960
|
+
tipo = database.Column(database.String(15), nullable=False) # 'ingreso' | 'deduccion' | 'prestacion'
|
|
961
|
+
codigo = database.Column(database.String(50), nullable=False)
|
|
962
|
+
descripcion = database.Column(database.String(255), nullable=True)
|
|
963
|
+
monto = database.Column(database.Numeric(14, 2), nullable=False, default=Decimal("0.00"))
|
|
964
|
+
orden = database.Column(database.Integer, nullable=True, default=0)
|
|
965
|
+
|
|
966
|
+
percepcion_id = database.Column(database.String(26), database.ForeignKey("percepcion.id"), nullable=True)
|
|
967
|
+
deduccion_id = database.Column(database.String(26), database.ForeignKey("deduccion.id"), nullable=True)
|
|
968
|
+
prestacion_id = database.Column(database.String(26), database.ForeignKey("prestacion.id"), nullable=True)
|
|
969
|
+
|
|
970
|
+
liquidacion = database.relationship("Liquidacion", back_populates="detalles")
|
|
971
|
+
percepcion = database.relationship("Percepcion", foreign_keys=[percepcion_id])
|
|
972
|
+
deduccion = database.relationship("Deduccion", foreign_keys=[deduccion_id])
|
|
973
|
+
prestacion = database.relationship("Prestacion", foreign_keys=[prestacion_id])
|
|
974
|
+
|
|
975
|
+
|
|
976
|
+
class NominaNovedad(database.Model, BaseTabla):
|
|
977
|
+
__tablename__ = "nomina_novedad"
|
|
978
|
+
|
|
979
|
+
# FK a la ejecución de Nómina (el ID que solicitaste)
|
|
980
|
+
nomina_id = database.Column(database.String(26), database.ForeignKey("nomina.id"), nullable=False)
|
|
981
|
+
# FK al empleado afectado
|
|
982
|
+
empleado_id = database.Column(database.String(26), database.ForeignKey("empleado.id"), nullable=False)
|
|
983
|
+
|
|
984
|
+
tipo_valor = database.Column(database.String(20), nullable=True) # horas | dias | cantidad | monto | porcentaje
|
|
985
|
+
|
|
986
|
+
# El código del concepto que se está modificando/aplicando
|
|
987
|
+
codigo_concepto = database.Column(database.String(50), nullable=False)
|
|
988
|
+
|
|
989
|
+
# Valor/cantidad de la novedad (ej. 5 horas, 1500 de comisión, 1 día de ausencia)
|
|
990
|
+
valor_cantidad = database.Column(database.Numeric(14, 2), nullable=False, default=Decimal("0.00"))
|
|
991
|
+
|
|
992
|
+
# Fecha de ocurrencia del evento (útil para auditoría y prorrateo)
|
|
993
|
+
fecha_novedad = database.Column(database.Date, nullable=True)
|
|
994
|
+
|
|
995
|
+
# Referencia opcional al maestro para saber qué regla aplica
|
|
996
|
+
percepcion_id = database.Column(database.String(26), database.ForeignKey("percepcion.id"), nullable=True)
|
|
997
|
+
deduccion_id = database.Column(database.String(26), database.ForeignKey("deduccion.id"), nullable=True)
|
|
998
|
+
|
|
999
|
+
# ---- Vacation Module Integration ----
|
|
1000
|
+
# Flag to mark this novelty as vacation/time-off
|
|
1001
|
+
es_descanso_vacaciones = database.Column(database.Boolean(), default=False, nullable=False)
|
|
1002
|
+
|
|
1003
|
+
# Reference to VacationNovelty if this is a vacation leave
|
|
1004
|
+
vacation_novelty_id = database.Column(
|
|
1005
|
+
database.String(26), database.ForeignKey("vacation_novelty.id"), nullable=True, index=True
|
|
1006
|
+
)
|
|
1007
|
+
|
|
1008
|
+
# Dates for vacation period (when es_descanso_vacaciones=True)
|
|
1009
|
+
fecha_inicio_descanso = database.Column(database.Date, nullable=True)
|
|
1010
|
+
fecha_fin_descanso = database.Column(database.Date, nullable=True)
|
|
1011
|
+
|
|
1012
|
+
# Estado de la novedad: 'pendiente' | 'ejecutada'
|
|
1013
|
+
# Se marca como 'ejecutada' cuando la nómina cambia a estado 'aplicado'
|
|
1014
|
+
estado = database.Column(database.String(20), nullable=False, default="pendiente") # Use NovedadEstado enum values
|
|
1015
|
+
|
|
1016
|
+
nomina = database.relationship("Nomina", back_populates="novedades")
|
|
1017
|
+
empleado = database.relationship("Empleado", back_populates="novedades_registradas")
|
|
1018
|
+
vacation_novelty = database.relationship("VacationNovelty", foreign_keys=[vacation_novelty_id])
|
|
1019
|
+
|
|
1020
|
+
|
|
1021
|
+
# Comprobante Contable (Accounting Voucher)
|
|
1022
|
+
class ComprobanteContable(database.Model, BaseTabla):
|
|
1023
|
+
"""Stores the accounting voucher header for audit purposes.
|
|
1024
|
+
|
|
1025
|
+
This model preserves the accounting voucher header generated at the time of payroll
|
|
1026
|
+
calculation, preventing configuration changes from affecting historical records.
|
|
1027
|
+
Detail lines are stored in ComprobanteContableLinea.
|
|
1028
|
+
|
|
1029
|
+
Audit Trail:
|
|
1030
|
+
- aplicado_por: User who applied the nomina (immutable)
|
|
1031
|
+
- fecha_aplicacion: Date when nomina was applied (immutable)
|
|
1032
|
+
- modificado_por: User who last regenerated the voucher
|
|
1033
|
+
- fecha_modificacion: Date when voucher was last regenerated
|
|
1034
|
+
"""
|
|
1035
|
+
|
|
1036
|
+
__tablename__ = "comprobante_contable"
|
|
1037
|
+
|
|
1038
|
+
nomina_id = database.Column(database.String(26), database.ForeignKey("nomina.id"), nullable=False, unique=True)
|
|
1039
|
+
|
|
1040
|
+
# Header information
|
|
1041
|
+
fecha_calculo = database.Column(database.Date, nullable=False, default=date.today)
|
|
1042
|
+
concepto = database.Column(database.String(255), nullable=True) # Description/concept of the voucher
|
|
1043
|
+
moneda_id = database.Column(database.String(26), database.ForeignKey("moneda.id"), nullable=True)
|
|
1044
|
+
|
|
1045
|
+
# Summary totals (calculated from lines)
|
|
1046
|
+
total_debitos = database.Column(database.Numeric(14, 2), nullable=False, default=Decimal("0.00"))
|
|
1047
|
+
total_creditos = database.Column(database.Numeric(14, 2), nullable=False, default=Decimal("0.00"))
|
|
1048
|
+
balance = database.Column(database.Numeric(14, 2), nullable=False, default=Decimal("0.00"))
|
|
1049
|
+
|
|
1050
|
+
# Warnings about incomplete configurations
|
|
1051
|
+
advertencias = database.Column(JSON, nullable=True, default=list)
|
|
1052
|
+
|
|
1053
|
+
# Audit trail - immutable fields (set once when nomina is applied)
|
|
1054
|
+
aplicado_por = database.Column(database.String(150), nullable=True)
|
|
1055
|
+
fecha_aplicacion = database.Column(database.DateTime, nullable=True)
|
|
1056
|
+
|
|
1057
|
+
# Audit trail - modification tracking (updated each time voucher is regenerated)
|
|
1058
|
+
modificado_por = database.Column(database.String(150), nullable=True)
|
|
1059
|
+
fecha_modificacion = database.Column(database.DateTime, nullable=True)
|
|
1060
|
+
veces_modificado = database.Column(database.Integer, nullable=False, default=0)
|
|
1061
|
+
|
|
1062
|
+
nomina = database.relationship("Nomina", back_populates="comprobante_contable")
|
|
1063
|
+
moneda = database.relationship("Moneda")
|
|
1064
|
+
lineas = database.relationship(
|
|
1065
|
+
"ComprobanteContableLinea",
|
|
1066
|
+
back_populates="comprobante",
|
|
1067
|
+
cascade="all, delete-orphan",
|
|
1068
|
+
order_by="ComprobanteContableLinea.orden",
|
|
1069
|
+
)
|
|
1070
|
+
|
|
1071
|
+
|
|
1072
|
+
class ComprobanteContableLinea(database.Model, BaseTabla):
|
|
1073
|
+
"""Stores individual accounting entry lines for each employee's payroll calculation.
|
|
1074
|
+
|
|
1075
|
+
Each line represents a single accounting entry (debit or credit) for a specific
|
|
1076
|
+
employee, concept, account, and cost center. Provides complete audit trail.
|
|
1077
|
+
|
|
1078
|
+
Audit Fields:
|
|
1079
|
+
- Empleado: empleado_id, empleado_codigo, empleado_nombre
|
|
1080
|
+
- Cuenta: codigo_cuenta, descripcion_cuenta
|
|
1081
|
+
- Centro de costos: centro_costos
|
|
1082
|
+
- Concepto origen: concepto_codigo (código de percepción/deducción/prestación)
|
|
1083
|
+
- Tipo: tipo_debito_credito ('debito' o 'credito')
|
|
1084
|
+
- Monto: debito o credito (solo uno tiene valor, el otro es 0)
|
|
1085
|
+
"""
|
|
1086
|
+
|
|
1087
|
+
__tablename__ = "comprobante_contable_linea"
|
|
1088
|
+
|
|
1089
|
+
comprobante_id = database.Column(
|
|
1090
|
+
database.String(26), database.ForeignKey("comprobante_contable.id"), nullable=False, index=True
|
|
1091
|
+
)
|
|
1092
|
+
nomina_empleado_id = database.Column(
|
|
1093
|
+
database.String(26), database.ForeignKey("nomina_empleado.id"), nullable=False, index=True
|
|
1094
|
+
)
|
|
1095
|
+
|
|
1096
|
+
# Employee information for audit trail (denormalized for easier reporting)
|
|
1097
|
+
empleado_id = database.Column(database.String(26), database.ForeignKey("empleado.id"), nullable=False, index=True)
|
|
1098
|
+
empleado_codigo = database.Column(database.String(20), nullable=False, index=True)
|
|
1099
|
+
empleado_nombre = database.Column(database.String(255), nullable=False)
|
|
1100
|
+
|
|
1101
|
+
# Accounting account information (nullable to support incomplete configuration)
|
|
1102
|
+
codigo_cuenta = database.Column(database.String(64), nullable=True, index=True)
|
|
1103
|
+
descripcion_cuenta = database.Column(database.String(255), nullable=True)
|
|
1104
|
+
|
|
1105
|
+
# Cost center for cost allocation
|
|
1106
|
+
centro_costos = database.Column(database.String(150), nullable=True, index=True)
|
|
1107
|
+
|
|
1108
|
+
# Amount (only one should be non-zero: debito OR credito)
|
|
1109
|
+
tipo_debito_credito = database.Column(database.String(10), nullable=False, index=True) # 'debito' or 'credito'
|
|
1110
|
+
debito = database.Column(database.Numeric(14, 2), nullable=False, default=Decimal("0.00"))
|
|
1111
|
+
credito = database.Column(database.Numeric(14, 2), nullable=False, default=Decimal("0.00"))
|
|
1112
|
+
|
|
1113
|
+
# Calculated amount (the actual value, duplicated for convenience)
|
|
1114
|
+
monto_calculado = database.Column(database.Numeric(14, 2), nullable=False, default=Decimal("0.00"))
|
|
1115
|
+
|
|
1116
|
+
# Source concept information for complete audit trail
|
|
1117
|
+
concepto = database.Column(database.String(255), nullable=False) # Description of the concept
|
|
1118
|
+
tipo_concepto = database.Column(
|
|
1119
|
+
database.String(20), nullable=False, index=True
|
|
1120
|
+
) # 'salario_base', 'percepcion', 'deduccion', 'prestacion', 'prestamo'
|
|
1121
|
+
concepto_codigo = database.Column(database.String(50), nullable=False, index=True) # Code from source concept
|
|
1122
|
+
|
|
1123
|
+
# Order for consistent display
|
|
1124
|
+
orden = database.Column(database.Integer, nullable=False, default=0, index=True)
|
|
1125
|
+
|
|
1126
|
+
comprobante = database.relationship("ComprobanteContable", back_populates="lineas")
|
|
1127
|
+
nomina_empleado = database.relationship("NominaEmpleado")
|
|
1128
|
+
empleado = database.relationship("Empleado")
|
|
1129
|
+
|
|
1130
|
+
|
|
1131
|
+
# Historial de cambios de salario
|
|
1132
|
+
class HistorialSalario(database.Model, BaseTabla):
|
|
1133
|
+
__tablename__ = "historial_salario"
|
|
1134
|
+
|
|
1135
|
+
empleado_id = database.Column(
|
|
1136
|
+
database.String(26),
|
|
1137
|
+
database.ForeignKey("empleado.id"),
|
|
1138
|
+
nullable=False,
|
|
1139
|
+
index=True,
|
|
1140
|
+
)
|
|
1141
|
+
fecha_efectiva = database.Column(database.Date, nullable=False, index=True)
|
|
1142
|
+
salario_anterior = database.Column(database.Numeric(14, 2), nullable=False, default=Decimal("0.00"))
|
|
1143
|
+
salario_nuevo = database.Column(database.Numeric(14, 2), nullable=False, default=Decimal("0.00"))
|
|
1144
|
+
motivo = database.Column(database.String(255), nullable=True)
|
|
1145
|
+
autorizado_por = database.Column(database.String(150), nullable=True)
|
|
1146
|
+
|
|
1147
|
+
empleado = database.relationship("Empleado", back_populates="historial_salarios")
|
|
1148
|
+
|
|
1149
|
+
|
|
1150
|
+
# Configuración de vacaciones por país/empresa
|
|
1151
|
+
class ConfiguracionVacaciones(database.Model, BaseTabla):
|
|
1152
|
+
__tablename__ = "configuracion_vacaciones"
|
|
1153
|
+
|
|
1154
|
+
codigo = database.Column(database.String(50), unique=True, nullable=False, index=True)
|
|
1155
|
+
descripcion = database.Column(database.String(255), nullable=True)
|
|
1156
|
+
dias_por_mes = database.Column(database.Numeric(5, 2), nullable=False, default=Decimal("2.50"))
|
|
1157
|
+
dias_minimos_descanso = database.Column(database.Integer, nullable=False, default=1)
|
|
1158
|
+
dias_maximos_acumulables = database.Column(database.Integer, nullable=True)
|
|
1159
|
+
meses_minimos_para_devengar = database.Column(database.Integer, nullable=False, default=1)
|
|
1160
|
+
activo = database.Column(database.Boolean(), default=True)
|
|
1161
|
+
|
|
1162
|
+
vacaciones_empleados = database.relationship("VacacionEmpleado", back_populates="configuracion")
|
|
1163
|
+
|
|
1164
|
+
|
|
1165
|
+
# Saldo y control de vacaciones por empleado
|
|
1166
|
+
class VacacionEmpleado(database.Model, BaseTabla):
|
|
1167
|
+
__tablename__ = "vacacion_empleado"
|
|
1168
|
+
__table_args__ = (database.UniqueConstraint("empleado_id", "anio", name="uq_vacacion_empleado_anio"),)
|
|
1169
|
+
|
|
1170
|
+
empleado_id = database.Column(
|
|
1171
|
+
database.String(26),
|
|
1172
|
+
database.ForeignKey("empleado.id"),
|
|
1173
|
+
nullable=False,
|
|
1174
|
+
index=True,
|
|
1175
|
+
)
|
|
1176
|
+
configuracion_id = database.Column(
|
|
1177
|
+
database.String(26),
|
|
1178
|
+
database.ForeignKey("configuracion_vacaciones.id"),
|
|
1179
|
+
nullable=False,
|
|
1180
|
+
)
|
|
1181
|
+
anio = database.Column(database.Integer, nullable=False)
|
|
1182
|
+
dias_devengados = database.Column(database.Numeric(5, 2), nullable=False, default=Decimal("0.00"))
|
|
1183
|
+
dias_tomados = database.Column(database.Numeric(5, 2), nullable=False, default=Decimal("0.00"))
|
|
1184
|
+
dias_pendientes = database.Column(database.Numeric(5, 2), nullable=False, default=Decimal("0.00"))
|
|
1185
|
+
dias_pagados = database.Column(database.Numeric(5, 2), nullable=False, default=Decimal("0.00"))
|
|
1186
|
+
fecha_ultimo_calculo = database.Column(database.Date, nullable=True)
|
|
1187
|
+
|
|
1188
|
+
empleado = database.relationship("Empleado", back_populates="vacaciones")
|
|
1189
|
+
configuracion = database.relationship("ConfiguracionVacaciones", back_populates="vacaciones_empleados")
|
|
1190
|
+
|
|
1191
|
+
|
|
1192
|
+
# Registro de vacaciones descansadas
|
|
1193
|
+
class VacacionDescansada(database.Model, BaseTabla):
|
|
1194
|
+
__tablename__ = "vacacion_descansada"
|
|
1195
|
+
|
|
1196
|
+
empleado_id = database.Column(
|
|
1197
|
+
database.String(26),
|
|
1198
|
+
database.ForeignKey("empleado.id"),
|
|
1199
|
+
nullable=False,
|
|
1200
|
+
index=True,
|
|
1201
|
+
)
|
|
1202
|
+
fecha_inicio = database.Column(database.Date, nullable=False)
|
|
1203
|
+
fecha_fin = database.Column(database.Date, nullable=False)
|
|
1204
|
+
dias_tomados = database.Column(database.Numeric(5, 2), nullable=False, default=Decimal("0.00"))
|
|
1205
|
+
estado = database.Column(database.String(30), nullable=False, default="pendiente")
|
|
1206
|
+
autorizado_por = database.Column(database.String(150), nullable=True)
|
|
1207
|
+
fecha_autorizacion = database.Column(database.Date, nullable=True)
|
|
1208
|
+
observaciones = database.Column(database.String(500), nullable=True)
|
|
1209
|
+
|
|
1210
|
+
empleado = database.relationship("Empleado", back_populates="vacaciones_descansadas")
|
|
1211
|
+
|
|
1212
|
+
|
|
1213
|
+
# Tabla de impuestos (tramos fiscales)
|
|
1214
|
+
class TablaImpuesto(database.Model, BaseTabla):
|
|
1215
|
+
__tablename__ = "tabla_impuesto"
|
|
1216
|
+
__table_args__ = (
|
|
1217
|
+
database.UniqueConstraint(
|
|
1218
|
+
"deduccion_id",
|
|
1219
|
+
"limite_inferior",
|
|
1220
|
+
"vigente_desde",
|
|
1221
|
+
name="uq_impuesto_tramo_vigencia",
|
|
1222
|
+
),
|
|
1223
|
+
)
|
|
1224
|
+
|
|
1225
|
+
deduccion_id = database.Column(
|
|
1226
|
+
database.String(26),
|
|
1227
|
+
database.ForeignKey("deduccion.id"),
|
|
1228
|
+
nullable=False,
|
|
1229
|
+
index=True,
|
|
1230
|
+
)
|
|
1231
|
+
limite_inferior = database.Column(database.Numeric(14, 2), nullable=False, default=Decimal("0.00"))
|
|
1232
|
+
limite_superior = database.Column(database.Numeric(14, 2), nullable=True)
|
|
1233
|
+
porcentaje = database.Column(database.Numeric(5, 2), nullable=False, default=Decimal("0.00"))
|
|
1234
|
+
cuota_fija = database.Column(database.Numeric(14, 2), nullable=True, default=Decimal("0.00"))
|
|
1235
|
+
sobre_excedente_de = database.Column(database.Numeric(14, 2), nullable=True, default=Decimal("0.00"))
|
|
1236
|
+
vigente_desde = database.Column(database.Date, nullable=False)
|
|
1237
|
+
vigente_hasta = database.Column(database.Date, nullable=True)
|
|
1238
|
+
activo = database.Column(database.Boolean(), default=True)
|
|
1239
|
+
|
|
1240
|
+
deduccion = database.relationship("Deduccion", back_populates="tablas_impuesto")
|
|
1241
|
+
|
|
1242
|
+
|
|
1243
|
+
# Adelantos de salario y préstamos
|
|
1244
|
+
class Adelanto(database.Model, BaseTabla):
|
|
1245
|
+
"""Loan and salary advance management.
|
|
1246
|
+
|
|
1247
|
+
Supports both loans (préstamos) with interest rates and salary advances (adelantos).
|
|
1248
|
+
Can handle multi-currency loans with automatic conversion tracking.
|
|
1249
|
+
"""
|
|
1250
|
+
|
|
1251
|
+
__tablename__ = "adelanto"
|
|
1252
|
+
|
|
1253
|
+
empleado_id = database.Column(
|
|
1254
|
+
database.String(26),
|
|
1255
|
+
database.ForeignKey("empleado.id"),
|
|
1256
|
+
nullable=False,
|
|
1257
|
+
index=True,
|
|
1258
|
+
)
|
|
1259
|
+
deduccion_id = database.Column(database.String(26), database.ForeignKey("deduccion.id"), nullable=True)
|
|
1260
|
+
|
|
1261
|
+
# Tipo: prestamo o adelanto
|
|
1262
|
+
tipo = database.Column(database.String(20), nullable=False, default="adelanto") # adelanto, prestamo
|
|
1263
|
+
|
|
1264
|
+
# Fechas
|
|
1265
|
+
fecha_solicitud = database.Column(database.Date, nullable=False, default=date.today)
|
|
1266
|
+
fecha_aprobacion = database.Column(database.Date, nullable=True)
|
|
1267
|
+
fecha_desembolso = database.Column(database.Date, nullable=True)
|
|
1268
|
+
|
|
1269
|
+
# Montos
|
|
1270
|
+
monto_solicitado = database.Column(database.Numeric(14, 2), nullable=False, default=Decimal("0.00"))
|
|
1271
|
+
monto_aprobado = database.Column(database.Numeric(14, 2), nullable=True, default=Decimal("0.00"))
|
|
1272
|
+
saldo_pendiente = database.Column(database.Numeric(14, 2), nullable=False, default=Decimal("0.00"))
|
|
1273
|
+
|
|
1274
|
+
# Currency support - loan can be in different currency than payroll
|
|
1275
|
+
moneda_id = database.Column(database.String(26), database.ForeignKey("moneda.id"), nullable=True)
|
|
1276
|
+
# Track amounts in both loan currency and payroll currency
|
|
1277
|
+
monto_deducido_moneda_planilla = database.Column(database.Numeric(14, 2), nullable=True, default=Decimal("0.00"))
|
|
1278
|
+
monto_aplicado_moneda_prestamo = database.Column(database.Numeric(14, 2), nullable=True, default=Decimal("0.00"))
|
|
1279
|
+
|
|
1280
|
+
# Cuotas
|
|
1281
|
+
cuotas_pactadas = database.Column(database.Integer, nullable=True)
|
|
1282
|
+
monto_por_cuota = database.Column(database.Numeric(14, 2), nullable=True, default=Decimal("0.00"))
|
|
1283
|
+
|
|
1284
|
+
# Interest rates (for loans)
|
|
1285
|
+
tasa_interes = database.Column(
|
|
1286
|
+
database.Numeric(5, 4), nullable=True, default=Decimal("0.0000")
|
|
1287
|
+
) # e.g., 0.0500 = 5%
|
|
1288
|
+
tipo_interes = database.Column(database.String(20), nullable=True, default="ninguno") # ninguno, simple, compuesto
|
|
1289
|
+
|
|
1290
|
+
# Amortization method (for loans with interest)
|
|
1291
|
+
metodo_amortizacion = database.Column(
|
|
1292
|
+
database.String(20), nullable=True, default="frances"
|
|
1293
|
+
) # frances (constant payment), aleman (constant amortization)
|
|
1294
|
+
|
|
1295
|
+
# Interest tracking
|
|
1296
|
+
interes_acumulado = database.Column(
|
|
1297
|
+
database.Numeric(14, 2), nullable=False, default=Decimal("0.00")
|
|
1298
|
+
) # Total interest accumulated
|
|
1299
|
+
fecha_ultimo_calculo_interes = database.Column(database.Date, nullable=True) # Last date interest was calculated
|
|
1300
|
+
|
|
1301
|
+
# Accounting fields for initial disbursement
|
|
1302
|
+
cuenta_debe = database.Column(database.String(64), nullable=True)
|
|
1303
|
+
descripcion_cuenta_debe = database.Column(database.String(255), nullable=True)
|
|
1304
|
+
cuenta_haber = database.Column(database.String(64), nullable=True)
|
|
1305
|
+
descripcion_cuenta_haber = database.Column(database.String(255), nullable=True)
|
|
1306
|
+
|
|
1307
|
+
# Estado: borrador, pendiente, aprobado, aplicado (pagado), rechazado, cancelado
|
|
1308
|
+
estado = database.Column(database.String(30), nullable=False, default="borrador")
|
|
1309
|
+
motivo = database.Column(database.String(500), nullable=True)
|
|
1310
|
+
aprobado_por = database.Column(database.String(150), nullable=True)
|
|
1311
|
+
rechazado_por = database.Column(database.String(150), nullable=True)
|
|
1312
|
+
motivo_rechazo = database.Column(database.String(500), nullable=True)
|
|
1313
|
+
|
|
1314
|
+
empleado = database.relationship("Empleado", back_populates="adelantos")
|
|
1315
|
+
deduccion = database.relationship("Deduccion", back_populates="adelantos")
|
|
1316
|
+
moneda = database.relationship("Moneda")
|
|
1317
|
+
abonos = database.relationship("AdelantoAbono", back_populates="adelanto", cascade="all,delete-orphan")
|
|
1318
|
+
intereses = database.relationship("InteresAdelanto", back_populates="adelanto", cascade="all,delete-orphan")
|
|
1319
|
+
|
|
1320
|
+
|
|
1321
|
+
# Control de abonos/pagos a adelantos
|
|
1322
|
+
class AdelantoAbono(database.Model, BaseTabla):
|
|
1323
|
+
"""Payment record for loans and advances.
|
|
1324
|
+
|
|
1325
|
+
Tracks all payments made against a loan, whether automatic (from payroll)
|
|
1326
|
+
or manual (cash, bank transfer, etc.). For manual payments, includes
|
|
1327
|
+
comprehensive audit trail information.
|
|
1328
|
+
"""
|
|
1329
|
+
|
|
1330
|
+
__tablename__ = "adelanto_abono"
|
|
1331
|
+
|
|
1332
|
+
adelanto_id = database.Column(
|
|
1333
|
+
database.String(26),
|
|
1334
|
+
database.ForeignKey("adelanto.id"),
|
|
1335
|
+
nullable=False,
|
|
1336
|
+
index=True,
|
|
1337
|
+
)
|
|
1338
|
+
nomina_id = database.Column(database.String(26), database.ForeignKey("nomina.id"), nullable=True)
|
|
1339
|
+
liquidacion_id = database.Column(database.String(26), database.ForeignKey("liquidacion.id"), nullable=True)
|
|
1340
|
+
fecha_abono = database.Column(database.Date, nullable=False, default=date.today)
|
|
1341
|
+
monto_abonado = database.Column(database.Numeric(14, 2), nullable=False, default=Decimal("0.00"))
|
|
1342
|
+
saldo_anterior = database.Column(database.Numeric(14, 2), nullable=False, default=Decimal("0.00"))
|
|
1343
|
+
saldo_posterior = database.Column(database.Numeric(14, 2), nullable=False, default=Decimal("0.00"))
|
|
1344
|
+
tipo_abono = database.Column(database.String(30), nullable=False, default="nomina") # nomina, manual, condonacion
|
|
1345
|
+
observaciones = database.Column(database.String(500), nullable=True)
|
|
1346
|
+
|
|
1347
|
+
# Audit trail fields for manual payments
|
|
1348
|
+
# These fields ensure compliance and traceability for financial audits
|
|
1349
|
+
tipo_comprobante = database.Column(
|
|
1350
|
+
database.String(50), nullable=True
|
|
1351
|
+
) # recibo_caja, minuta_deposito, transferencia, cheque, otro
|
|
1352
|
+
numero_comprobante = database.Column(database.String(100), nullable=True) # Receipt/document number
|
|
1353
|
+
referencia_bancaria = database.Column(database.String(100), nullable=True) # Bank reference for electronic payments
|
|
1354
|
+
|
|
1355
|
+
# Accounting entries for manual payments/deductions
|
|
1356
|
+
# Optional for payments/forgiveness, but useful for financial reconciliation
|
|
1357
|
+
cuenta_debe = database.Column(database.String(64), nullable=True) # Debit account for payment/deduction
|
|
1358
|
+
descripcion_cuenta_debe = database.Column(database.String(255), nullable=True)
|
|
1359
|
+
cuenta_haber = database.Column(database.String(64), nullable=True) # Credit account for payment/deduction
|
|
1360
|
+
descripcion_cuenta_haber = database.Column(database.String(255), nullable=True)
|
|
1361
|
+
|
|
1362
|
+
# Loan forgiveness/write-off fields (condonación)
|
|
1363
|
+
# Used when company decides not to collect part or all of the loan
|
|
1364
|
+
autorizado_por = database.Column(database.String(150), nullable=True) # Name/title of authorizing person
|
|
1365
|
+
documento_soporte = database.Column(
|
|
1366
|
+
database.String(50), nullable=True
|
|
1367
|
+
) # Type: correo, memorandum, acta, resolucion, carta, otro
|
|
1368
|
+
referencia_documento = database.Column(
|
|
1369
|
+
database.String(200), nullable=True
|
|
1370
|
+
) # Document reference (date, number, etc.)
|
|
1371
|
+
justificacion = database.Column(database.Text, nullable=True) # Full justification text for audit trail
|
|
1372
|
+
|
|
1373
|
+
adelanto = database.relationship("Adelanto", back_populates="abonos")
|
|
1374
|
+
nomina = database.relationship("Nomina")
|
|
1375
|
+
liquidacion = database.relationship("Liquidacion")
|
|
1376
|
+
|
|
1377
|
+
|
|
1378
|
+
# Interest journal for loans
|
|
1379
|
+
class InteresAdelanto(database.Model, BaseTabla):
|
|
1380
|
+
"""Interest calculation journal for loans.
|
|
1381
|
+
|
|
1382
|
+
Tracks interest calculations for each loan during payroll processing.
|
|
1383
|
+
Each payroll execution calculates interest for the days elapsed since
|
|
1384
|
+
the last calculation and records it here.
|
|
1385
|
+
|
|
1386
|
+
This provides a complete audit trail of interest calculations and ensures
|
|
1387
|
+
interest is properly tracked and applied to the loan balance.
|
|
1388
|
+
"""
|
|
1389
|
+
|
|
1390
|
+
__tablename__ = "interes_adelanto"
|
|
1391
|
+
|
|
1392
|
+
adelanto_id = database.Column(
|
|
1393
|
+
database.String(26),
|
|
1394
|
+
database.ForeignKey("adelanto.id"),
|
|
1395
|
+
nullable=False,
|
|
1396
|
+
index=True,
|
|
1397
|
+
)
|
|
1398
|
+
nomina_id = database.Column(database.String(26), database.ForeignKey("nomina.id"), nullable=True)
|
|
1399
|
+
|
|
1400
|
+
# Calculation period
|
|
1401
|
+
fecha_desde = database.Column(database.Date, nullable=False)
|
|
1402
|
+
fecha_hasta = database.Column(database.Date, nullable=False)
|
|
1403
|
+
dias_transcurridos = database.Column(database.Integer, nullable=False)
|
|
1404
|
+
|
|
1405
|
+
# Interest calculation
|
|
1406
|
+
saldo_base = database.Column(
|
|
1407
|
+
database.Numeric(14, 2), nullable=False, default=Decimal("0.00")
|
|
1408
|
+
) # Balance used for interest calculation
|
|
1409
|
+
tasa_aplicada = database.Column(
|
|
1410
|
+
database.Numeric(5, 4), nullable=False, default=Decimal("0.0000")
|
|
1411
|
+
) # Interest rate applied
|
|
1412
|
+
interes_calculado = database.Column(
|
|
1413
|
+
database.Numeric(14, 2), nullable=False, default=Decimal("0.00")
|
|
1414
|
+
) # Interest amount calculated
|
|
1415
|
+
|
|
1416
|
+
# Balance tracking
|
|
1417
|
+
saldo_anterior = database.Column(
|
|
1418
|
+
database.Numeric(14, 2), nullable=False, default=Decimal("0.00")
|
|
1419
|
+
) # Balance before interest
|
|
1420
|
+
saldo_posterior = database.Column(
|
|
1421
|
+
database.Numeric(14, 2), nullable=False, default=Decimal("0.00")
|
|
1422
|
+
) # Balance after interest
|
|
1423
|
+
|
|
1424
|
+
observaciones = database.Column(database.String(500), nullable=True)
|
|
1425
|
+
|
|
1426
|
+
adelanto = database.relationship("Adelanto", back_populates="intereses")
|
|
1427
|
+
nomina = database.relationship("Nomina")
|
|
1428
|
+
|
|
1429
|
+
|
|
1430
|
+
# Definición de campos personalizados para empleados
|
|
1431
|
+
class CampoPersonalizado(database.Model, BaseTabla):
|
|
1432
|
+
"""Custom field definition for employee records.
|
|
1433
|
+
|
|
1434
|
+
Stores the definition of custom fields that can be added to employee records.
|
|
1435
|
+
The actual values are stored in the `datos_adicionales` JSON column of Empleado.
|
|
1436
|
+
|
|
1437
|
+
Field types:
|
|
1438
|
+
- texto: String/text field
|
|
1439
|
+
- entero: Integer field
|
|
1440
|
+
- decimal: Decimal/float field
|
|
1441
|
+
- booleano: Boolean (true/false) field
|
|
1442
|
+
"""
|
|
1443
|
+
|
|
1444
|
+
__tablename__ = "campo_personalizado"
|
|
1445
|
+
__table_args__ = (database.UniqueConstraint("nombre_campo", name="uq_campo_nombre"),)
|
|
1446
|
+
|
|
1447
|
+
nombre_campo = database.Column(database.String(100), unique=True, nullable=False, index=True)
|
|
1448
|
+
etiqueta = database.Column(database.String(150), nullable=False)
|
|
1449
|
+
tipo_dato = database.Column(
|
|
1450
|
+
database.String(20), nullable=False, default="texto"
|
|
1451
|
+
) # texto, entero, decimal, booleano
|
|
1452
|
+
descripcion = database.Column(database.String(255), nullable=True)
|
|
1453
|
+
orden = database.Column(database.Integer, nullable=False, default=0)
|
|
1454
|
+
activo = database.Column(database.Boolean(), default=True, nullable=False)
|
|
1455
|
+
|
|
1456
|
+
|
|
1457
|
+
# Reglas de cálculo (impuestos, percepciones, deducciones complejas)
|
|
1458
|
+
class ReglaCalculo(database.Model, BaseTabla):
|
|
1459
|
+
"""Calculation rules for taxes, perceptions, and deductions.
|
|
1460
|
+
|
|
1461
|
+
Stores the complete JSON schema for calculating complex rules like
|
|
1462
|
+
income tax (IR), social security deductions, etc. The schema defines
|
|
1463
|
+
variables, conditions, formulas, and tax lookup tables.
|
|
1464
|
+
|
|
1465
|
+
This allows country-agnostic configuration of tax rules that can be
|
|
1466
|
+
versioned and applied based on effective dates.
|
|
1467
|
+
"""
|
|
1468
|
+
|
|
1469
|
+
__tablename__ = "regla_calculo"
|
|
1470
|
+
__table_args__ = (database.UniqueConstraint("codigo", "version", name="uq_regla_codigo_version"),)
|
|
1471
|
+
|
|
1472
|
+
codigo = database.Column(
|
|
1473
|
+
database.String(50), nullable=False, index=True
|
|
1474
|
+
) # e.g., 'INCOME_TAX_001', 'SOCIAL_SEC_001'
|
|
1475
|
+
nombre = database.Column(database.String(150), nullable=False)
|
|
1476
|
+
descripcion = database.Column(database.Text, nullable=True)
|
|
1477
|
+
jurisdiccion = database.Column(database.String(100), nullable=True) # e.g., 'Country A', 'Region B'
|
|
1478
|
+
|
|
1479
|
+
# Reference currency for the tax rule calculations.
|
|
1480
|
+
# The rule is currency-agnostic - the actual payroll currency is defined
|
|
1481
|
+
# in TipoPlanilla. When the payroll currency differs from the reference
|
|
1482
|
+
# currency, exchange rates are applied during calculation.
|
|
1483
|
+
# Example: A tax rule may use local currency as reference, but payroll can be in another currency.
|
|
1484
|
+
moneda_referencia = database.Column(
|
|
1485
|
+
database.String(10), nullable=True
|
|
1486
|
+
) # e.g., 'USD', 'EUR' - reference currency for rule calculations
|
|
1487
|
+
|
|
1488
|
+
version = database.Column(database.String(20), nullable=False, default="1.0.0") # Semantic versioning
|
|
1489
|
+
|
|
1490
|
+
# Type of rule: 'impuesto', 'deduccion', 'percepcion', 'prestacion'
|
|
1491
|
+
tipo_regla = database.Column(database.String(30), nullable=False, default="impuesto")
|
|
1492
|
+
|
|
1493
|
+
# The complete JSON schema defining the calculation logic
|
|
1494
|
+
# Structure includes: meta, inputs, steps, tax_tables, output
|
|
1495
|
+
esquema_json = database.Column(MutableDict.as_mutable(OrjsonType), nullable=False, default=dict)
|
|
1496
|
+
|
|
1497
|
+
# Validity period
|
|
1498
|
+
vigente_desde = database.Column(database.Date, nullable=False)
|
|
1499
|
+
vigente_hasta = database.Column(database.Date, nullable=True)
|
|
1500
|
+
|
|
1501
|
+
activo = database.Column(database.Boolean(), default=True, nullable=False)
|
|
1502
|
+
|
|
1503
|
+
# Optional relationship to specific deduction/perception/benefit
|
|
1504
|
+
deduccion_id = database.Column(database.String(26), database.ForeignKey("deduccion.id"), nullable=True)
|
|
1505
|
+
percepcion_id = database.Column(database.String(26), database.ForeignKey("percepcion.id"), nullable=True)
|
|
1506
|
+
prestacion_id = database.Column(database.String(26), database.ForeignKey("prestacion.id"), nullable=True)
|
|
1507
|
+
|
|
1508
|
+
# Audit and governance fields
|
|
1509
|
+
estado_aprobacion = database.Column(database.String(20), nullable=False, default="borrador", index=True)
|
|
1510
|
+
aprobado_por = database.Column(database.String(150), nullable=True)
|
|
1511
|
+
aprobado_en = database.Column(database.DateTime, nullable=True)
|
|
1512
|
+
creado_por_plugin = database.Column(database.Boolean(), default=False, nullable=False)
|
|
1513
|
+
plugin_source = database.Column(database.String(200), nullable=True)
|
|
1514
|
+
|
|
1515
|
+
# Relationship to planillas that use this rule
|
|
1516
|
+
planillas = database.relationship(
|
|
1517
|
+
"PlanillaReglaCalculo",
|
|
1518
|
+
back_populates="regla_calculo",
|
|
1519
|
+
)
|
|
1520
|
+
audit_logs = database.relationship(
|
|
1521
|
+
"ReglaCalculoAuditLog",
|
|
1522
|
+
back_populates="regla_calculo",
|
|
1523
|
+
cascade="all, delete-orphan",
|
|
1524
|
+
)
|
|
1525
|
+
|
|
1526
|
+
|
|
1527
|
+
# Acumulados anuales por empleado (para cálculos de impuestos progresivos)
|
|
1528
|
+
class AcumuladoAnual(database.Model, BaseTabla):
|
|
1529
|
+
"""Annual accumulated values per employee per payroll type per company.
|
|
1530
|
+
|
|
1531
|
+
Stores running totals of salary, deductions, and taxes for each employee
|
|
1532
|
+
per fiscal year, payroll type, and company. This is essential for progressive tax
|
|
1533
|
+
calculations which require annual accumulated values.
|
|
1534
|
+
|
|
1535
|
+
IMPORTANT: Accumulated values are tracked per company (empresa_id) to support
|
|
1536
|
+
employees who change companies mid-year. Each company maintains separate
|
|
1537
|
+
accumulated values since they are distinct legal entities. For tax calculations
|
|
1538
|
+
that require total annual income across all employers, the initial accumulated
|
|
1539
|
+
values in the Empleado model represent the sum from all previous employers.
|
|
1540
|
+
|
|
1541
|
+
The fiscal year period is defined in the TipoPlanilla (payroll type) to
|
|
1542
|
+
support different fiscal periods (not just Jan-Dec).
|
|
1543
|
+
|
|
1544
|
+
Updated after each payroll run to maintain accurate year-to-date totals.
|
|
1545
|
+
"""
|
|
1546
|
+
|
|
1547
|
+
__tablename__ = "acumulado_anual"
|
|
1548
|
+
__table_args__ = (
|
|
1549
|
+
database.UniqueConstraint(
|
|
1550
|
+
"empleado_id",
|
|
1551
|
+
"tipo_planilla_id",
|
|
1552
|
+
"empresa_id",
|
|
1553
|
+
"periodo_fiscal_inicio",
|
|
1554
|
+
name="uq_acumulado_empleado_tipo_empresa_periodo",
|
|
1555
|
+
),
|
|
1556
|
+
)
|
|
1557
|
+
|
|
1558
|
+
empleado_id = database.Column(
|
|
1559
|
+
database.String(26),
|
|
1560
|
+
database.ForeignKey("empleado.id"),
|
|
1561
|
+
nullable=False,
|
|
1562
|
+
index=True,
|
|
1563
|
+
)
|
|
1564
|
+
|
|
1565
|
+
# Reference to payroll type (which defines the fiscal period)
|
|
1566
|
+
tipo_planilla_id = database.Column(
|
|
1567
|
+
database.String(26),
|
|
1568
|
+
database.ForeignKey("tipo_planilla.id"),
|
|
1569
|
+
nullable=False,
|
|
1570
|
+
index=True,
|
|
1571
|
+
)
|
|
1572
|
+
|
|
1573
|
+
# Company association - critical for employees who change companies
|
|
1574
|
+
# Each company tracks accumulated values separately as they are distinct legal entities
|
|
1575
|
+
empresa_id = database.Column(
|
|
1576
|
+
database.String(26),
|
|
1577
|
+
database.ForeignKey("empresa.id"),
|
|
1578
|
+
nullable=False,
|
|
1579
|
+
index=True,
|
|
1580
|
+
)
|
|
1581
|
+
|
|
1582
|
+
# Fiscal period start date (calculated from TipoPlanilla settings)
|
|
1583
|
+
# This allows tracking accumulated values per fiscal year
|
|
1584
|
+
periodo_fiscal_inicio = database.Column(database.Date, nullable=False, index=True)
|
|
1585
|
+
periodo_fiscal_fin = database.Column(database.Date, nullable=False)
|
|
1586
|
+
|
|
1587
|
+
# Accumulated salary values
|
|
1588
|
+
salario_bruto_acumulado = database.Column(database.Numeric(14, 2), nullable=False, default=Decimal("0.00"))
|
|
1589
|
+
salario_gravable_acumulado = database.Column(database.Numeric(14, 2), nullable=False, default=Decimal("0.00"))
|
|
1590
|
+
|
|
1591
|
+
# Accumulated deductions (before tax)
|
|
1592
|
+
deducciones_antes_impuesto_acumulado = database.Column(
|
|
1593
|
+
database.Numeric(14, 2), nullable=False, default=Decimal("0.00")
|
|
1594
|
+
)
|
|
1595
|
+
|
|
1596
|
+
# Accumulated taxes
|
|
1597
|
+
impuesto_retenido_acumulado = database.Column(database.Numeric(14, 2), nullable=False, default=Decimal("0.00"))
|
|
1598
|
+
|
|
1599
|
+
# Number of payrolls processed
|
|
1600
|
+
periodos_procesados = database.Column(database.Integer, nullable=False, default=0)
|
|
1601
|
+
|
|
1602
|
+
# Last processed period
|
|
1603
|
+
ultimo_periodo_procesado = database.Column(database.Date, nullable=True)
|
|
1604
|
+
|
|
1605
|
+
# Monthly accumulated salary (for biweekly/weekly payrolls)
|
|
1606
|
+
# This tracks the accumulated salary in the current calendar month
|
|
1607
|
+
# Essential for calculations that require month-to-date totals
|
|
1608
|
+
salario_acumulado_mes = database.Column(database.Numeric(14, 2), nullable=False, default=Decimal("0.00"))
|
|
1609
|
+
mes_actual = database.Column(database.Integer, nullable=True) # Current month (1-12) for tracking monthly resets
|
|
1610
|
+
|
|
1611
|
+
# Additional accumulated data (JSON for flexibility)
|
|
1612
|
+
# Can store: inss_acumulado, otras_deducciones_acumuladas, percepciones_acumuladas, etc.
|
|
1613
|
+
datos_adicionales = database.Column(MutableDict.as_mutable(OrjsonType), nullable=True, default=dict)
|
|
1614
|
+
|
|
1615
|
+
empleado = database.relationship("Empleado", backref="acumulados_anuales")
|
|
1616
|
+
tipo_planilla = database.relationship("TipoPlanilla", back_populates="acumulados")
|
|
1617
|
+
empresa = database.relationship("Empresa")
|
|
1618
|
+
|
|
1619
|
+
def reset_mes_acumulado_if_needed(self, periodo_fin: date) -> None:
|
|
1620
|
+
"""Reset monthly accumulated salary if entering a new month.
|
|
1621
|
+
|
|
1622
|
+
Args:
|
|
1623
|
+
periodo_fin: End date of the current payroll period
|
|
1624
|
+
"""
|
|
1625
|
+
if self.mes_actual != periodo_fin.month:
|
|
1626
|
+
self.salario_acumulado_mes = Decimal("0.00")
|
|
1627
|
+
self.mes_actual = periodo_fin.month
|
|
1628
|
+
|
|
1629
|
+
|
|
1630
|
+
# Global configuration settings
|
|
1631
|
+
class ConfiguracionGlobal(database.Model, BaseTabla):
|
|
1632
|
+
"""Global configuration settings for the application.
|
|
1633
|
+
|
|
1634
|
+
Stores system-wide settings like default language, theme preferences, etc.
|
|
1635
|
+
Only one record should exist in this table (singleton pattern).
|
|
1636
|
+
"""
|
|
1637
|
+
|
|
1638
|
+
__tablename__ = "configuracion_global"
|
|
1639
|
+
|
|
1640
|
+
# Language setting: 'en' for English, 'es' for Spanish
|
|
1641
|
+
idioma = database.Column(database.String(10), nullable=False, default="en")
|
|
1642
|
+
|
|
1643
|
+
# Additional global settings can be stored as JSON
|
|
1644
|
+
configuracion_adicional = database.Column(MutableDict.as_mutable(OrjsonType), nullable=True, default=dict)
|
|
1645
|
+
|
|
1646
|
+
|
|
1647
|
+
# Configuration for calculation parameters
|
|
1648
|
+
class ConfiguracionCalculos(database.Model, BaseTabla):
|
|
1649
|
+
"""Configuration table for calculation parameters.
|
|
1650
|
+
|
|
1651
|
+
Stores configurable values for payroll calculations that were previously hardcoded.
|
|
1652
|
+
Supports company-specific and country-specific configurations.
|
|
1653
|
+
"""
|
|
1654
|
+
|
|
1655
|
+
__tablename__ = "config_calculos"
|
|
1656
|
+
__table_args__ = (database.UniqueConstraint("empresa_id", "pais_id", name="uq_config_empresa_pais"),)
|
|
1657
|
+
|
|
1658
|
+
# Optional relationships - can be None for global defaults
|
|
1659
|
+
empresa_id = database.Column(database.String(26), database.ForeignKey("empresa.id"), nullable=True, index=True)
|
|
1660
|
+
pais_id = database.Column(database.String(26), nullable=True, index=True) # Future: ForeignKey("pais.id")
|
|
1661
|
+
|
|
1662
|
+
# Días base para nómina
|
|
1663
|
+
dias_mes_nomina = database.Column(database.Integer, nullable=False, default=30) # 28, 29, 30, 31
|
|
1664
|
+
dias_anio_nomina = database.Column(database.Integer, nullable=False, default=365) # 360, 365, 366
|
|
1665
|
+
horas_jornada_diaria = database.Column(database.Numeric(4, 2), nullable=False, default=Decimal("8.00"))
|
|
1666
|
+
|
|
1667
|
+
# Vacaciones
|
|
1668
|
+
dias_mes_vacaciones = database.Column(database.Integer, nullable=False, default=30)
|
|
1669
|
+
dias_anio_vacaciones = database.Column(database.Integer, nullable=False, default=365)
|
|
1670
|
+
considerar_bisiesto_vacaciones = database.Column(database.Boolean, nullable=False, default=True)
|
|
1671
|
+
|
|
1672
|
+
# Intereses
|
|
1673
|
+
dias_anio_financiero = database.Column(
|
|
1674
|
+
database.Integer, nullable=False, default=365
|
|
1675
|
+
) # 360 o 365 (default 365 for compatibility)
|
|
1676
|
+
meses_anio_financiero = database.Column(database.Integer, nullable=False, default=12)
|
|
1677
|
+
|
|
1678
|
+
# Quincenas
|
|
1679
|
+
dias_quincena = database.Column(database.Integer, nullable=False, default=15) # 14 o 15
|
|
1680
|
+
|
|
1681
|
+
# Liquidaciones
|
|
1682
|
+
liquidacion_modo_dias = database.Column(database.String(20), nullable=False, default="calendario")
|
|
1683
|
+
liquidacion_factor_calendario = database.Column(database.Integer, nullable=False, default=30)
|
|
1684
|
+
liquidacion_factor_laboral = database.Column(database.Integer, nullable=False, default=28)
|
|
1685
|
+
|
|
1686
|
+
# Antigüedad
|
|
1687
|
+
dias_mes_antiguedad = database.Column(database.Integer, nullable=False, default=30)
|
|
1688
|
+
dias_anio_antiguedad = database.Column(database.Integer, nullable=False, default=365)
|
|
1689
|
+
|
|
1690
|
+
activo = database.Column(database.Boolean, nullable=False, default=True)
|
|
1691
|
+
|
|
1692
|
+
# Relationships
|
|
1693
|
+
empresa = database.relationship("Empresa", backref="configuraciones_calculos")
|
|
1694
|
+
|
|
1695
|
+
|
|
1696
|
+
# Prestaciones Acumuladas - Accumulated Benefits Tracking (Transactional)
|
|
1697
|
+
class PrestacionAcumulada(database.Model, BaseTabla):
|
|
1698
|
+
"""Transactional log of accumulated employee benefits over time.
|
|
1699
|
+
|
|
1700
|
+
IMPORTANT: This is a transactional (append-only) table for audit purposes.
|
|
1701
|
+
Each record represents a single transaction that affects the benefit balance.
|
|
1702
|
+
Never update or delete records - always insert new transactions.
|
|
1703
|
+
|
|
1704
|
+
This table maintains a complete audit trail of each benefit (prestacion) for each employee,
|
|
1705
|
+
independent of which payroll (planilla) they are assigned to. This is critical because
|
|
1706
|
+
employees can change payrolls while their benefit accumulations continue.
|
|
1707
|
+
|
|
1708
|
+
Transaction types:
|
|
1709
|
+
- saldo_inicial: Initial balance loading
|
|
1710
|
+
- adicion: Addition (increase) - typically from payroll provisions
|
|
1711
|
+
- disminucion: Decrease (reduction) - typically from settlements/payments
|
|
1712
|
+
- ajuste: Adjustment (can be positive or negative) - manual corrections
|
|
1713
|
+
|
|
1714
|
+
The accumulation respects the tipo_acumulacion setting:
|
|
1715
|
+
- mensual: Settled and reset monthly (e.g., INSS, INATEC)
|
|
1716
|
+
- anual: Accumulated annually based on payroll configuration
|
|
1717
|
+
- vida_laboral: Accumulated over the employee's entire tenure (e.g., severance)
|
|
1718
|
+
"""
|
|
1719
|
+
|
|
1720
|
+
__tablename__ = "prestacion_acumulada"
|
|
1721
|
+
__table_args__ = (
|
|
1722
|
+
database.Index("ix_prestacion_acum_empleado_prestacion", "empleado_id", "prestacion_id"),
|
|
1723
|
+
database.Index("ix_prestacion_acum_fecha", "fecha_transaccion"),
|
|
1724
|
+
database.Index("ix_prestacion_acum_periodo", "anio", "mes"),
|
|
1725
|
+
)
|
|
1726
|
+
|
|
1727
|
+
empleado_id = database.Column(
|
|
1728
|
+
database.String(26),
|
|
1729
|
+
database.ForeignKey("empleado.id"),
|
|
1730
|
+
nullable=False,
|
|
1731
|
+
index=True,
|
|
1732
|
+
)
|
|
1733
|
+
prestacion_id = database.Column(
|
|
1734
|
+
database.String(26),
|
|
1735
|
+
database.ForeignKey("prestacion.id"),
|
|
1736
|
+
nullable=False,
|
|
1737
|
+
index=True,
|
|
1738
|
+
)
|
|
1739
|
+
|
|
1740
|
+
# Transaction details
|
|
1741
|
+
fecha_transaccion = database.Column(database.Date, nullable=False, default=date.today, index=True)
|
|
1742
|
+
tipo_transaccion = database.Column(
|
|
1743
|
+
database.String(20), nullable=False, index=True
|
|
1744
|
+
) # saldo_inicial | adicion | disminucion | ajuste
|
|
1745
|
+
|
|
1746
|
+
# Period tracking (for reporting and grouping)
|
|
1747
|
+
anio = database.Column(database.Integer, nullable=False, index=True)
|
|
1748
|
+
mes = database.Column(database.Integer, nullable=False) # 1-12
|
|
1749
|
+
|
|
1750
|
+
# Currency tracking
|
|
1751
|
+
moneda_id = database.Column(database.String(26), database.ForeignKey("moneda.id"), nullable=False)
|
|
1752
|
+
|
|
1753
|
+
# Transaction amounts
|
|
1754
|
+
# For audit clarity, we store the transaction amount and running balance separately
|
|
1755
|
+
monto_transaccion = database.Column(
|
|
1756
|
+
database.Numeric(14, 2), nullable=False, default=Decimal("0.00")
|
|
1757
|
+
) # Can be positive (addition) or negative (deduction)
|
|
1758
|
+
saldo_anterior = database.Column(
|
|
1759
|
+
database.Numeric(14, 2), nullable=False, default=Decimal("0.00")
|
|
1760
|
+
) # Balance before this transaction
|
|
1761
|
+
saldo_nuevo = database.Column(
|
|
1762
|
+
database.Numeric(14, 2), nullable=False, default=Decimal("0.00")
|
|
1763
|
+
) # Balance after this transaction
|
|
1764
|
+
|
|
1765
|
+
# Reference to source document that created this transaction
|
|
1766
|
+
nomina_id = database.Column(database.String(26), database.ForeignKey("nomina.id"), nullable=True)
|
|
1767
|
+
carga_inicial_id = database.Column(
|
|
1768
|
+
database.String(26), database.ForeignKey("carga_inicial_prestacion.id"), nullable=True
|
|
1769
|
+
)
|
|
1770
|
+
|
|
1771
|
+
# Company association - for employees who change companies
|
|
1772
|
+
# Balances accumulate per company as they are distinct legal entities
|
|
1773
|
+
empresa_id = database.Column(
|
|
1774
|
+
database.String(26),
|
|
1775
|
+
database.ForeignKey("empresa.id"),
|
|
1776
|
+
nullable=True,
|
|
1777
|
+
index=True,
|
|
1778
|
+
)
|
|
1779
|
+
|
|
1780
|
+
# Reversal tracking (if this transaction reverses another)
|
|
1781
|
+
transaccion_reversada_id = database.Column(database.String(26), nullable=True) # FK to another transaction
|
|
1782
|
+
|
|
1783
|
+
# Audit trail
|
|
1784
|
+
observaciones = database.Column(database.String(500), nullable=True)
|
|
1785
|
+
procesado_por = database.Column(database.String(150), nullable=True)
|
|
1786
|
+
|
|
1787
|
+
# Relationships
|
|
1788
|
+
empleado = database.relationship("Empleado")
|
|
1789
|
+
prestacion = database.relationship("Prestacion", back_populates="prestaciones_acumuladas")
|
|
1790
|
+
moneda = database.relationship("Moneda")
|
|
1791
|
+
nomina = database.relationship("Nomina")
|
|
1792
|
+
carga_inicial = database.relationship("CargaInicialPrestacion", back_populates="transacciones")
|
|
1793
|
+
empresa = database.relationship("Empresa")
|
|
1794
|
+
|
|
1795
|
+
|
|
1796
|
+
# Carga Inicial de Prestaciones - Initial Benefit Balance Loading
|
|
1797
|
+
class CargaInicialPrestacion(database.Model, BaseTabla):
|
|
1798
|
+
"""Initial benefit balance loading for system implementation.
|
|
1799
|
+
|
|
1800
|
+
When implementing the system mid-year or mid-employment, this table allows
|
|
1801
|
+
loading existing accumulated balances for employees. Once applied, these
|
|
1802
|
+
balances are transferred to the PrestacionAcumulada table.
|
|
1803
|
+
|
|
1804
|
+
Workflow:
|
|
1805
|
+
1. Create entry in 'borrador' (draft) status
|
|
1806
|
+
2. Review and validate the data
|
|
1807
|
+
3. Change status to 'aplicado' (applied)
|
|
1808
|
+
4. System automatically creates corresponding PrestacionAcumulada record
|
|
1809
|
+
"""
|
|
1810
|
+
|
|
1811
|
+
__tablename__ = "carga_inicial_prestacion"
|
|
1812
|
+
__table_args__ = (
|
|
1813
|
+
database.UniqueConstraint(
|
|
1814
|
+
"empleado_id",
|
|
1815
|
+
"prestacion_id",
|
|
1816
|
+
"anio_corte",
|
|
1817
|
+
"mes_corte",
|
|
1818
|
+
name="uq_carga_inicial_emp_prest_periodo",
|
|
1819
|
+
),
|
|
1820
|
+
)
|
|
1821
|
+
|
|
1822
|
+
empleado_id = database.Column(
|
|
1823
|
+
database.String(26),
|
|
1824
|
+
database.ForeignKey("empleado.id"),
|
|
1825
|
+
nullable=False,
|
|
1826
|
+
index=True,
|
|
1827
|
+
)
|
|
1828
|
+
prestacion_id = database.Column(
|
|
1829
|
+
database.String(26),
|
|
1830
|
+
database.ForeignKey("prestacion.id"),
|
|
1831
|
+
nullable=False,
|
|
1832
|
+
index=True,
|
|
1833
|
+
)
|
|
1834
|
+
|
|
1835
|
+
# Cutoff period (when this balance was calculated)
|
|
1836
|
+
anio_corte = database.Column(database.Integer, nullable=False)
|
|
1837
|
+
mes_corte = database.Column(database.Integer, nullable=False) # 1-12
|
|
1838
|
+
|
|
1839
|
+
# Currency and exchange rate
|
|
1840
|
+
moneda_id = database.Column(database.String(26), database.ForeignKey("moneda.id"), nullable=False)
|
|
1841
|
+
saldo_acumulado = database.Column(database.Numeric(14, 2), nullable=False, default=Decimal("0.00"))
|
|
1842
|
+
tipo_cambio = database.Column(database.Numeric(24, 10), nullable=True, default=Decimal("1.0000000000"))
|
|
1843
|
+
saldo_convertido = database.Column(database.Numeric(14, 2), nullable=False, default=Decimal("0.00"))
|
|
1844
|
+
|
|
1845
|
+
# Status: borrador (draft) or aplicado (applied)
|
|
1846
|
+
estado = database.Column(database.String(20), nullable=False, default="borrador") # borrador | aplicado
|
|
1847
|
+
|
|
1848
|
+
# Application tracking
|
|
1849
|
+
fecha_aplicacion = database.Column(database.DateTime, nullable=True)
|
|
1850
|
+
aplicado_por = database.Column(database.String(150), nullable=True)
|
|
1851
|
+
|
|
1852
|
+
# Notes
|
|
1853
|
+
observaciones = database.Column(database.String(500), nullable=True)
|
|
1854
|
+
|
|
1855
|
+
# Relationships
|
|
1856
|
+
empleado = database.relationship("Empleado")
|
|
1857
|
+
prestacion = database.relationship("Prestacion", back_populates="cargas_iniciales")
|
|
1858
|
+
moneda = database.relationship("Moneda")
|
|
1859
|
+
transacciones = database.relationship("PrestacionAcumulada", back_populates="carga_inicial")
|
|
1860
|
+
|
|
1861
|
+
|
|
1862
|
+
# ============================================================================
|
|
1863
|
+
# Vacation Module - Robust, Flexible, Country-Agnostic
|
|
1864
|
+
# ============================================================================
|
|
1865
|
+
|
|
1866
|
+
|
|
1867
|
+
class VacationPolicy(database.Model, BaseTabla):
|
|
1868
|
+
"""Vacation policy configuration (payroll/company-specific).
|
|
1869
|
+
|
|
1870
|
+
This model represents the policy engine for vacation management.
|
|
1871
|
+
Policies are configurable and define how vacation is accrued, used, and expired.
|
|
1872
|
+
Completely agnostic to country-specific laws - all rules are configuration-based.
|
|
1873
|
+
|
|
1874
|
+
Design principles:
|
|
1875
|
+
- Policies are declarative, not hardcoded
|
|
1876
|
+
- Support for all Americas (LATAM, USA, Canada)
|
|
1877
|
+
- Flexible enough to handle diverse legal requirements
|
|
1878
|
+
- Associated with Planillas (payrolls) to support multiple countries in consolidated companies
|
|
1879
|
+
"""
|
|
1880
|
+
|
|
1881
|
+
__tablename__ = "vacation_policy"
|
|
1882
|
+
__table_args__ = (
|
|
1883
|
+
database.UniqueConstraint("codigo", name="uq_vacation_policy_codigo"),
|
|
1884
|
+
database.Index("ix_vacation_policy_empresa", "empresa_id"),
|
|
1885
|
+
database.Index("ix_vacation_policy_planilla", "planilla_id"),
|
|
1886
|
+
)
|
|
1887
|
+
|
|
1888
|
+
# Policy identification
|
|
1889
|
+
codigo = database.Column(database.String(50), unique=True, nullable=False, index=True)
|
|
1890
|
+
nombre = database.Column(database.String(200), nullable=False)
|
|
1891
|
+
descripcion = database.Column(database.String(500), nullable=True)
|
|
1892
|
+
|
|
1893
|
+
# Payroll association (primary) - policies are tied to specific payrolls
|
|
1894
|
+
# This allows different vacation rules for different payrolls in consolidated companies
|
|
1895
|
+
planilla_id = database.Column(database.String(26), database.ForeignKey("planilla.id"), nullable=True, index=True)
|
|
1896
|
+
planilla = database.relationship("Planilla", backref="vacation_policies")
|
|
1897
|
+
|
|
1898
|
+
# Company association (secondary, optional) - for policies that apply to entire company
|
|
1899
|
+
empresa_id = database.Column(database.String(26), database.ForeignKey("empresa.id"), nullable=True, index=True)
|
|
1900
|
+
empresa = database.relationship("Empresa")
|
|
1901
|
+
|
|
1902
|
+
# Status
|
|
1903
|
+
activo = database.Column(database.Boolean(), default=True, nullable=False)
|
|
1904
|
+
|
|
1905
|
+
# ---- Accrual Configuration ----
|
|
1906
|
+
# How vacation is earned
|
|
1907
|
+
accrual_method = database.Column(
|
|
1908
|
+
database.String(20), nullable=False, default="periodic"
|
|
1909
|
+
) # periodic | proportional | seniority
|
|
1910
|
+
|
|
1911
|
+
# Amount earned per period (for periodic method)
|
|
1912
|
+
accrual_rate = database.Column(database.Numeric(10, 4), nullable=False, default=Decimal("0.0000"))
|
|
1913
|
+
|
|
1914
|
+
# How often accrual happens
|
|
1915
|
+
accrual_frequency = database.Column(
|
|
1916
|
+
database.String(20), nullable=False, default="monthly"
|
|
1917
|
+
) # monthly | biweekly | annual
|
|
1918
|
+
|
|
1919
|
+
# Basis for proportional calculation
|
|
1920
|
+
accrual_basis = database.Column(
|
|
1921
|
+
database.String(20), nullable=True
|
|
1922
|
+
) # days_worked | hours_worked (used when accrual_method=proportional)
|
|
1923
|
+
|
|
1924
|
+
# Minimum service days before accrual begins
|
|
1925
|
+
min_service_days = database.Column(database.Integer, nullable=False, default=0)
|
|
1926
|
+
|
|
1927
|
+
# Seniority tiers (JSON format for flexibility)
|
|
1928
|
+
# Example: [{"years": 0, "rate": 10}, {"years": 2, "rate": 15}, {"years": 5, "rate": 20}]
|
|
1929
|
+
seniority_tiers = database.Column(JSON, nullable=True)
|
|
1930
|
+
|
|
1931
|
+
# ---- Balance Limits ----
|
|
1932
|
+
# Maximum balance allowed
|
|
1933
|
+
max_balance = database.Column(database.Numeric(10, 4), nullable=True)
|
|
1934
|
+
|
|
1935
|
+
# Maximum that can be carried over to next period
|
|
1936
|
+
carryover_limit = database.Column(database.Numeric(10, 4), nullable=True)
|
|
1937
|
+
|
|
1938
|
+
# Allow negative balance (advance vacation)
|
|
1939
|
+
allow_negative = database.Column(database.Boolean(), default=False, nullable=False)
|
|
1940
|
+
|
|
1941
|
+
# ---- Expiration Rules ----
|
|
1942
|
+
# When does unused vacation expire
|
|
1943
|
+
expiration_rule = database.Column(
|
|
1944
|
+
database.String(20), nullable=False, default="never"
|
|
1945
|
+
) # never | fiscal_year_end | anniversary | custom_date
|
|
1946
|
+
|
|
1947
|
+
# Months after accrual when vacation expires (used with fiscal_year_end, anniversary)
|
|
1948
|
+
expiration_months = database.Column(database.Integer, nullable=True)
|
|
1949
|
+
|
|
1950
|
+
# Custom expiration date (used with custom_date rule)
|
|
1951
|
+
expiration_date = database.Column(database.Date, nullable=True)
|
|
1952
|
+
|
|
1953
|
+
# ---- Termination Rules ----
|
|
1954
|
+
# Pay out unused vacation on termination
|
|
1955
|
+
payout_on_termination = database.Column(database.Boolean(), default=True, nullable=False)
|
|
1956
|
+
|
|
1957
|
+
# ---- Usage Configuration ----
|
|
1958
|
+
# Unit type for vacation balances
|
|
1959
|
+
unit_type = database.Column(database.String(10), nullable=False, default="days") # days | hours
|
|
1960
|
+
|
|
1961
|
+
# Count weekends when calculating vacation days
|
|
1962
|
+
count_weekends = database.Column(database.Boolean(), default=True, nullable=False)
|
|
1963
|
+
|
|
1964
|
+
# Count holidays when calculating vacation days
|
|
1965
|
+
count_holidays = database.Column(database.Boolean(), default=True, nullable=False)
|
|
1966
|
+
|
|
1967
|
+
# Allow partial days/hours
|
|
1968
|
+
partial_units_allowed = database.Column(database.Boolean(), default=False, nullable=False)
|
|
1969
|
+
|
|
1970
|
+
# Rounding rule (for partial units): up | down | nearest
|
|
1971
|
+
rounding_rule = database.Column(database.String(10), nullable=True, default="nearest")
|
|
1972
|
+
|
|
1973
|
+
# Continue accruing during vacation leave
|
|
1974
|
+
accrue_during_leave = database.Column(database.Boolean(), default=True, nullable=False)
|
|
1975
|
+
|
|
1976
|
+
# Additional configuration (JSON for future extensibility)
|
|
1977
|
+
configuracion_adicional = database.Column(JSON, nullable=True)
|
|
1978
|
+
|
|
1979
|
+
# Relationships
|
|
1980
|
+
accounts = database.relationship("VacationAccount", back_populates="policy")
|
|
1981
|
+
|
|
1982
|
+
|
|
1983
|
+
class VacationAccount(database.Model, BaseTabla):
|
|
1984
|
+
"""Vacation account per employee.
|
|
1985
|
+
|
|
1986
|
+
Represents the vacation balance for a single employee under a specific policy.
|
|
1987
|
+
This is the control record that tracks current balance and last accrual.
|
|
1988
|
+
|
|
1989
|
+
IMPORTANT: Never update balance directly. All changes must go through VacationLedger.
|
|
1990
|
+
"""
|
|
1991
|
+
|
|
1992
|
+
__tablename__ = "vacation_account"
|
|
1993
|
+
__table_args__ = (
|
|
1994
|
+
database.UniqueConstraint("empleado_id", "policy_id", name="uq_vacation_account_emp_policy"),
|
|
1995
|
+
database.Index("ix_vacation_account_empleado", "empleado_id"),
|
|
1996
|
+
database.Index("ix_vacation_account_policy", "policy_id"),
|
|
1997
|
+
)
|
|
1998
|
+
|
|
1999
|
+
# Employee and policy association
|
|
2000
|
+
empleado_id = database.Column(database.String(26), database.ForeignKey("empleado.id"), nullable=False, index=True)
|
|
2001
|
+
empleado = database.relationship("Empleado")
|
|
2002
|
+
|
|
2003
|
+
policy_id = database.Column(
|
|
2004
|
+
database.String(26), database.ForeignKey("vacation_policy.id"), nullable=False, index=True
|
|
2005
|
+
)
|
|
2006
|
+
policy = database.relationship("VacationPolicy", back_populates="accounts")
|
|
2007
|
+
|
|
2008
|
+
# Current balance (calculated from ledger)
|
|
2009
|
+
current_balance = database.Column(database.Numeric(10, 4), nullable=False, default=Decimal("0.0000"))
|
|
2010
|
+
|
|
2011
|
+
# Last accrual date (for automated accrual processing)
|
|
2012
|
+
last_accrual_date = database.Column(database.Date, nullable=True)
|
|
2013
|
+
|
|
2014
|
+
# Status
|
|
2015
|
+
activo = database.Column(database.Boolean(), default=True, nullable=False)
|
|
2016
|
+
|
|
2017
|
+
# Relationships
|
|
2018
|
+
ledger_entries = database.relationship("VacationLedger", back_populates="account", order_by="VacationLedger.fecha")
|
|
2019
|
+
|
|
2020
|
+
|
|
2021
|
+
class VacationLedger(database.Model, BaseTabla):
|
|
2022
|
+
"""Immutable ledger of all vacation transactions.
|
|
2023
|
+
|
|
2024
|
+
This is the core of the vacation system - all vacation balance changes are recorded here.
|
|
2025
|
+
The ledger is append-only (immutable) for full audit trail.
|
|
2026
|
+
|
|
2027
|
+
Design principle: Balance = SUM(ledger entries)
|
|
2028
|
+
|
|
2029
|
+
Entry types:
|
|
2030
|
+
- ACCRUAL: Vacation earned
|
|
2031
|
+
- USAGE: Vacation taken
|
|
2032
|
+
- ADJUSTMENT: Manual adjustment
|
|
2033
|
+
- EXPIRATION: Vacation expired
|
|
2034
|
+
- PAYOUT: Vacation paid out (e.g., termination)
|
|
2035
|
+
"""
|
|
2036
|
+
|
|
2037
|
+
__tablename__ = "vacation_ledger"
|
|
2038
|
+
__table_args__ = (
|
|
2039
|
+
database.Index("ix_vacation_ledger_account", "account_id"),
|
|
2040
|
+
database.Index("ix_vacation_ledger_fecha", "fecha"),
|
|
2041
|
+
database.Index("ix_vacation_ledger_type", "entry_type"),
|
|
2042
|
+
database.Index("ix_vacation_ledger_empleado", "empleado_id"),
|
|
2043
|
+
)
|
|
2044
|
+
|
|
2045
|
+
# Account reference
|
|
2046
|
+
account_id = database.Column(database.String(26), database.ForeignKey("vacation_account.id"), nullable=False)
|
|
2047
|
+
account = database.relationship("VacationAccount", back_populates="ledger_entries")
|
|
2048
|
+
|
|
2049
|
+
# Employee reference (for easier querying)
|
|
2050
|
+
empleado_id = database.Column(database.String(26), database.ForeignKey("empleado.id"), nullable=False)
|
|
2051
|
+
empleado = database.relationship("Empleado")
|
|
2052
|
+
|
|
2053
|
+
# Transaction details
|
|
2054
|
+
fecha = database.Column(database.Date, nullable=False, default=date.today)
|
|
2055
|
+
entry_type = database.Column(
|
|
2056
|
+
database.String(20), nullable=False
|
|
2057
|
+
) # accrual | usage | adjustment | expiration | payout
|
|
2058
|
+
|
|
2059
|
+
# Quantity (positive for additions, negative for deductions)
|
|
2060
|
+
quantity = database.Column(database.Numeric(10, 4), nullable=False)
|
|
2061
|
+
|
|
2062
|
+
# Source of the transaction
|
|
2063
|
+
source = database.Column(database.String(50), nullable=False) # system | novelty | termination | manual
|
|
2064
|
+
|
|
2065
|
+
# Reference to source record (e.g., novelty_id, nomina_id)
|
|
2066
|
+
reference_id = database.Column(database.String(26), nullable=True)
|
|
2067
|
+
reference_type = database.Column(database.String(50), nullable=True) # Type of reference (novelty, nomina, etc.)
|
|
2068
|
+
|
|
2069
|
+
# Notes/description
|
|
2070
|
+
observaciones = database.Column(database.String(500), nullable=True)
|
|
2071
|
+
|
|
2072
|
+
# Balance after this transaction (for convenience, though can be calculated)
|
|
2073
|
+
balance_after = database.Column(database.Numeric(10, 4), nullable=True)
|
|
2074
|
+
|
|
2075
|
+
|
|
2076
|
+
class VacationNovelty(database.Model, BaseTabla):
|
|
2077
|
+
"""Vacation leave request/novelty.
|
|
2078
|
+
|
|
2079
|
+
Represents a vacation leave request that affects the vacation balance.
|
|
2080
|
+
When approved, it creates entries in the VacationLedger.
|
|
2081
|
+
|
|
2082
|
+
This integrates the vacation system with the existing novelty workflow.
|
|
2083
|
+
"""
|
|
2084
|
+
|
|
2085
|
+
__tablename__ = "vacation_novelty"
|
|
2086
|
+
__table_args__ = (
|
|
2087
|
+
database.Index("ix_vacation_novelty_empleado", "empleado_id"),
|
|
2088
|
+
database.Index("ix_vacation_novelty_account", "account_id"),
|
|
2089
|
+
database.Index("ix_vacation_novelty_estado", "estado"),
|
|
2090
|
+
database.Index("ix_vacation_novelty_dates", "start_date", "end_date"),
|
|
2091
|
+
)
|
|
2092
|
+
|
|
2093
|
+
# Employee and account
|
|
2094
|
+
empleado_id = database.Column(database.String(26), database.ForeignKey("empleado.id"), nullable=False, index=True)
|
|
2095
|
+
empleado = database.relationship("Empleado")
|
|
2096
|
+
|
|
2097
|
+
account_id = database.Column(
|
|
2098
|
+
database.String(26), database.ForeignKey("vacation_account.id"), nullable=False, index=True
|
|
2099
|
+
)
|
|
2100
|
+
account = database.relationship("VacationAccount")
|
|
2101
|
+
|
|
2102
|
+
# Leave dates
|
|
2103
|
+
start_date = database.Column(database.Date, nullable=False, index=True)
|
|
2104
|
+
end_date = database.Column(database.Date, nullable=False, index=True)
|
|
2105
|
+
|
|
2106
|
+
# Units (days or hours, depending on policy)
|
|
2107
|
+
units = database.Column(database.Numeric(10, 4), nullable=False)
|
|
2108
|
+
|
|
2109
|
+
# Status
|
|
2110
|
+
estado = database.Column(
|
|
2111
|
+
database.String(20), nullable=False, default="pendiente"
|
|
2112
|
+
) # pendiente | aprobado | rechazado | disfrutado
|
|
2113
|
+
|
|
2114
|
+
# Approval tracking
|
|
2115
|
+
fecha_aprobacion = database.Column(database.Date, nullable=True)
|
|
2116
|
+
aprobado_por = database.Column(database.String(150), nullable=True)
|
|
2117
|
+
|
|
2118
|
+
# Link to ledger entry (when processed)
|
|
2119
|
+
ledger_entry_id = database.Column(database.String(26), database.ForeignKey("vacation_ledger.id"), nullable=True)
|
|
2120
|
+
ledger_entry = database.relationship("VacationLedger")
|
|
2121
|
+
|
|
2122
|
+
# Link to payroll novelty (NominaNovedad) when integrated with payroll
|
|
2123
|
+
nomina_novedades = database.relationship(
|
|
2124
|
+
"NominaNovedad", back_populates="vacation_novelty", foreign_keys="NominaNovedad.vacation_novelty_id"
|
|
2125
|
+
)
|
|
2126
|
+
|
|
2127
|
+
# Notes
|
|
2128
|
+
observaciones = database.Column(database.String(500), nullable=True)
|
|
2129
|
+
motivo_rechazo = database.Column(database.String(500), nullable=True)
|
|
2130
|
+
|
|
2131
|
+
|
|
2132
|
+
# ============================================================================
|
|
2133
|
+
# Reports Module
|
|
2134
|
+
# ============================================================================
|
|
2135
|
+
|
|
2136
|
+
|
|
2137
|
+
class Report(database.Model, BaseTabla):
|
|
2138
|
+
"""Report definition and configuration.
|
|
2139
|
+
|
|
2140
|
+
Represents both System and Custom reports. System reports are pre-defined
|
|
2141
|
+
in the application code with optimized queries. Custom reports are defined
|
|
2142
|
+
by users through the UI using a declarative JSON-based configuration.
|
|
2143
|
+
"""
|
|
2144
|
+
|
|
2145
|
+
__tablename__ = "report"
|
|
2146
|
+
__table_args__ = (database.UniqueConstraint("name", name="uq_report_name"),)
|
|
2147
|
+
|
|
2148
|
+
# Basic information
|
|
2149
|
+
name = database.Column(database.String(150), nullable=False, unique=True, index=True)
|
|
2150
|
+
description = database.Column(database.String(500), nullable=True)
|
|
2151
|
+
|
|
2152
|
+
# Report type: SYSTEM or CUSTOM
|
|
2153
|
+
type = database.Column(database.String(20), nullable=False, default="custom") # system | custom
|
|
2154
|
+
|
|
2155
|
+
# Administrative status
|
|
2156
|
+
status = database.Column(database.String(20), nullable=False, default="enabled") # enabled | disabled
|
|
2157
|
+
|
|
2158
|
+
# Base entity for the report (e.g., Employee, Nomina, Vacation)
|
|
2159
|
+
base_entity = database.Column(database.String(100), nullable=False)
|
|
2160
|
+
|
|
2161
|
+
# Report definition (JSON, nullable for System reports as they're coded)
|
|
2162
|
+
# For Custom reports: contains columns, filters, sorting, expressions
|
|
2163
|
+
definition = database.Column(MutableDict.as_mutable(OrjsonType), nullable=True, default=dict)
|
|
2164
|
+
|
|
2165
|
+
# System report identifier (for system reports only)
|
|
2166
|
+
# Used to identify the report implementation in code
|
|
2167
|
+
system_report_id = database.Column(database.String(100), nullable=True, unique=True, index=True)
|
|
2168
|
+
|
|
2169
|
+
# Category for organization (e.g., "payroll", "employee", "vacation")
|
|
2170
|
+
category = database.Column(database.String(50), nullable=True, index=True)
|
|
2171
|
+
|
|
2172
|
+
# Relationships
|
|
2173
|
+
permissions = database.relationship("ReportRole", back_populates="report", cascade="all,delete-orphan")
|
|
2174
|
+
executions = database.relationship("ReportExecution", back_populates="report", cascade="all,delete-orphan")
|
|
2175
|
+
audit_entries = database.relationship("ReportAudit", back_populates="report", cascade="all,delete-orphan")
|
|
2176
|
+
|
|
2177
|
+
|
|
2178
|
+
class ReportRole(database.Model, BaseTabla):
|
|
2179
|
+
"""Report permissions by user role.
|
|
2180
|
+
|
|
2181
|
+
Defines which user types (admin, hhrr, audit) can view, execute, and
|
|
2182
|
+
export a specific report.
|
|
2183
|
+
"""
|
|
2184
|
+
|
|
2185
|
+
__tablename__ = "report_role"
|
|
2186
|
+
__table_args__ = (database.UniqueConstraint("report_id", "role", name="uq_report_role"),)
|
|
2187
|
+
|
|
2188
|
+
# Foreign key to report
|
|
2189
|
+
report_id = database.Column(database.String(26), database.ForeignKey("report.id"), nullable=False)
|
|
2190
|
+
report = database.relationship("Report", back_populates="permissions")
|
|
2191
|
+
|
|
2192
|
+
# User role (admin, hhrr, audit)
|
|
2193
|
+
role = database.Column(database.String(20), nullable=False, index=True)
|
|
2194
|
+
|
|
2195
|
+
# Permissions
|
|
2196
|
+
can_view = database.Column(database.Boolean(), nullable=False, default=True)
|
|
2197
|
+
can_execute = database.Column(database.Boolean(), nullable=False, default=True)
|
|
2198
|
+
can_export = database.Column(database.Boolean(), nullable=False, default=False)
|
|
2199
|
+
|
|
2200
|
+
|
|
2201
|
+
class ReportExecution(database.Model, BaseTabla):
|
|
2202
|
+
"""Report execution history and status.
|
|
2203
|
+
|
|
2204
|
+
Tracks report executions including status, parameters, results,
|
|
2205
|
+
and performance metrics. Used for auditing and async execution.
|
|
2206
|
+
"""
|
|
2207
|
+
|
|
2208
|
+
__tablename__ = "report_execution"
|
|
2209
|
+
|
|
2210
|
+
# Foreign key to report
|
|
2211
|
+
report_id = database.Column(database.String(26), database.ForeignKey("report.id"), nullable=False)
|
|
2212
|
+
report = database.relationship("Report", back_populates="executions")
|
|
2213
|
+
|
|
2214
|
+
# Execution status
|
|
2215
|
+
status = database.Column(
|
|
2216
|
+
database.String(20), nullable=False, default="queued"
|
|
2217
|
+
) # queued | running | completed | failed | cancelled
|
|
2218
|
+
|
|
2219
|
+
# Execution parameters (filters applied by user)
|
|
2220
|
+
parameters = database.Column(MutableDict.as_mutable(OrjsonType), nullable=True, default=dict)
|
|
2221
|
+
|
|
2222
|
+
# User who requested the execution
|
|
2223
|
+
executed_by = database.Column(database.String(150), nullable=False, index=True)
|
|
2224
|
+
|
|
2225
|
+
# Execution timestamps
|
|
2226
|
+
started_at = database.Column(database.DateTime, nullable=True)
|
|
2227
|
+
completed_at = database.Column(database.DateTime, nullable=True)
|
|
2228
|
+
|
|
2229
|
+
# Results
|
|
2230
|
+
row_count = database.Column(database.Integer, nullable=True)
|
|
2231
|
+
execution_time_ms = database.Column(database.Integer, nullable=True) # in milliseconds
|
|
2232
|
+
|
|
2233
|
+
# Error information (if failed)
|
|
2234
|
+
error_message = database.Column(database.String(1000), nullable=True)
|
|
2235
|
+
|
|
2236
|
+
# Export file path (if exported)
|
|
2237
|
+
export_file_path = database.Column(database.String(500), nullable=True)
|
|
2238
|
+
export_format = database.Column(database.String(20), nullable=True) # excel, csv, pdf
|
|
2239
|
+
|
|
2240
|
+
|
|
2241
|
+
class ReportAudit(database.Model, BaseTabla):
|
|
2242
|
+
"""Audit trail for report configuration changes.
|
|
2243
|
+
|
|
2244
|
+
Records all changes to report definitions, status, and permissions
|
|
2245
|
+
for compliance and debugging.
|
|
2246
|
+
"""
|
|
2247
|
+
|
|
2248
|
+
__tablename__ = "report_audit"
|
|
2249
|
+
|
|
2250
|
+
# Foreign key to report
|
|
2251
|
+
report_id = database.Column(database.String(26), database.ForeignKey("report.id"), nullable=False)
|
|
2252
|
+
report = database.relationship("Report", back_populates="audit_entries")
|
|
2253
|
+
|
|
2254
|
+
# Action performed
|
|
2255
|
+
action = database.Column(
|
|
2256
|
+
database.String(50), nullable=False, index=True
|
|
2257
|
+
) # created | updated | status_changed | etc
|
|
2258
|
+
|
|
2259
|
+
# User who performed the action
|
|
2260
|
+
performed_by = database.Column(database.String(150), nullable=False, index=True)
|
|
2261
|
+
|
|
2262
|
+
# Changes (JSON storing before/after values)
|
|
2263
|
+
changes = database.Column(MutableDict.as_mutable(OrjsonType), nullable=True, default=dict)
|
|
2264
|
+
|
|
2265
|
+
# Timestamp is inherited from BaseTabla
|
|
2266
|
+
|
|
2267
|
+
|
|
2268
|
+
class ConceptoAuditLog(database.Model, BaseTabla):
|
|
2269
|
+
"""Audit trail for payroll concept changes (percepciones, deducciones, prestaciones).
|
|
2270
|
+
|
|
2271
|
+
Records all changes to payroll concepts including creation, modification, and approval.
|
|
2272
|
+
Tracks who made changes, when, and what was changed for governance and compliance.
|
|
2273
|
+
"""
|
|
2274
|
+
|
|
2275
|
+
__tablename__ = "concepto_audit_log"
|
|
2276
|
+
|
|
2277
|
+
# Foreign keys to the concepts (only one will be set)
|
|
2278
|
+
percepcion_id = database.Column(database.String(26), database.ForeignKey("percepcion.id"), nullable=True)
|
|
2279
|
+
deduccion_id = database.Column(database.String(26), database.ForeignKey("deduccion.id"), nullable=True)
|
|
2280
|
+
prestacion_id = database.Column(database.String(26), database.ForeignKey("prestacion.id"), nullable=True)
|
|
2281
|
+
|
|
2282
|
+
# Type of concept (for easier filtering)
|
|
2283
|
+
tipo_concepto = database.Column(
|
|
2284
|
+
database.String(20), nullable=False, index=True
|
|
2285
|
+
) # percepcion | deduccion | prestacion
|
|
2286
|
+
|
|
2287
|
+
# Action performed
|
|
2288
|
+
accion = database.Column(
|
|
2289
|
+
database.String(50), nullable=False, index=True
|
|
2290
|
+
) # created | updated | approved | rejected | status_changed
|
|
2291
|
+
|
|
2292
|
+
# User who performed the action
|
|
2293
|
+
usuario = database.Column(database.String(150), nullable=False, index=True)
|
|
2294
|
+
|
|
2295
|
+
# Description of the change (human-readable)
|
|
2296
|
+
descripcion = database.Column(database.String(1000), nullable=True)
|
|
2297
|
+
|
|
2298
|
+
# Detailed changes (JSON storing field-level before/after values)
|
|
2299
|
+
cambios = database.Column(MutableDict.as_mutable(OrjsonType), nullable=True, default=dict)
|
|
2300
|
+
|
|
2301
|
+
# Previous and new approval status (if applicable)
|
|
2302
|
+
estado_anterior = database.Column(database.String(20), nullable=True)
|
|
2303
|
+
estado_nuevo = database.Column(database.String(20), nullable=True)
|
|
2304
|
+
|
|
2305
|
+
# Relationships
|
|
2306
|
+
percepcion = database.relationship("Percepcion", back_populates="audit_logs")
|
|
2307
|
+
deduccion = database.relationship("Deduccion", back_populates="audit_logs")
|
|
2308
|
+
prestacion = database.relationship("Prestacion", back_populates="audit_logs")
|
|
2309
|
+
|
|
2310
|
+
|
|
2311
|
+
class PlanillaAuditLog(database.Model, BaseTabla):
|
|
2312
|
+
"""Audit trail for Planilla changes.
|
|
2313
|
+
|
|
2314
|
+
Records all changes to planillas including creation, modification, approval,
|
|
2315
|
+
and configuration changes (adding/removing employees, concepts, etc.).
|
|
2316
|
+
"""
|
|
2317
|
+
|
|
2318
|
+
__tablename__ = "planilla_audit_log"
|
|
2319
|
+
|
|
2320
|
+
# Foreign key to planilla
|
|
2321
|
+
planilla_id = database.Column(database.String(26), database.ForeignKey("planilla.id"), nullable=False)
|
|
2322
|
+
|
|
2323
|
+
# Action performed
|
|
2324
|
+
accion = database.Column(
|
|
2325
|
+
database.String(50), nullable=False, index=True
|
|
2326
|
+
) # created | updated | approved | rejected | employee_added | employee_removed | concept_added | concept_removed
|
|
2327
|
+
|
|
2328
|
+
# User who performed the action
|
|
2329
|
+
usuario = database.Column(database.String(150), nullable=False, index=True)
|
|
2330
|
+
|
|
2331
|
+
# Description of the change (human-readable)
|
|
2332
|
+
descripcion = database.Column(database.String(1000), nullable=True)
|
|
2333
|
+
|
|
2334
|
+
# Detailed changes (JSON storing field-level before/after values)
|
|
2335
|
+
cambios = database.Column(MutableDict.as_mutable(OrjsonType), nullable=True, default=dict)
|
|
2336
|
+
|
|
2337
|
+
# Previous and new approval status (if applicable)
|
|
2338
|
+
estado_anterior = database.Column(database.String(20), nullable=True)
|
|
2339
|
+
estado_nuevo = database.Column(database.String(20), nullable=True)
|
|
2340
|
+
|
|
2341
|
+
# Relationship
|
|
2342
|
+
planilla = database.relationship("Planilla", back_populates="audit_logs")
|
|
2343
|
+
|
|
2344
|
+
|
|
2345
|
+
class NominaAuditLog(database.Model, BaseTabla):
|
|
2346
|
+
"""Audit trail for Nomina state changes.
|
|
2347
|
+
|
|
2348
|
+
Records all state transitions of nominas: generation, approval, application,
|
|
2349
|
+
cancellation, and any modifications.
|
|
2350
|
+
"""
|
|
2351
|
+
|
|
2352
|
+
__tablename__ = "nomina_audit_log"
|
|
2353
|
+
|
|
2354
|
+
# Foreign key to nomina
|
|
2355
|
+
nomina_id = database.Column(database.String(26), database.ForeignKey("nomina.id"), nullable=False)
|
|
2356
|
+
|
|
2357
|
+
# Action performed
|
|
2358
|
+
accion = database.Column(
|
|
2359
|
+
database.String(50), nullable=False, index=True
|
|
2360
|
+
) # generated | approved | applied | cancelled | recalculated | modified
|
|
2361
|
+
|
|
2362
|
+
# User who performed the action
|
|
2363
|
+
usuario = database.Column(database.String(150), nullable=False, index=True)
|
|
2364
|
+
|
|
2365
|
+
# Description of the change (human-readable)
|
|
2366
|
+
descripcion = database.Column(database.String(1000), nullable=True)
|
|
2367
|
+
|
|
2368
|
+
# Detailed changes (JSON storing field-level before/after values)
|
|
2369
|
+
cambios = database.Column(MutableDict.as_mutable(OrjsonType), nullable=True, default=dict)
|
|
2370
|
+
|
|
2371
|
+
# Previous and new state (for state transitions)
|
|
2372
|
+
estado_anterior = database.Column(database.String(30), nullable=True)
|
|
2373
|
+
estado_nuevo = database.Column(database.String(30), nullable=True)
|
|
2374
|
+
|
|
2375
|
+
# Relationship
|
|
2376
|
+
nomina = database.relationship("Nomina", back_populates="audit_logs")
|
|
2377
|
+
|
|
2378
|
+
|
|
2379
|
+
class ReglaCalculoAuditLog(database.Model, BaseTabla):
|
|
2380
|
+
"""Audit trail for ReglaCalculo changes.
|
|
2381
|
+
|
|
2382
|
+
Records all changes to calculation rules including creation, modification,
|
|
2383
|
+
approval, schema changes, and versioning for SOX/COSO compliance.
|
|
2384
|
+
"""
|
|
2385
|
+
|
|
2386
|
+
__tablename__ = "regla_calculo_audit_log"
|
|
2387
|
+
|
|
2388
|
+
# Foreign key to regla_calculo
|
|
2389
|
+
regla_calculo_id = database.Column(database.String(26), database.ForeignKey("regla_calculo.id"), nullable=False)
|
|
2390
|
+
|
|
2391
|
+
# Action performed
|
|
2392
|
+
accion = database.Column(
|
|
2393
|
+
database.String(50), nullable=False, index=True
|
|
2394
|
+
) # created | updated | approved | rejected | schema_changed | version_changed | status_changed
|
|
2395
|
+
|
|
2396
|
+
# User who performed the action
|
|
2397
|
+
usuario = database.Column(database.String(150), nullable=False, index=True)
|
|
2398
|
+
|
|
2399
|
+
# Description of the change (human-readable)
|
|
2400
|
+
descripcion = database.Column(database.String(1000), nullable=True)
|
|
2401
|
+
|
|
2402
|
+
# Detailed changes (JSON storing field-level before/after values)
|
|
2403
|
+
cambios = database.Column(MutableDict.as_mutable(OrjsonType), nullable=True, default=dict)
|
|
2404
|
+
|
|
2405
|
+
# Previous and new approval status (if applicable)
|
|
2406
|
+
estado_anterior = database.Column(database.String(20), nullable=True)
|
|
2407
|
+
estado_nuevo = database.Column(database.String(20), nullable=True)
|
|
2408
|
+
|
|
2409
|
+
# Relationship
|
|
2410
|
+
regla_calculo = database.relationship("ReglaCalculo", back_populates="audit_logs")
|