coati-payroll 0.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of coati-payroll might be problematic. Click here for more details.
- coati_payroll/__init__.py +415 -0
- coati_payroll/app.py +95 -0
- coati_payroll/audit_helpers.py +904 -0
- coati_payroll/auth.py +123 -0
- coati_payroll/cli.py +1318 -0
- coati_payroll/config.py +219 -0
- coati_payroll/demo_data.py +813 -0
- coati_payroll/enums.py +278 -0
- coati_payroll/forms.py +1769 -0
- coati_payroll/formula_engine/__init__.py +81 -0
- coati_payroll/formula_engine/ast/__init__.py +110 -0
- coati_payroll/formula_engine/ast/ast_visitor.py +259 -0
- coati_payroll/formula_engine/ast/expression_evaluator.py +228 -0
- coati_payroll/formula_engine/ast/safe_operators.py +131 -0
- coati_payroll/formula_engine/ast/type_converter.py +172 -0
- coati_payroll/formula_engine/data_sources.py +752 -0
- coati_payroll/formula_engine/engine.py +247 -0
- coati_payroll/formula_engine/exceptions.py +52 -0
- coati_payroll/formula_engine/execution/__init__.py +24 -0
- coati_payroll/formula_engine/execution/execution_context.py +52 -0
- coati_payroll/formula_engine/execution/step_executor.py +62 -0
- coati_payroll/formula_engine/execution/variable_store.py +59 -0
- coati_payroll/formula_engine/novelty_codes.py +206 -0
- coati_payroll/formula_engine/results/__init__.py +20 -0
- coati_payroll/formula_engine/results/execution_result.py +59 -0
- coati_payroll/formula_engine/steps/__init__.py +30 -0
- coati_payroll/formula_engine/steps/assignment_step.py +71 -0
- coati_payroll/formula_engine/steps/base_step.py +48 -0
- coati_payroll/formula_engine/steps/calculation_step.py +42 -0
- coati_payroll/formula_engine/steps/conditional_step.py +122 -0
- coati_payroll/formula_engine/steps/step_factory.py +58 -0
- coati_payroll/formula_engine/steps/tax_lookup_step.py +45 -0
- coati_payroll/formula_engine/tables/__init__.py +24 -0
- coati_payroll/formula_engine/tables/bracket_calculator.py +51 -0
- coati_payroll/formula_engine/tables/table_lookup.py +161 -0
- coati_payroll/formula_engine/tables/tax_table.py +32 -0
- coati_payroll/formula_engine/validation/__init__.py +24 -0
- coati_payroll/formula_engine/validation/schema_validator.py +37 -0
- coati_payroll/formula_engine/validation/security_validator.py +52 -0
- coati_payroll/formula_engine/validation/tax_table_validator.py +205 -0
- coati_payroll/formula_engine_examples.py +153 -0
- coati_payroll/i18n.py +54 -0
- coati_payroll/initial_data.py +613 -0
- coati_payroll/interes_engine.py +450 -0
- coati_payroll/liquidacion_engine/__init__.py +25 -0
- coati_payroll/liquidacion_engine/engine.py +267 -0
- coati_payroll/locale_config.py +165 -0
- coati_payroll/log.py +138 -0
- coati_payroll/model.py +2410 -0
- coati_payroll/nomina_engine/__init__.py +87 -0
- coati_payroll/nomina_engine/calculators/__init__.py +30 -0
- coati_payroll/nomina_engine/calculators/benefit_calculator.py +79 -0
- coati_payroll/nomina_engine/calculators/concept_calculator.py +254 -0
- coati_payroll/nomina_engine/calculators/deduction_calculator.py +105 -0
- coati_payroll/nomina_engine/calculators/exchange_rate_calculator.py +51 -0
- coati_payroll/nomina_engine/calculators/perception_calculator.py +75 -0
- coati_payroll/nomina_engine/calculators/salary_calculator.py +86 -0
- coati_payroll/nomina_engine/domain/__init__.py +27 -0
- coati_payroll/nomina_engine/domain/calculation_items.py +52 -0
- coati_payroll/nomina_engine/domain/employee_calculation.py +53 -0
- coati_payroll/nomina_engine/domain/payroll_context.py +44 -0
- coati_payroll/nomina_engine/engine.py +188 -0
- coati_payroll/nomina_engine/processors/__init__.py +28 -0
- coati_payroll/nomina_engine/processors/accounting_processor.py +171 -0
- coati_payroll/nomina_engine/processors/accumulation_processor.py +90 -0
- coati_payroll/nomina_engine/processors/loan_processor.py +227 -0
- coati_payroll/nomina_engine/processors/novelty_processor.py +42 -0
- coati_payroll/nomina_engine/processors/vacation_processor.py +67 -0
- coati_payroll/nomina_engine/repositories/__init__.py +32 -0
- coati_payroll/nomina_engine/repositories/acumulado_repository.py +83 -0
- coati_payroll/nomina_engine/repositories/base_repository.py +40 -0
- coati_payroll/nomina_engine/repositories/config_repository.py +102 -0
- coati_payroll/nomina_engine/repositories/employee_repository.py +34 -0
- coati_payroll/nomina_engine/repositories/exchange_rate_repository.py +58 -0
- coati_payroll/nomina_engine/repositories/novelty_repository.py +54 -0
- coati_payroll/nomina_engine/repositories/planilla_repository.py +52 -0
- coati_payroll/nomina_engine/results/__init__.py +24 -0
- coati_payroll/nomina_engine/results/error_result.py +28 -0
- coati_payroll/nomina_engine/results/payroll_result.py +53 -0
- coati_payroll/nomina_engine/results/validation_result.py +39 -0
- coati_payroll/nomina_engine/services/__init__.py +22 -0
- coati_payroll/nomina_engine/services/accounting_voucher_service.py +708 -0
- coati_payroll/nomina_engine/services/employee_processing_service.py +173 -0
- coati_payroll/nomina_engine/services/payroll_execution_service.py +374 -0
- coati_payroll/nomina_engine/services/snapshot_service.py +295 -0
- coati_payroll/nomina_engine/validators/__init__.py +31 -0
- coati_payroll/nomina_engine/validators/base_validator.py +48 -0
- coati_payroll/nomina_engine/validators/currency_validator.py +50 -0
- coati_payroll/nomina_engine/validators/employee_validator.py +87 -0
- coati_payroll/nomina_engine/validators/period_validator.py +44 -0
- coati_payroll/nomina_engine/validators/planilla_validator.py +136 -0
- coati_payroll/plugin_manager.py +176 -0
- coati_payroll/queue/__init__.py +33 -0
- coati_payroll/queue/driver.py +127 -0
- coati_payroll/queue/drivers/__init__.py +22 -0
- coati_payroll/queue/drivers/dramatiq_driver.py +268 -0
- coati_payroll/queue/drivers/huey_driver.py +390 -0
- coati_payroll/queue/drivers/noop_driver.py +54 -0
- coati_payroll/queue/selector.py +121 -0
- coati_payroll/queue/tasks.py +764 -0
- coati_payroll/rate_limiting.py +83 -0
- coati_payroll/rbac.py +183 -0
- coati_payroll/report_engine.py +512 -0
- coati_payroll/report_export.py +208 -0
- coati_payroll/schema_validator.py +167 -0
- coati_payroll/security.py +77 -0
- coati_payroll/static/styles.css +1044 -0
- coati_payroll/system_reports.py +573 -0
- coati_payroll/templates/auth/login.html +189 -0
- coati_payroll/templates/base.html +283 -0
- coati_payroll/templates/index.html +227 -0
- coati_payroll/templates/macros.html +146 -0
- coati_payroll/templates/modules/calculation_rule/form.html +78 -0
- coati_payroll/templates/modules/calculation_rule/index.html +102 -0
- coati_payroll/templates/modules/calculation_rule/schema_editor.html +1159 -0
- coati_payroll/templates/modules/carga_inicial_prestacion/form.html +170 -0
- coati_payroll/templates/modules/carga_inicial_prestacion/index.html +170 -0
- coati_payroll/templates/modules/carga_inicial_prestacion/reporte.html +193 -0
- coati_payroll/templates/modules/config_calculos/index.html +44 -0
- coati_payroll/templates/modules/configuracion/index.html +90 -0
- coati_payroll/templates/modules/currency/form.html +47 -0
- coati_payroll/templates/modules/currency/index.html +64 -0
- coati_payroll/templates/modules/custom_field/form.html +62 -0
- coati_payroll/templates/modules/custom_field/index.html +78 -0
- coati_payroll/templates/modules/deduccion/form.html +1 -0
- coati_payroll/templates/modules/deduccion/index.html +1 -0
- coati_payroll/templates/modules/employee/form.html +254 -0
- coati_payroll/templates/modules/employee/index.html +76 -0
- coati_payroll/templates/modules/empresa/form.html +74 -0
- coati_payroll/templates/modules/empresa/index.html +71 -0
- coati_payroll/templates/modules/exchange_rate/form.html +47 -0
- coati_payroll/templates/modules/exchange_rate/import.html +93 -0
- coati_payroll/templates/modules/exchange_rate/index.html +114 -0
- coati_payroll/templates/modules/liquidacion/index.html +58 -0
- coati_payroll/templates/modules/liquidacion/nueva.html +51 -0
- coati_payroll/templates/modules/liquidacion/ver.html +91 -0
- coati_payroll/templates/modules/payroll_concepts/audit_log.html +146 -0
- coati_payroll/templates/modules/percepcion/form.html +1 -0
- coati_payroll/templates/modules/percepcion/index.html +1 -0
- coati_payroll/templates/modules/planilla/config.html +190 -0
- coati_payroll/templates/modules/planilla/config_deducciones.html +129 -0
- coati_payroll/templates/modules/planilla/config_empleados.html +116 -0
- coati_payroll/templates/modules/planilla/config_percepciones.html +113 -0
- coati_payroll/templates/modules/planilla/config_prestaciones.html +118 -0
- coati_payroll/templates/modules/planilla/config_reglas.html +120 -0
- coati_payroll/templates/modules/planilla/ejecutar_nomina.html +106 -0
- coati_payroll/templates/modules/planilla/form.html +197 -0
- coati_payroll/templates/modules/planilla/index.html +144 -0
- coati_payroll/templates/modules/planilla/listar_nominas.html +91 -0
- coati_payroll/templates/modules/planilla/log_nomina.html +135 -0
- coati_payroll/templates/modules/planilla/novedades/form.html +177 -0
- coati_payroll/templates/modules/planilla/novedades/index.html +170 -0
- coati_payroll/templates/modules/planilla/ver_nomina.html +477 -0
- coati_payroll/templates/modules/planilla/ver_nomina_empleado.html +231 -0
- coati_payroll/templates/modules/plugins/index.html +71 -0
- coati_payroll/templates/modules/prestacion/form.html +1 -0
- coati_payroll/templates/modules/prestacion/index.html +1 -0
- coati_payroll/templates/modules/prestacion_management/dashboard.html +150 -0
- coati_payroll/templates/modules/prestacion_management/initial_balance_bulk.html +195 -0
- coati_payroll/templates/modules/prestamo/approve.html +156 -0
- coati_payroll/templates/modules/prestamo/condonacion.html +249 -0
- coati_payroll/templates/modules/prestamo/detail.html +443 -0
- coati_payroll/templates/modules/prestamo/form.html +203 -0
- coati_payroll/templates/modules/prestamo/index.html +150 -0
- coati_payroll/templates/modules/prestamo/pago_extraordinario.html +211 -0
- coati_payroll/templates/modules/prestamo/tabla_pago_pdf.html +181 -0
- coati_payroll/templates/modules/report/admin_index.html +125 -0
- coati_payroll/templates/modules/report/detail.html +129 -0
- coati_payroll/templates/modules/report/execute.html +266 -0
- coati_payroll/templates/modules/report/index.html +95 -0
- coati_payroll/templates/modules/report/permissions.html +64 -0
- coati_payroll/templates/modules/settings/index.html +274 -0
- coati_payroll/templates/modules/shared/concept_form.html +201 -0
- coati_payroll/templates/modules/shared/concept_index.html +145 -0
- coati_payroll/templates/modules/tipo_planilla/form.html +70 -0
- coati_payroll/templates/modules/tipo_planilla/index.html +68 -0
- coati_payroll/templates/modules/user/form.html +65 -0
- coati_payroll/templates/modules/user/index.html +76 -0
- coati_payroll/templates/modules/user/profile.html +81 -0
- coati_payroll/templates/modules/vacation/account_detail.html +149 -0
- coati_payroll/templates/modules/vacation/account_form.html +52 -0
- coati_payroll/templates/modules/vacation/account_index.html +68 -0
- coati_payroll/templates/modules/vacation/dashboard.html +156 -0
- coati_payroll/templates/modules/vacation/initial_balance_bulk.html +149 -0
- coati_payroll/templates/modules/vacation/initial_balance_form.html +93 -0
- coati_payroll/templates/modules/vacation/leave_request_detail.html +158 -0
- coati_payroll/templates/modules/vacation/leave_request_form.html +61 -0
- coati_payroll/templates/modules/vacation/leave_request_index.html +98 -0
- coati_payroll/templates/modules/vacation/policy_detail.html +176 -0
- coati_payroll/templates/modules/vacation/policy_form.html +152 -0
- coati_payroll/templates/modules/vacation/policy_index.html +79 -0
- coati_payroll/templates/modules/vacation/register_taken_form.html +178 -0
- coati_payroll/translations/en/LC_MESSAGES/messages.mo +0 -0
- coati_payroll/translations/en/LC_MESSAGES/messages.po +7283 -0
- coati_payroll/translations/es/LC_MESSAGES/messages.mo +0 -0
- coati_payroll/translations/es/LC_MESSAGES/messages.po +7374 -0
- coati_payroll/vacation_service.py +451 -0
- coati_payroll/version.py +18 -0
- coati_payroll/vistas/__init__.py +64 -0
- coati_payroll/vistas/calculation_rule.py +307 -0
- coati_payroll/vistas/carga_inicial_prestacion.py +423 -0
- coati_payroll/vistas/config_calculos.py +72 -0
- coati_payroll/vistas/configuracion.py +87 -0
- coati_payroll/vistas/constants.py +17 -0
- coati_payroll/vistas/currency.py +112 -0
- coati_payroll/vistas/custom_field.py +120 -0
- coati_payroll/vistas/employee.py +305 -0
- coati_payroll/vistas/empresa.py +153 -0
- coati_payroll/vistas/exchange_rate.py +341 -0
- coati_payroll/vistas/liquidacion.py +205 -0
- coati_payroll/vistas/payroll_concepts.py +580 -0
- coati_payroll/vistas/planilla/__init__.py +38 -0
- coati_payroll/vistas/planilla/association_routes.py +238 -0
- coati_payroll/vistas/planilla/config_routes.py +158 -0
- coati_payroll/vistas/planilla/export_routes.py +175 -0
- coati_payroll/vistas/planilla/helpers/__init__.py +34 -0
- coati_payroll/vistas/planilla/helpers/association_helpers.py +161 -0
- coati_payroll/vistas/planilla/helpers/excel_helpers.py +29 -0
- coati_payroll/vistas/planilla/helpers/form_helpers.py +97 -0
- coati_payroll/vistas/planilla/nomina_routes.py +488 -0
- coati_payroll/vistas/planilla/novedad_routes.py +227 -0
- coati_payroll/vistas/planilla/routes.py +145 -0
- coati_payroll/vistas/planilla/services/__init__.py +26 -0
- coati_payroll/vistas/planilla/services/export_service.py +687 -0
- coati_payroll/vistas/planilla/services/nomina_service.py +233 -0
- coati_payroll/vistas/planilla/services/novedad_service.py +126 -0
- coati_payroll/vistas/planilla/services/planilla_service.py +34 -0
- coati_payroll/vistas/planilla/validators/__init__.py +18 -0
- coati_payroll/vistas/planilla/validators/planilla_validators.py +40 -0
- coati_payroll/vistas/plugins.py +45 -0
- coati_payroll/vistas/prestacion.py +272 -0
- coati_payroll/vistas/prestamo.py +808 -0
- coati_payroll/vistas/report.py +432 -0
- coati_payroll/vistas/settings.py +29 -0
- coati_payroll/vistas/tipo_planilla.py +134 -0
- coati_payroll/vistas/user.py +172 -0
- coati_payroll/vistas/vacation.py +1045 -0
- coati_payroll-0.0.2.dist-info/LICENSE +201 -0
- coati_payroll-0.0.2.dist-info/METADATA +581 -0
- coati_payroll-0.0.2.dist-info/RECORD +243 -0
- coati_payroll-0.0.2.dist-info/WHEEL +5 -0
- coati_payroll-0.0.2.dist-info/entry_points.txt +2 -0
- coati_payroll-0.0.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,580 @@
|
|
|
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
|
+
"""Payroll concepts CRUD routes: Percepciones, Deducciones, Prestaciones.
|
|
15
|
+
|
|
16
|
+
This module provides a unified, reusable backend for managing payroll concepts
|
|
17
|
+
(perceptions, deductions, benefits) with integrated calculation rule support.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
from decimal import Decimal
|
|
23
|
+
|
|
24
|
+
from flask import Blueprint, flash, redirect, render_template, request, url_for
|
|
25
|
+
from flask_login import current_user
|
|
26
|
+
|
|
27
|
+
from coati_payroll.audit_helpers import (
|
|
28
|
+
aprobar_concepto,
|
|
29
|
+
crear_log_auditoria,
|
|
30
|
+
detectar_cambios,
|
|
31
|
+
marcar_como_borrador_si_editado,
|
|
32
|
+
puede_aprobar_concepto,
|
|
33
|
+
rechazar_concepto,
|
|
34
|
+
)
|
|
35
|
+
from coati_payroll.enums import EstadoAprobacion
|
|
36
|
+
from coati_payroll.forms import (
|
|
37
|
+
DeduccionForm,
|
|
38
|
+
PercepcionForm,
|
|
39
|
+
PrestacionForm,
|
|
40
|
+
)
|
|
41
|
+
from coati_payroll.i18n import _
|
|
42
|
+
from coati_payroll.model import Deduccion, Percepcion, Prestacion, db
|
|
43
|
+
from coati_payroll.rbac import require_read_access, require_write_access
|
|
44
|
+
from coati_payroll.vistas.constants import PER_PAGE
|
|
45
|
+
|
|
46
|
+
# Create blueprints for each concept type
|
|
47
|
+
percepcion_bp = Blueprint("percepcion", __name__, url_prefix="/percepciones")
|
|
48
|
+
deduccion_bp = Blueprint("deduccion", __name__, url_prefix="/deducciones")
|
|
49
|
+
prestacion_bp = Blueprint("prestacion", __name__, url_prefix="/prestaciones")
|
|
50
|
+
|
|
51
|
+
# Constants
|
|
52
|
+
ERROR_CONCEPT_NOT_FOUND = "%(type)s no encontrada."
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# ============================================================================
|
|
56
|
+
# SHARED UTILITIES
|
|
57
|
+
# ============================================================================
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def get_concept_config(concept_type: str) -> dict:
|
|
61
|
+
"""Get configuration for a specific concept type.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
concept_type: One of 'percepcion', 'deduccion', 'prestacion'
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
Dictionary with model, form, labels, and template paths
|
|
68
|
+
"""
|
|
69
|
+
match concept_type:
|
|
70
|
+
case "percepcion":
|
|
71
|
+
return {
|
|
72
|
+
"model": Percepcion,
|
|
73
|
+
"form": PercepcionForm,
|
|
74
|
+
"singular": _("Percepción"),
|
|
75
|
+
"plural": _("Percepciones"),
|
|
76
|
+
"icon": "bi-plus-circle",
|
|
77
|
+
"template_dir": "modules/percepcion",
|
|
78
|
+
"blueprint": "percepcion",
|
|
79
|
+
"route_prefix": "percepcion_",
|
|
80
|
+
}
|
|
81
|
+
case "deduccion":
|
|
82
|
+
return {
|
|
83
|
+
"model": Deduccion,
|
|
84
|
+
"form": DeduccionForm,
|
|
85
|
+
"singular": _("Deducción"),
|
|
86
|
+
"plural": _("Deducciones"),
|
|
87
|
+
"icon": "bi-dash-circle",
|
|
88
|
+
"template_dir": "modules/deduccion",
|
|
89
|
+
"blueprint": "deduccion",
|
|
90
|
+
"route_prefix": "deduccion_",
|
|
91
|
+
}
|
|
92
|
+
case "prestacion":
|
|
93
|
+
return {
|
|
94
|
+
"model": Prestacion,
|
|
95
|
+
"form": PrestacionForm,
|
|
96
|
+
"singular": _("Prestación"),
|
|
97
|
+
"plural": _("Prestaciones"),
|
|
98
|
+
"icon": "bi-gift",
|
|
99
|
+
"template_dir": "modules/prestacion",
|
|
100
|
+
"blueprint": "prestacion",
|
|
101
|
+
"route_prefix": "prestacion_",
|
|
102
|
+
}
|
|
103
|
+
case _:
|
|
104
|
+
raise ValueError(f"Unknown concept type: {concept_type}")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def list_concepts(concept_type: str):
|
|
108
|
+
"""Generic list view for payroll concepts."""
|
|
109
|
+
config = get_concept_config(concept_type)
|
|
110
|
+
Model = config["model"]
|
|
111
|
+
|
|
112
|
+
page = request.args.get("page", 1, type=int)
|
|
113
|
+
pagination = db.paginate(
|
|
114
|
+
db.select(Model).order_by(Model.codigo),
|
|
115
|
+
page=page,
|
|
116
|
+
per_page=PER_PAGE,
|
|
117
|
+
error_out=False,
|
|
118
|
+
)
|
|
119
|
+
return render_template(
|
|
120
|
+
f"{config['template_dir']}/index.html",
|
|
121
|
+
items=pagination.items,
|
|
122
|
+
pagination=pagination,
|
|
123
|
+
config=config,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def create_concept(concept_type: str):
|
|
128
|
+
"""Generic create view for payroll concepts."""
|
|
129
|
+
config = get_concept_config(concept_type)
|
|
130
|
+
Model = config["model"]
|
|
131
|
+
Form = config["form"]
|
|
132
|
+
|
|
133
|
+
form = Form()
|
|
134
|
+
|
|
135
|
+
if form.validate_on_submit():
|
|
136
|
+
concept = Model()
|
|
137
|
+
populate_concept_from_form(concept, form)
|
|
138
|
+
concept.creado_por = current_user.usuario
|
|
139
|
+
|
|
140
|
+
# Set initial status as draft
|
|
141
|
+
concept.estado_aprobacion = EstadoAprobacion.BORRADOR
|
|
142
|
+
|
|
143
|
+
db.session.add(concept)
|
|
144
|
+
db.session.flush() # Get the ID before creating audit log
|
|
145
|
+
|
|
146
|
+
# Create audit log for creation
|
|
147
|
+
crear_log_auditoria(
|
|
148
|
+
concepto=concept,
|
|
149
|
+
accion="created",
|
|
150
|
+
usuario=current_user.usuario,
|
|
151
|
+
descripcion=f"Creó {concept_type} '{concept.nombre}' (código: {concept.codigo})",
|
|
152
|
+
estado_nuevo=EstadoAprobacion.BORRADOR,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
db.session.commit()
|
|
156
|
+
flash(_("%(type)s creada exitosamente en estado borrador.", type=config["singular"]), "success")
|
|
157
|
+
return redirect(url_for(f"{config['blueprint']}.{concept_type}_index"))
|
|
158
|
+
|
|
159
|
+
return render_template(
|
|
160
|
+
f"{config['template_dir']}/form.html",
|
|
161
|
+
form=form,
|
|
162
|
+
title=_("Nueva %(type)s", type=config["singular"]),
|
|
163
|
+
config=config,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def edit_concept(concept_type: str, concept_id: str):
|
|
168
|
+
"""Generic edit view for payroll concepts."""
|
|
169
|
+
config = get_concept_config(concept_type)
|
|
170
|
+
Model = config["model"]
|
|
171
|
+
Form = config["form"]
|
|
172
|
+
|
|
173
|
+
concept = db.session.get(Model, concept_id)
|
|
174
|
+
if not concept:
|
|
175
|
+
flash(_(ERROR_CONCEPT_NOT_FOUND, type=config["singular"]), "error")
|
|
176
|
+
return redirect(url_for(f"{config['blueprint']}.{concept_type}_index"))
|
|
177
|
+
|
|
178
|
+
# Store original values for change detection
|
|
179
|
+
original_data = {
|
|
180
|
+
"nombre": concept.nombre,
|
|
181
|
+
"descripcion": concept.descripcion,
|
|
182
|
+
"codigo": concept.codigo,
|
|
183
|
+
"formula_tipo": concept.formula_tipo,
|
|
184
|
+
"monto_default": concept.monto_default,
|
|
185
|
+
"porcentaje": concept.porcentaje,
|
|
186
|
+
"base_calculo": concept.base_calculo,
|
|
187
|
+
"activo": concept.activo,
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
form = Form(obj=concept)
|
|
191
|
+
|
|
192
|
+
if form.validate_on_submit():
|
|
193
|
+
populate_concept_from_form(concept, form)
|
|
194
|
+
concept.modificado_por = current_user.usuario
|
|
195
|
+
|
|
196
|
+
# Detect changes
|
|
197
|
+
new_data = {
|
|
198
|
+
"nombre": concept.nombre,
|
|
199
|
+
"descripcion": concept.descripcion,
|
|
200
|
+
"codigo": concept.codigo,
|
|
201
|
+
"formula_tipo": concept.formula_tipo,
|
|
202
|
+
"monto_default": concept.monto_default,
|
|
203
|
+
"porcentaje": concept.porcentaje,
|
|
204
|
+
"base_calculo": concept.base_calculo,
|
|
205
|
+
"activo": concept.activo,
|
|
206
|
+
}
|
|
207
|
+
cambios = detectar_cambios(original_data, new_data)
|
|
208
|
+
|
|
209
|
+
# Mark as draft if edited (unless created by plugin)
|
|
210
|
+
if cambios:
|
|
211
|
+
marcar_como_borrador_si_editado(concept, current_user.usuario, cambios)
|
|
212
|
+
|
|
213
|
+
db.session.commit()
|
|
214
|
+
|
|
215
|
+
if concept.estado_aprobacion == EstadoAprobacion.BORRADOR and not concept.creado_por_plugin:
|
|
216
|
+
flash(
|
|
217
|
+
_("%(type)s actualizada. Estado cambiado a borrador - requiere aprobación.", type=config["singular"]),
|
|
218
|
+
"warning",
|
|
219
|
+
)
|
|
220
|
+
else:
|
|
221
|
+
flash(_("%(type)s actualizada exitosamente.", type=config["singular"]), "success")
|
|
222
|
+
|
|
223
|
+
return redirect(url_for(f"{config['blueprint']}.{concept_type}_index"))
|
|
224
|
+
|
|
225
|
+
return render_template(
|
|
226
|
+
f"{config['template_dir']}/form.html",
|
|
227
|
+
form=form,
|
|
228
|
+
title=_("Editar %(type)s", type=config["singular"]),
|
|
229
|
+
concept=concept,
|
|
230
|
+
config=config,
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def delete_concept(concept_type: str, concept_id: str):
|
|
235
|
+
"""Generic delete view for payroll concepts."""
|
|
236
|
+
config = get_concept_config(concept_type)
|
|
237
|
+
Model = config["model"]
|
|
238
|
+
|
|
239
|
+
concept = db.session.get(Model, concept_id)
|
|
240
|
+
if not concept:
|
|
241
|
+
flash(_(ERROR_CONCEPT_NOT_FOUND, type=config["singular"]), "error")
|
|
242
|
+
return redirect(url_for(f"{config['blueprint']}.{concept_type}_index"))
|
|
243
|
+
|
|
244
|
+
# Check if concept is in use
|
|
245
|
+
if hasattr(concept, "planillas") and concept.planillas:
|
|
246
|
+
flash(
|
|
247
|
+
_(
|
|
248
|
+
"No se puede eliminar: %(type)s está asociada a una o más planillas.",
|
|
249
|
+
type=config["singular"],
|
|
250
|
+
),
|
|
251
|
+
"error",
|
|
252
|
+
)
|
|
253
|
+
return redirect(url_for(f"{config['blueprint']}.{concept_type}_index"))
|
|
254
|
+
|
|
255
|
+
db.session.delete(concept)
|
|
256
|
+
db.session.commit()
|
|
257
|
+
flash(_("%(type)s eliminada exitosamente.", type=config["singular"]), "success")
|
|
258
|
+
return redirect(url_for(f"{config['blueprint']}.{concept_type}_index"))
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def populate_concept_from_form(concept, form):
|
|
262
|
+
"""Populate a concept model from a form.
|
|
263
|
+
|
|
264
|
+
This is a shared function that handles the common fields
|
|
265
|
+
across Percepcion, Deduccion, and Prestacion.
|
|
266
|
+
"""
|
|
267
|
+
concept.codigo = form.codigo.data
|
|
268
|
+
concept.nombre = form.nombre.data
|
|
269
|
+
concept.descripcion = form.descripcion.data
|
|
270
|
+
concept.formula_tipo = form.formula_tipo.data
|
|
271
|
+
concept.activo = form.activo.data
|
|
272
|
+
|
|
273
|
+
# Handle monto_default
|
|
274
|
+
if form.monto_default.data:
|
|
275
|
+
concept.monto_default = Decimal(str(form.monto_default.data))
|
|
276
|
+
else:
|
|
277
|
+
concept.monto_default = None
|
|
278
|
+
|
|
279
|
+
# Handle porcentaje
|
|
280
|
+
if form.porcentaje.data:
|
|
281
|
+
concept.porcentaje = Decimal(str(form.porcentaje.data))
|
|
282
|
+
else:
|
|
283
|
+
concept.porcentaje = None
|
|
284
|
+
|
|
285
|
+
# Common optional fields
|
|
286
|
+
if hasattr(form, "base_calculo") and form.base_calculo.data:
|
|
287
|
+
concept.base_calculo = form.base_calculo.data
|
|
288
|
+
|
|
289
|
+
if hasattr(form, "unidad_calculo") and form.unidad_calculo.data:
|
|
290
|
+
concept.unidad_calculo = form.unidad_calculo.data
|
|
291
|
+
|
|
292
|
+
if hasattr(form, "recurrente"):
|
|
293
|
+
concept.recurrente = form.recurrente.data
|
|
294
|
+
|
|
295
|
+
if hasattr(form, "contabilizable"):
|
|
296
|
+
concept.contabilizable = form.contabilizable.data
|
|
297
|
+
|
|
298
|
+
if hasattr(form, "codigo_cuenta_debe") and form.codigo_cuenta_debe.data:
|
|
299
|
+
concept.codigo_cuenta_debe = form.codigo_cuenta_debe.data
|
|
300
|
+
|
|
301
|
+
if hasattr(form, "descripcion_cuenta_debe") and form.descripcion_cuenta_debe.data:
|
|
302
|
+
concept.descripcion_cuenta_debe = form.descripcion_cuenta_debe.data
|
|
303
|
+
|
|
304
|
+
if hasattr(form, "codigo_cuenta_haber") and form.codigo_cuenta_haber.data:
|
|
305
|
+
concept.codigo_cuenta_haber = form.codigo_cuenta_haber.data
|
|
306
|
+
|
|
307
|
+
if hasattr(form, "descripcion_cuenta_haber") and form.descripcion_cuenta_haber.data:
|
|
308
|
+
concept.descripcion_cuenta_haber = form.descripcion_cuenta_haber.data
|
|
309
|
+
|
|
310
|
+
if hasattr(form, "editable_en_nomina"):
|
|
311
|
+
concept.editable_en_nomina = form.editable_en_nomina.data
|
|
312
|
+
|
|
313
|
+
# Vigencia fields
|
|
314
|
+
if hasattr(form, "vigente_desde"):
|
|
315
|
+
concept.vigente_desde = form.vigente_desde.data
|
|
316
|
+
|
|
317
|
+
if hasattr(form, "valido_hasta"):
|
|
318
|
+
concept.valido_hasta = form.valido_hasta.data
|
|
319
|
+
|
|
320
|
+
# Type-specific fields
|
|
321
|
+
if hasattr(form, "gravable"): # Percepcion
|
|
322
|
+
concept.gravable = form.gravable.data
|
|
323
|
+
|
|
324
|
+
if hasattr(form, "tipo") and form.tipo.data: # Deduccion, Prestacion
|
|
325
|
+
concept.tipo = form.tipo.data
|
|
326
|
+
|
|
327
|
+
if hasattr(form, "es_impuesto"): # Deduccion
|
|
328
|
+
concept.es_impuesto = form.es_impuesto.data
|
|
329
|
+
|
|
330
|
+
if hasattr(form, "antes_impuesto"): # Deduccion
|
|
331
|
+
concept.antes_impuesto = form.antes_impuesto.data
|
|
332
|
+
|
|
333
|
+
# Prestacion-specific fields
|
|
334
|
+
if hasattr(form, "tope_aplicacion"):
|
|
335
|
+
if form.tope_aplicacion.data:
|
|
336
|
+
concept.tope_aplicacion = Decimal(str(form.tope_aplicacion.data))
|
|
337
|
+
else:
|
|
338
|
+
concept.tope_aplicacion = None
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
# ============================================================================
|
|
342
|
+
# PERCEPCION ROUTES
|
|
343
|
+
# ============================================================================
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
@percepcion_bp.route("/")
|
|
347
|
+
@require_read_access()
|
|
348
|
+
def percepcion_index():
|
|
349
|
+
"""List all perceptions."""
|
|
350
|
+
return list_concepts("percepcion")
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
@percepcion_bp.route("/new", methods=["GET", "POST"])
|
|
354
|
+
@require_write_access()
|
|
355
|
+
def percepcion_new():
|
|
356
|
+
"""Create a new perception. Admin and HR can create perceptions."""
|
|
357
|
+
return create_concept("percepcion")
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
@percepcion_bp.route("/edit/<string:concept_id>", methods=["GET", "POST"])
|
|
361
|
+
@require_write_access()
|
|
362
|
+
def percepcion_edit(concept_id: str):
|
|
363
|
+
"""Edit an existing perception. Admin and HR can edit perceptions."""
|
|
364
|
+
return edit_concept("percepcion", concept_id)
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
@percepcion_bp.route("/delete/<string:concept_id>", methods=["POST"])
|
|
368
|
+
@require_write_access()
|
|
369
|
+
def percepcion_delete(concept_id: str):
|
|
370
|
+
"""Delete a perception. Admin and HR can delete perceptions."""
|
|
371
|
+
return delete_concept("percepcion", concept_id)
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
# ============================================================================
|
|
375
|
+
# DEDUCCION ROUTES
|
|
376
|
+
# ============================================================================
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
@deduccion_bp.route("/")
|
|
380
|
+
@require_read_access()
|
|
381
|
+
def deduccion_index():
|
|
382
|
+
"""List all deductions."""
|
|
383
|
+
return list_concepts("deduccion")
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
@deduccion_bp.route("/new", methods=["GET", "POST"])
|
|
387
|
+
@require_write_access()
|
|
388
|
+
def deduccion_new():
|
|
389
|
+
"""Create a new deduction. Admin and HR can create deductions."""
|
|
390
|
+
return create_concept("deduccion")
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
@deduccion_bp.route("/edit/<string:concept_id>", methods=["GET", "POST"])
|
|
394
|
+
@require_write_access()
|
|
395
|
+
def deduccion_edit(concept_id: str):
|
|
396
|
+
"""Edit an existing deduction. Admin and HR can edit deductions."""
|
|
397
|
+
return edit_concept("deduccion", concept_id)
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
@deduccion_bp.route("/delete/<string:concept_id>", methods=["POST"])
|
|
401
|
+
@require_write_access()
|
|
402
|
+
def deduccion_delete(concept_id: str):
|
|
403
|
+
"""Delete a deduction. Admin and HR can delete deductions."""
|
|
404
|
+
return delete_concept("deduccion", concept_id)
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
# ============================================================================
|
|
408
|
+
# PRESTACION ROUTES
|
|
409
|
+
# ============================================================================
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
@prestacion_bp.route("/")
|
|
413
|
+
@require_read_access()
|
|
414
|
+
def prestacion_index():
|
|
415
|
+
"""List all benefits."""
|
|
416
|
+
return list_concepts("prestacion")
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
@prestacion_bp.route("/new", methods=["GET", "POST"])
|
|
420
|
+
@require_write_access()
|
|
421
|
+
def prestacion_new():
|
|
422
|
+
"""Create a new benefit. Admin and HR can create benefits."""
|
|
423
|
+
return create_concept("prestacion")
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
@prestacion_bp.route("/edit/<string:concept_id>", methods=["GET", "POST"])
|
|
427
|
+
@require_write_access()
|
|
428
|
+
def prestacion_edit(concept_id: str):
|
|
429
|
+
"""Edit an existing benefit. Admin and HR can edit benefits."""
|
|
430
|
+
return edit_concept("prestacion", concept_id)
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
@prestacion_bp.route("/delete/<string:concept_id>", methods=["POST"])
|
|
434
|
+
@require_write_access()
|
|
435
|
+
def prestacion_delete(concept_id: str):
|
|
436
|
+
"""Delete a benefit. Admin and HR can delete benefits."""
|
|
437
|
+
return delete_concept("prestacion", concept_id)
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
# ============================================================================
|
|
441
|
+
# APPROVAL ROUTES (for all concept types)
|
|
442
|
+
# ============================================================================
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def approve_concept_route(concept_type: str, concept_id: str):
|
|
446
|
+
"""Generic approval route for payroll concepts."""
|
|
447
|
+
# Check if user can approve
|
|
448
|
+
if not puede_aprobar_concepto(current_user.tipo):
|
|
449
|
+
flash(_("No tiene permisos para aprobar conceptos de nómina."), "error")
|
|
450
|
+
return redirect(url_for(f"{concept_type}.{concept_type}_index"))
|
|
451
|
+
|
|
452
|
+
config = get_concept_config(concept_type)
|
|
453
|
+
Model = config["model"]
|
|
454
|
+
|
|
455
|
+
concept = db.session.get(Model, concept_id)
|
|
456
|
+
if not concept:
|
|
457
|
+
flash(_(ERROR_CONCEPT_NOT_FOUND, type=config["singular"]), "error")
|
|
458
|
+
return redirect(url_for(f"{config['blueprint']}.{concept_type}_index"))
|
|
459
|
+
|
|
460
|
+
# Approve the concept
|
|
461
|
+
if aprobar_concepto(concept, current_user.usuario):
|
|
462
|
+
db.session.commit()
|
|
463
|
+
flash(_("%(type)s aprobada exitosamente.", type=config["singular"]), "success")
|
|
464
|
+
else:
|
|
465
|
+
flash(_("%(type)s ya está aprobada.", type=config["singular"]), "info")
|
|
466
|
+
|
|
467
|
+
return redirect(url_for(f"{config['blueprint']}.{concept_type}_index"))
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
def reject_concept_route(concept_type: str, concept_id: str):
|
|
471
|
+
"""Generic rejection route for payroll concepts."""
|
|
472
|
+
# Check if user can approve/reject
|
|
473
|
+
if not puede_aprobar_concepto(current_user.tipo):
|
|
474
|
+
flash(_("No tiene permisos para rechazar conceptos de nómina."), "error")
|
|
475
|
+
return redirect(url_for(f"{concept_type}.{concept_type}_index"))
|
|
476
|
+
|
|
477
|
+
config = get_concept_config(concept_type)
|
|
478
|
+
Model = config["model"]
|
|
479
|
+
|
|
480
|
+
concept = db.session.get(Model, concept_id)
|
|
481
|
+
if not concept:
|
|
482
|
+
flash(_(ERROR_CONCEPT_NOT_FOUND, type=config["singular"]), "error")
|
|
483
|
+
return redirect(url_for(f"{config['blueprint']}.{concept_type}_index"))
|
|
484
|
+
|
|
485
|
+
# Get rejection reason from form
|
|
486
|
+
razon = request.form.get("razon", "")
|
|
487
|
+
|
|
488
|
+
# Reject the concept
|
|
489
|
+
rechazar_concepto(concept, current_user.usuario, razon)
|
|
490
|
+
db.session.commit()
|
|
491
|
+
flash(_("%(type)s marcada como borrador.", type=config["singular"]), "warning")
|
|
492
|
+
|
|
493
|
+
return redirect(url_for(f"{config['blueprint']}.{concept_type}_index"))
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
def view_audit_log_route(concept_type: str, concept_id: str):
|
|
497
|
+
"""View audit log for a specific concept."""
|
|
498
|
+
config = get_concept_config(concept_type)
|
|
499
|
+
Model = config["model"]
|
|
500
|
+
|
|
501
|
+
concept = db.session.get(Model, concept_id)
|
|
502
|
+
if not concept:
|
|
503
|
+
flash(_(ERROR_CONCEPT_NOT_FOUND, type=config["singular"]), "error")
|
|
504
|
+
return redirect(url_for(f"{config['blueprint']}.{concept_type}_index"))
|
|
505
|
+
|
|
506
|
+
# Get audit logs
|
|
507
|
+
audit_logs = sorted(concept.audit_logs, key=lambda x: x.timestamp, reverse=True)
|
|
508
|
+
|
|
509
|
+
return render_template(
|
|
510
|
+
"modules/payroll_concepts/audit_log.html",
|
|
511
|
+
concept=concept,
|
|
512
|
+
audit_logs=audit_logs,
|
|
513
|
+
config=config,
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
# Percepcion approval routes
|
|
518
|
+
@percepcion_bp.route("/approve/<string:concept_id>", methods=["POST"])
|
|
519
|
+
@require_write_access()
|
|
520
|
+
def percepcion_approve(concept_id: str):
|
|
521
|
+
"""Approve a perception. Only ADMIN and HHRR can approve."""
|
|
522
|
+
return approve_concept_route("percepcion", concept_id)
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
@percepcion_bp.route("/reject/<string:concept_id>", methods=["POST"])
|
|
526
|
+
@require_write_access()
|
|
527
|
+
def percepcion_reject(concept_id: str):
|
|
528
|
+
"""Reject a perception. Only ADMIN and HHRR can reject."""
|
|
529
|
+
return reject_concept_route("percepcion", concept_id)
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
@percepcion_bp.route("/audit/<string:concept_id>")
|
|
533
|
+
@require_read_access()
|
|
534
|
+
def percepcion_audit(concept_id: str):
|
|
535
|
+
"""View audit log for a perception."""
|
|
536
|
+
return view_audit_log_route("percepcion", concept_id)
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
# Deduccion approval routes
|
|
540
|
+
@deduccion_bp.route("/approve/<string:concept_id>", methods=["POST"])
|
|
541
|
+
@require_write_access()
|
|
542
|
+
def deduccion_approve(concept_id: str):
|
|
543
|
+
"""Approve a deduction. Only ADMIN and HHRR can approve."""
|
|
544
|
+
return approve_concept_route("deduccion", concept_id)
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
@deduccion_bp.route("/reject/<string:concept_id>", methods=["POST"])
|
|
548
|
+
@require_write_access()
|
|
549
|
+
def deduccion_reject(concept_id: str):
|
|
550
|
+
"""Reject a deduction. Only ADMIN and HHRR can reject."""
|
|
551
|
+
return reject_concept_route("deduccion", concept_id)
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
@deduccion_bp.route("/audit/<string:concept_id>")
|
|
555
|
+
@require_read_access()
|
|
556
|
+
def deduccion_audit(concept_id: str):
|
|
557
|
+
"""View audit log for a deduction."""
|
|
558
|
+
return view_audit_log_route("deduccion", concept_id)
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
# Prestacion approval routes
|
|
562
|
+
@prestacion_bp.route("/approve/<string:concept_id>", methods=["POST"])
|
|
563
|
+
@require_write_access()
|
|
564
|
+
def prestacion_approve(concept_id: str):
|
|
565
|
+
"""Approve a benefit. Only ADMIN and HHRR can approve."""
|
|
566
|
+
return approve_concept_route("prestacion", concept_id)
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
@prestacion_bp.route("/reject/<string:concept_id>", methods=["POST"])
|
|
570
|
+
@require_write_access()
|
|
571
|
+
def prestacion_reject(concept_id: str):
|
|
572
|
+
"""Reject a benefit. Only ADMIN and HHRR can reject."""
|
|
573
|
+
return reject_concept_route("prestacion", concept_id)
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
@prestacion_bp.route("/audit/<string:concept_id>")
|
|
577
|
+
@require_read_access()
|
|
578
|
+
def prestacion_audit(concept_id: str):
|
|
579
|
+
"""View audit log for a benefit."""
|
|
580
|
+
return view_audit_log_route("prestacion", concept_id)
|
|
@@ -0,0 +1,38 @@
|
|
|
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
|
+
"""Views for managing Planilla (master payroll) and its associations.
|
|
15
|
+
|
|
16
|
+
A Planilla is the central hub that connects:
|
|
17
|
+
- Employees (via PlanillaEmpleado)
|
|
18
|
+
- Perceptions (via PlanillaIngreso)
|
|
19
|
+
- Deductions (via PlanillaDeduccion) - with priority ordering
|
|
20
|
+
- Benefits/Prestaciones (via PlanillaPrestacion)
|
|
21
|
+
- Calculation Rules (via PlanillaReglaCalculo)
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from flask import Blueprint
|
|
25
|
+
|
|
26
|
+
# Create the blueprint
|
|
27
|
+
planilla_bp = Blueprint("planilla", __name__, url_prefix="/planilla")
|
|
28
|
+
|
|
29
|
+
# Import all route modules to register them with the blueprint
|
|
30
|
+
# This must be done after creating the blueprint
|
|
31
|
+
from coati_payroll.vistas.planilla import routes # noqa: E402, F401
|
|
32
|
+
from coati_payroll.vistas.planilla import config_routes # noqa: E402, F401
|
|
33
|
+
from coati_payroll.vistas.planilla import association_routes # noqa: E402, F401
|
|
34
|
+
from coati_payroll.vistas.planilla import nomina_routes # noqa: E402, F401
|
|
35
|
+
from coati_payroll.vistas.planilla import novedad_routes # noqa: E402, F401
|
|
36
|
+
from coati_payroll.vistas.planilla import export_routes # noqa: E402, F401
|
|
37
|
+
|
|
38
|
+
__all__ = ["planilla_bp"]
|