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,423 @@
|
|
|
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
|
+
"""Initial benefit balance loading views."""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from decimal import Decimal
|
|
19
|
+
from datetime import datetime
|
|
20
|
+
|
|
21
|
+
from flask import Blueprint, flash, redirect, render_template, request, url_for, Response
|
|
22
|
+
from flask_login import current_user
|
|
23
|
+
from sqlalchemy import and_
|
|
24
|
+
|
|
25
|
+
from coati_payroll.forms import CargaInicialPrestacionForm
|
|
26
|
+
from coati_payroll.i18n import _
|
|
27
|
+
from coati_payroll.model import (
|
|
28
|
+
CargaInicialPrestacion,
|
|
29
|
+
Empleado,
|
|
30
|
+
Moneda,
|
|
31
|
+
Prestacion,
|
|
32
|
+
PrestacionAcumulada,
|
|
33
|
+
db,
|
|
34
|
+
)
|
|
35
|
+
from coati_payroll.rbac import require_read_access, require_write_access
|
|
36
|
+
from coati_payroll.vistas.constants import PER_PAGE
|
|
37
|
+
|
|
38
|
+
carga_inicial_prestacion_bp = Blueprint("carga_inicial_prestacion", __name__, url_prefix="/carga-inicial-prestaciones")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@carga_inicial_prestacion_bp.route("/")
|
|
42
|
+
@require_read_access()
|
|
43
|
+
def index():
|
|
44
|
+
"""List all initial benefit balance loads."""
|
|
45
|
+
page = request.args.get("page", 1, type=int)
|
|
46
|
+
estado_filter = request.args.get("estado", "")
|
|
47
|
+
|
|
48
|
+
query = CargaInicialPrestacion.query
|
|
49
|
+
|
|
50
|
+
# Apply filters
|
|
51
|
+
if estado_filter:
|
|
52
|
+
query = query.filter(CargaInicialPrestacion.estado == estado_filter)
|
|
53
|
+
|
|
54
|
+
# Order by creation date descending
|
|
55
|
+
query = query.order_by(CargaInicialPrestacion.creado.desc())
|
|
56
|
+
|
|
57
|
+
# Paginate
|
|
58
|
+
pagination = query.paginate(page=page, per_page=PER_PAGE, error_out=False)
|
|
59
|
+
cargas = pagination.items
|
|
60
|
+
|
|
61
|
+
return render_template(
|
|
62
|
+
"modules/carga_inicial_prestacion/index.html",
|
|
63
|
+
cargas=cargas,
|
|
64
|
+
pagination=pagination,
|
|
65
|
+
estado_filter=estado_filter,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@carga_inicial_prestacion_bp.route("/nueva", methods=["GET", "POST"])
|
|
70
|
+
@require_write_access()
|
|
71
|
+
def nueva():
|
|
72
|
+
"""Create a new initial benefit balance load."""
|
|
73
|
+
form = CargaInicialPrestacionForm()
|
|
74
|
+
|
|
75
|
+
# Populate select field choices
|
|
76
|
+
form.empleado_id.choices = [("", _("-- Seleccionar --"))] + [
|
|
77
|
+
(emp.id, f"{emp.codigo_empleado} - {emp.primer_nombre} {emp.primer_apellido}")
|
|
78
|
+
for emp in Empleado.query.filter_by(activo=True).order_by(Empleado.codigo_empleado).all()
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
form.prestacion_id.choices = [("", _("-- Seleccionar --"))] + [
|
|
82
|
+
(prest.id, f"{prest.codigo} - {prest.nombre}")
|
|
83
|
+
for prest in Prestacion.query.filter_by(activo=True).order_by(Prestacion.codigo).all()
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
form.moneda_id.choices = [("", _("-- Seleccionar --"))] + [
|
|
87
|
+
(mon.id, f"{mon.codigo} - {mon.nombre}")
|
|
88
|
+
for mon in Moneda.query.filter_by(activo=True).order_by(Moneda.codigo).all()
|
|
89
|
+
]
|
|
90
|
+
|
|
91
|
+
if form.validate_on_submit():
|
|
92
|
+
# Check for duplicate
|
|
93
|
+
existing = CargaInicialPrestacion.query.filter(
|
|
94
|
+
and_(
|
|
95
|
+
CargaInicialPrestacion.empleado_id == form.empleado_id.data,
|
|
96
|
+
CargaInicialPrestacion.prestacion_id == form.prestacion_id.data,
|
|
97
|
+
CargaInicialPrestacion.anio_corte == form.anio_corte.data,
|
|
98
|
+
CargaInicialPrestacion.mes_corte == form.mes_corte.data,
|
|
99
|
+
)
|
|
100
|
+
).first()
|
|
101
|
+
|
|
102
|
+
if existing:
|
|
103
|
+
flash(
|
|
104
|
+
_("Ya existe una carga inicial para este empleado, prestación y periodo."),
|
|
105
|
+
"warning",
|
|
106
|
+
)
|
|
107
|
+
return render_template("modules/carga_inicial_prestacion/form.html", form=form)
|
|
108
|
+
|
|
109
|
+
carga = CargaInicialPrestacion(
|
|
110
|
+
empleado_id=form.empleado_id.data,
|
|
111
|
+
prestacion_id=form.prestacion_id.data,
|
|
112
|
+
anio_corte=form.anio_corte.data,
|
|
113
|
+
mes_corte=form.mes_corte.data,
|
|
114
|
+
moneda_id=form.moneda_id.data,
|
|
115
|
+
saldo_acumulado=form.saldo_acumulado.data if form.saldo_acumulado.data is not None else Decimal("0.00"),
|
|
116
|
+
tipo_cambio=form.tipo_cambio.data if form.tipo_cambio.data is not None else Decimal("1.0"),
|
|
117
|
+
saldo_convertido=form.saldo_convertido.data if form.saldo_convertido.data is not None else Decimal("0.00"),
|
|
118
|
+
observaciones=form.observaciones.data,
|
|
119
|
+
estado="borrador",
|
|
120
|
+
creado_por=current_user.usuario if current_user.is_authenticated else None,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
db.session.add(carga)
|
|
124
|
+
db.session.commit()
|
|
125
|
+
|
|
126
|
+
flash(_("Carga inicial creada exitosamente en estado borrador."), "success")
|
|
127
|
+
return redirect(url_for("carga_inicial_prestacion.index"))
|
|
128
|
+
|
|
129
|
+
return render_template("modules/carga_inicial_prestacion/form.html", form=form)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@carga_inicial_prestacion_bp.route("/<carga_id>/editar", methods=["GET", "POST"])
|
|
133
|
+
@require_write_access()
|
|
134
|
+
def editar(carga_id):
|
|
135
|
+
"""Edit an initial benefit balance load (only if in draft status)."""
|
|
136
|
+
carga = CargaInicialPrestacion.query.get_or_404(carga_id)
|
|
137
|
+
|
|
138
|
+
if carga.estado == "aplicado":
|
|
139
|
+
flash(_("No se puede editar una carga inicial ya aplicada."), "warning")
|
|
140
|
+
return redirect(url_for("carga_inicial_prestacion.index"))
|
|
141
|
+
|
|
142
|
+
form = CargaInicialPrestacionForm(obj=carga)
|
|
143
|
+
|
|
144
|
+
# Populate select field choices
|
|
145
|
+
form.empleado_id.choices = [("", _("-- Seleccionar --"))] + [
|
|
146
|
+
(emp.id, f"{emp.codigo_empleado} - {emp.primer_nombre} {emp.primer_apellido}")
|
|
147
|
+
for emp in Empleado.query.filter_by(activo=True).order_by(Empleado.codigo_empleado).all()
|
|
148
|
+
]
|
|
149
|
+
|
|
150
|
+
form.prestacion_id.choices = [("", _("-- Seleccionar --"))] + [
|
|
151
|
+
(prest.id, f"{prest.codigo} - {prest.nombre}")
|
|
152
|
+
for prest in Prestacion.query.filter_by(activo=True).order_by(Prestacion.codigo).all()
|
|
153
|
+
]
|
|
154
|
+
|
|
155
|
+
form.moneda_id.choices = [("", _("-- Seleccionar --"))] + [
|
|
156
|
+
(mon.id, f"{mon.codigo} - {mon.nombre}")
|
|
157
|
+
for mon in Moneda.query.filter_by(activo=True).order_by(Moneda.codigo).all()
|
|
158
|
+
]
|
|
159
|
+
|
|
160
|
+
if form.validate_on_submit():
|
|
161
|
+
carga.empleado_id = form.empleado_id.data
|
|
162
|
+
carga.prestacion_id = form.prestacion_id.data
|
|
163
|
+
carga.anio_corte = form.anio_corte.data
|
|
164
|
+
carga.mes_corte = form.mes_corte.data
|
|
165
|
+
carga.moneda_id = form.moneda_id.data
|
|
166
|
+
carga.saldo_acumulado = form.saldo_acumulado.data if form.saldo_acumulado.data is not None else Decimal("0.00")
|
|
167
|
+
carga.tipo_cambio = form.tipo_cambio.data if form.tipo_cambio.data is not None else Decimal("1.0")
|
|
168
|
+
carga.saldo_convertido = (
|
|
169
|
+
form.saldo_convertido.data if form.saldo_convertido.data is not None else Decimal("0.00")
|
|
170
|
+
)
|
|
171
|
+
carga.observaciones = form.observaciones.data
|
|
172
|
+
carga.modificado_por = current_user.usuario if current_user.is_authenticated else None
|
|
173
|
+
|
|
174
|
+
db.session.commit()
|
|
175
|
+
|
|
176
|
+
flash(_("Carga inicial actualizada exitosamente."), "success")
|
|
177
|
+
return redirect(url_for("carga_inicial_prestacion.index"))
|
|
178
|
+
|
|
179
|
+
return render_template("modules/carga_inicial_prestacion/form.html", form=form, carga=carga)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
@carga_inicial_prestacion_bp.route("/<carga_id>/aplicar", methods=["POST"])
|
|
183
|
+
@require_write_access()
|
|
184
|
+
def aplicar(carga_id):
|
|
185
|
+
"""Apply an initial balance load - creates transaction in prestacion_acumulada."""
|
|
186
|
+
carga = CargaInicialPrestacion.query.get_or_404(carga_id)
|
|
187
|
+
|
|
188
|
+
if carga.estado == "aplicado":
|
|
189
|
+
flash(_("Esta carga inicial ya ha sido aplicada."), "warning")
|
|
190
|
+
return redirect(url_for("carga_inicial_prestacion.index"))
|
|
191
|
+
|
|
192
|
+
try:
|
|
193
|
+
# Create transaction in prestacion_acumulada
|
|
194
|
+
transaccion = PrestacionAcumulada(
|
|
195
|
+
empleado_id=carga.empleado_id,
|
|
196
|
+
prestacion_id=carga.prestacion_id,
|
|
197
|
+
fecha_transaccion=datetime.now().date(),
|
|
198
|
+
tipo_transaccion="saldo_inicial",
|
|
199
|
+
anio=carga.anio_corte,
|
|
200
|
+
mes=carga.mes_corte,
|
|
201
|
+
moneda_id=carga.moneda_id,
|
|
202
|
+
monto_transaccion=carga.saldo_convertido,
|
|
203
|
+
saldo_anterior=Decimal("0.00"),
|
|
204
|
+
saldo_nuevo=carga.saldo_convertido,
|
|
205
|
+
carga_inicial_id=carga.id,
|
|
206
|
+
observaciones=f"Carga inicial - {carga.observaciones or ''}",
|
|
207
|
+
procesado_por=current_user.usuario if current_user.is_authenticated else None,
|
|
208
|
+
creado_por=current_user.usuario if current_user.is_authenticated else None,
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
db.session.add(transaccion)
|
|
212
|
+
|
|
213
|
+
# Update carga status
|
|
214
|
+
carga.estado = "aplicado"
|
|
215
|
+
carga.fecha_aplicacion = datetime.now()
|
|
216
|
+
carga.aplicado_por = current_user.usuario if current_user.is_authenticated else None
|
|
217
|
+
carga.modificado_por = current_user.usuario if current_user.is_authenticated else None
|
|
218
|
+
|
|
219
|
+
db.session.commit()
|
|
220
|
+
|
|
221
|
+
flash(_("Carga inicial aplicada exitosamente."), "success")
|
|
222
|
+
|
|
223
|
+
except Exception as e:
|
|
224
|
+
db.session.rollback()
|
|
225
|
+
flash(_("Error al aplicar la carga inicial: %(error)s", error=str(e)), "danger")
|
|
226
|
+
|
|
227
|
+
return redirect(url_for("carga_inicial_prestacion.index"))
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
@carga_inicial_prestacion_bp.route("/<carga_id>/eliminar", methods=["POST"])
|
|
231
|
+
@require_write_access()
|
|
232
|
+
def eliminar(carga_id):
|
|
233
|
+
"""Delete an initial balance load (only if in draft status)."""
|
|
234
|
+
carga = CargaInicialPrestacion.query.get_or_404(carga_id)
|
|
235
|
+
|
|
236
|
+
if carga.estado == "aplicado":
|
|
237
|
+
flash(_("No se puede eliminar una carga inicial ya aplicada."), "warning")
|
|
238
|
+
return redirect(url_for("carga_inicial_prestacion.index"))
|
|
239
|
+
|
|
240
|
+
try:
|
|
241
|
+
db.session.delete(carga)
|
|
242
|
+
db.session.commit()
|
|
243
|
+
flash(_("Carga inicial eliminada exitosamente."), "success")
|
|
244
|
+
except Exception as e:
|
|
245
|
+
db.session.rollback()
|
|
246
|
+
flash(_("Error al eliminar la carga inicial: %(error)s", error=str(e)), "danger")
|
|
247
|
+
|
|
248
|
+
return redirect(url_for("carga_inicial_prestacion.index"))
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
@carga_inicial_prestacion_bp.route("/reporte")
|
|
252
|
+
@require_read_access()
|
|
253
|
+
def reporte():
|
|
254
|
+
"""Generate accumulated benefits report."""
|
|
255
|
+
# Get filter parameters
|
|
256
|
+
empleado_id = request.args.get("empleado_id")
|
|
257
|
+
prestacion_id = request.args.get("prestacion_id")
|
|
258
|
+
fecha_desde = request.args.get("fecha_desde")
|
|
259
|
+
fecha_hasta = request.args.get("fecha_hasta")
|
|
260
|
+
|
|
261
|
+
# Build query
|
|
262
|
+
query = PrestacionAcumulada.query
|
|
263
|
+
|
|
264
|
+
if empleado_id:
|
|
265
|
+
query = query.filter(PrestacionAcumulada.empleado_id == empleado_id)
|
|
266
|
+
|
|
267
|
+
if prestacion_id:
|
|
268
|
+
query = query.filter(PrestacionAcumulada.prestacion_id == prestacion_id)
|
|
269
|
+
|
|
270
|
+
if fecha_desde:
|
|
271
|
+
query = query.filter(PrestacionAcumulada.fecha_transaccion >= fecha_desde)
|
|
272
|
+
|
|
273
|
+
if fecha_hasta:
|
|
274
|
+
query = query.filter(PrestacionAcumulada.fecha_transaccion <= fecha_hasta)
|
|
275
|
+
|
|
276
|
+
# Order by date
|
|
277
|
+
transacciones = query.order_by(
|
|
278
|
+
PrestacionAcumulada.empleado_id, PrestacionAcumulada.prestacion_id, PrestacionAcumulada.fecha_transaccion
|
|
279
|
+
).all()
|
|
280
|
+
|
|
281
|
+
# Get choices for filters
|
|
282
|
+
empleados = Empleado.query.filter_by(activo=True).order_by(Empleado.codigo_empleado).all()
|
|
283
|
+
prestaciones = Prestacion.query.filter_by(activo=True).order_by(Prestacion.codigo).all()
|
|
284
|
+
|
|
285
|
+
return render_template(
|
|
286
|
+
"modules/carga_inicial_prestacion/reporte.html",
|
|
287
|
+
transacciones=transacciones,
|
|
288
|
+
empleados=empleados,
|
|
289
|
+
prestaciones=prestaciones,
|
|
290
|
+
empleado_id=empleado_id,
|
|
291
|
+
prestacion_id=prestacion_id,
|
|
292
|
+
fecha_desde=fecha_desde,
|
|
293
|
+
fecha_hasta=fecha_hasta,
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
@carga_inicial_prestacion_bp.route("/reporte/excel")
|
|
298
|
+
@require_read_access()
|
|
299
|
+
def reporte_excel():
|
|
300
|
+
"""Export accumulated benefits report to Excel."""
|
|
301
|
+
import io
|
|
302
|
+
|
|
303
|
+
try:
|
|
304
|
+
from openpyxl import Workbook
|
|
305
|
+
from openpyxl.styles import Font, PatternFill
|
|
306
|
+
except ImportError:
|
|
307
|
+
flash(_("La librería openpyxl no está instalada. No se puede generar el reporte Excel."), "danger")
|
|
308
|
+
return redirect(url_for("carga_inicial_prestacion.reporte"))
|
|
309
|
+
|
|
310
|
+
# Get filter parameters (same as reporte)
|
|
311
|
+
empleado_id = request.args.get("empleado_id")
|
|
312
|
+
prestacion_id = request.args.get("prestacion_id")
|
|
313
|
+
fecha_desde = request.args.get("fecha_desde")
|
|
314
|
+
fecha_hasta = request.args.get("fecha_hasta")
|
|
315
|
+
|
|
316
|
+
# Build query
|
|
317
|
+
query = PrestacionAcumulada.query
|
|
318
|
+
|
|
319
|
+
if empleado_id:
|
|
320
|
+
query = query.filter(PrestacionAcumulada.empleado_id == empleado_id)
|
|
321
|
+
|
|
322
|
+
if prestacion_id:
|
|
323
|
+
query = query.filter(PrestacionAcumulada.prestacion_id == prestacion_id)
|
|
324
|
+
|
|
325
|
+
if fecha_desde:
|
|
326
|
+
query = query.filter(PrestacionAcumulada.fecha_transaccion >= fecha_desde)
|
|
327
|
+
|
|
328
|
+
if fecha_hasta:
|
|
329
|
+
query = query.filter(PrestacionAcumulada.fecha_transaccion <= fecha_hasta)
|
|
330
|
+
|
|
331
|
+
transacciones = query.order_by(
|
|
332
|
+
PrestacionAcumulada.empleado_id, PrestacionAcumulada.prestacion_id, PrestacionAcumulada.fecha_transaccion
|
|
333
|
+
).all()
|
|
334
|
+
|
|
335
|
+
# Create Excel workbook
|
|
336
|
+
wb = Workbook()
|
|
337
|
+
ws = wb.active
|
|
338
|
+
ws.title = "Prestaciones Acumuladas"
|
|
339
|
+
|
|
340
|
+
# Header style
|
|
341
|
+
header_fill = PatternFill(start_color="366092", end_color="366092", fill_type="solid")
|
|
342
|
+
header_font = Font(color="FFFFFF", bold=True)
|
|
343
|
+
|
|
344
|
+
# Headers - Enhanced for audit purposes
|
|
345
|
+
headers = [
|
|
346
|
+
"ID Transacción",
|
|
347
|
+
"Fecha Transacción",
|
|
348
|
+
"Código Empleado",
|
|
349
|
+
"Empleado",
|
|
350
|
+
"Código Prestación",
|
|
351
|
+
"Prestación",
|
|
352
|
+
"Tipo Acumulación",
|
|
353
|
+
"Tipo Transacción",
|
|
354
|
+
"Año",
|
|
355
|
+
"Mes",
|
|
356
|
+
"Monto Transacción",
|
|
357
|
+
"Saldo Anterior",
|
|
358
|
+
"Saldo Nuevo",
|
|
359
|
+
"Moneda",
|
|
360
|
+
"Nómina ID",
|
|
361
|
+
"Carga Inicial ID",
|
|
362
|
+
"Procesado Por",
|
|
363
|
+
"Fecha Creación",
|
|
364
|
+
"Creado Por",
|
|
365
|
+
"Observaciones",
|
|
366
|
+
]
|
|
367
|
+
|
|
368
|
+
for col_num, header in enumerate(headers, 1):
|
|
369
|
+
cell = ws.cell(row=1, column=col_num, value=header)
|
|
370
|
+
cell.fill = header_fill
|
|
371
|
+
cell.font = header_font
|
|
372
|
+
|
|
373
|
+
# Data rows - Enhanced with all audit fields
|
|
374
|
+
for row_num, trans in enumerate(transacciones, 2):
|
|
375
|
+
ws.cell(row=row_num, column=1, value=trans.id)
|
|
376
|
+
ws.cell(row=row_num, column=2, value=trans.fecha_transaccion.strftime("%Y-%m-%d"))
|
|
377
|
+
ws.cell(row=row_num, column=3, value=trans.empleado.codigo_empleado)
|
|
378
|
+
ws.cell(
|
|
379
|
+
row=row_num,
|
|
380
|
+
column=4,
|
|
381
|
+
value=f"{trans.empleado.primer_nombre} {trans.empleado.primer_apellido}",
|
|
382
|
+
)
|
|
383
|
+
ws.cell(row=row_num, column=5, value=trans.prestacion.codigo)
|
|
384
|
+
ws.cell(row=row_num, column=6, value=trans.prestacion.nombre)
|
|
385
|
+
ws.cell(row=row_num, column=7, value=trans.prestacion.tipo_acumulacion)
|
|
386
|
+
ws.cell(row=row_num, column=8, value=trans.tipo_transaccion)
|
|
387
|
+
ws.cell(row=row_num, column=9, value=trans.anio)
|
|
388
|
+
ws.cell(row=row_num, column=10, value=trans.mes)
|
|
389
|
+
ws.cell(row=row_num, column=11, value=float(trans.monto_transaccion))
|
|
390
|
+
ws.cell(row=row_num, column=12, value=float(trans.saldo_anterior))
|
|
391
|
+
ws.cell(row=row_num, column=13, value=float(trans.saldo_nuevo))
|
|
392
|
+
ws.cell(row=row_num, column=14, value=trans.moneda.codigo)
|
|
393
|
+
ws.cell(row=row_num, column=15, value=trans.nomina_id or "")
|
|
394
|
+
ws.cell(row=row_num, column=16, value=trans.carga_inicial_id or "")
|
|
395
|
+
ws.cell(row=row_num, column=17, value=trans.procesado_por or "")
|
|
396
|
+
ws.cell(row=row_num, column=18, value=trans.creado.strftime("%Y-%m-%d"))
|
|
397
|
+
ws.cell(row=row_num, column=19, value=trans.creado_por or "")
|
|
398
|
+
ws.cell(row=row_num, column=20, value=trans.observaciones or "")
|
|
399
|
+
|
|
400
|
+
# Auto-adjust column widths
|
|
401
|
+
for column in ws.columns:
|
|
402
|
+
max_length = 0
|
|
403
|
+
column_letter = column[0].column_letter
|
|
404
|
+
for cell in column:
|
|
405
|
+
try:
|
|
406
|
+
if cell.value is not None and len(str(cell.value)) > max_length:
|
|
407
|
+
max_length = len(cell.value)
|
|
408
|
+
except (TypeError, AttributeError):
|
|
409
|
+
# Skip cells with values that can't be converted to string
|
|
410
|
+
pass
|
|
411
|
+
adjusted_width = min(max_length + 2, 50)
|
|
412
|
+
ws.column_dimensions[column_letter].width = adjusted_width
|
|
413
|
+
|
|
414
|
+
# Save to BytesIO
|
|
415
|
+
output = io.BytesIO()
|
|
416
|
+
wb.save(output)
|
|
417
|
+
output.seek(0)
|
|
418
|
+
|
|
419
|
+
return Response(
|
|
420
|
+
output.read(),
|
|
421
|
+
mimetype="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
422
|
+
headers={"Content-Disposition": "attachment;filename=prestaciones_acumuladas.xlsx"},
|
|
423
|
+
)
|
|
@@ -0,0 +1,72 @@
|
|
|
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
|
+
|
|
15
|
+
"""Configuración de parámetros de cálculo."""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from flask import Blueprint, flash, redirect, render_template, url_for
|
|
20
|
+
|
|
21
|
+
from coati_payroll.forms import ConfiguracionCalculosForm
|
|
22
|
+
from coati_payroll.i18n import _
|
|
23
|
+
from coati_payroll.model import ConfiguracionCalculos, db
|
|
24
|
+
from coati_payroll.rbac import require_write_access
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
config_calculos_bp = Blueprint("config_calculos", __name__, url_prefix="/config-calculos")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _get_or_create_global_config() -> ConfiguracionCalculos:
|
|
31
|
+
"""Get or create the global (empresa=None, pais=None) calculation config."""
|
|
32
|
+
config = db.session.execute(
|
|
33
|
+
db.select(ConfiguracionCalculos).filter(
|
|
34
|
+
ConfiguracionCalculos.empresa_id.is_(None),
|
|
35
|
+
ConfiguracionCalculos.pais_id.is_(None),
|
|
36
|
+
ConfiguracionCalculos.activo.is_(True),
|
|
37
|
+
)
|
|
38
|
+
).scalar_one_or_none()
|
|
39
|
+
|
|
40
|
+
if config:
|
|
41
|
+
return config
|
|
42
|
+
|
|
43
|
+
config = ConfiguracionCalculos(
|
|
44
|
+
empresa_id=None,
|
|
45
|
+
pais_id=None,
|
|
46
|
+
activo=True,
|
|
47
|
+
)
|
|
48
|
+
db.session.add(config)
|
|
49
|
+
db.session.commit()
|
|
50
|
+
return config
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@config_calculos_bp.route("/", methods=["GET", "POST"])
|
|
54
|
+
@require_write_access()
|
|
55
|
+
def index():
|
|
56
|
+
"""Edit global calculation parameters."""
|
|
57
|
+
config = _get_or_create_global_config()
|
|
58
|
+
form = ConfiguracionCalculosForm(obj=config)
|
|
59
|
+
|
|
60
|
+
if form.validate_on_submit():
|
|
61
|
+
config.liquidacion_modo_dias = form.liquidacion_modo_dias.data
|
|
62
|
+
config.liquidacion_factor_calendario = form.liquidacion_factor_calendario.data
|
|
63
|
+
config.liquidacion_factor_laboral = form.liquidacion_factor_laboral.data
|
|
64
|
+
db.session.commit()
|
|
65
|
+
flash(_("Configuración de cálculos actualizada."), "success")
|
|
66
|
+
return redirect(url_for("config_calculos.index"))
|
|
67
|
+
|
|
68
|
+
return render_template(
|
|
69
|
+
"modules/config_calculos/index.html",
|
|
70
|
+
form=form,
|
|
71
|
+
config=config,
|
|
72
|
+
)
|
|
@@ -0,0 +1,87 @@
|
|
|
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
|
+
"""Global configuration views."""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
# <-------------------------------------------------------------------------> #
|
|
19
|
+
# Standard library
|
|
20
|
+
# <-------------------------------------------------------------------------> #
|
|
21
|
+
|
|
22
|
+
# <-------------------------------------------------------------------------> #
|
|
23
|
+
# Third party libraries
|
|
24
|
+
# <-------------------------------------------------------------------------> #
|
|
25
|
+
from flask import Blueprint, flash, redirect, render_template, request, url_for
|
|
26
|
+
|
|
27
|
+
# <-------------------------------------------------------------------------> #
|
|
28
|
+
# Local modules
|
|
29
|
+
# <-------------------------------------------------------------------------> #
|
|
30
|
+
from coati_payroll.i18n import _
|
|
31
|
+
from coati_payroll.rbac import require_write_access
|
|
32
|
+
from coati_payroll.locale_config import (
|
|
33
|
+
SUPPORTED_LANGUAGES,
|
|
34
|
+
get_language_from_db,
|
|
35
|
+
set_language_in_db,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
configuracion_bp = Blueprint("configuracion", __name__, url_prefix="/configuracion")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@configuracion_bp.route("/")
|
|
42
|
+
@require_write_access()
|
|
43
|
+
def index():
|
|
44
|
+
"""Display global configuration page."""
|
|
45
|
+
current_language = get_language_from_db()
|
|
46
|
+
|
|
47
|
+
# Language names for display
|
|
48
|
+
language_names = {"en": "English", "es": "Español"}
|
|
49
|
+
|
|
50
|
+
return render_template(
|
|
51
|
+
"modules/configuracion/index.html",
|
|
52
|
+
current_language=current_language,
|
|
53
|
+
supported_languages=SUPPORTED_LANGUAGES,
|
|
54
|
+
language_names=language_names,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@configuracion_bp.route("/idioma", methods=["POST"])
|
|
59
|
+
@require_write_access()
|
|
60
|
+
def cambiar_idioma():
|
|
61
|
+
"""Change the application language."""
|
|
62
|
+
new_language = request.form.get("idioma", "").strip()
|
|
63
|
+
|
|
64
|
+
if not new_language:
|
|
65
|
+
flash(_("Por favor seleccione un idioma."), "warning")
|
|
66
|
+
return redirect(url_for("configuracion.index"))
|
|
67
|
+
|
|
68
|
+
if new_language not in SUPPORTED_LANGUAGES:
|
|
69
|
+
flash(_("Idioma no soportado."), "danger")
|
|
70
|
+
return redirect(url_for("configuracion.index"))
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
set_language_in_db(new_language)
|
|
74
|
+
|
|
75
|
+
# Message will be shown in the new language after redirect
|
|
76
|
+
language_names = {"en": "English", "es": "Español"}
|
|
77
|
+
flash(
|
|
78
|
+
_(
|
|
79
|
+
"Idioma actualizado a %(language)s.",
|
|
80
|
+
language=language_names[new_language],
|
|
81
|
+
),
|
|
82
|
+
"success",
|
|
83
|
+
)
|
|
84
|
+
except Exception as e:
|
|
85
|
+
flash(_("Error al actualizar el idioma: %(error)s", error=str(e)), "danger")
|
|
86
|
+
|
|
87
|
+
return redirect(url_for("configuracion.index"))
|
|
@@ -0,0 +1,17 @@
|
|
|
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
|
+
"""Constants for the vistas module."""
|
|
15
|
+
|
|
16
|
+
# Number of items per page for pagination
|
|
17
|
+
PER_PAGE = 10
|