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,112 @@
|
|
|
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
|
+
"""Currency CRUD routes."""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from flask import Blueprint, flash, redirect, render_template, request, url_for
|
|
19
|
+
from flask_login import current_user
|
|
20
|
+
|
|
21
|
+
from coati_payroll.forms import CurrencyForm
|
|
22
|
+
from coati_payroll.i18n import _
|
|
23
|
+
from coati_payroll.rbac import require_read_access, require_write_access
|
|
24
|
+
from coati_payroll.model import Moneda, db
|
|
25
|
+
from coati_payroll.vistas.constants import PER_PAGE
|
|
26
|
+
|
|
27
|
+
currency_bp = Blueprint("currency", __name__, url_prefix="/currency")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@currency_bp.route("/")
|
|
31
|
+
@require_read_access()
|
|
32
|
+
def index():
|
|
33
|
+
"""List all currencies with pagination."""
|
|
34
|
+
page = request.args.get("page", 1, type=int)
|
|
35
|
+
pagination = db.paginate(
|
|
36
|
+
db.select(Moneda).order_by(Moneda.codigo),
|
|
37
|
+
page=page,
|
|
38
|
+
per_page=PER_PAGE,
|
|
39
|
+
error_out=False,
|
|
40
|
+
)
|
|
41
|
+
return render_template(
|
|
42
|
+
"modules/currency/index.html",
|
|
43
|
+
currencies=pagination.items,
|
|
44
|
+
pagination=pagination,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@currency_bp.route("/new", methods=["GET", "POST"])
|
|
49
|
+
@require_write_access()
|
|
50
|
+
def new():
|
|
51
|
+
"""Create a new currency."""
|
|
52
|
+
form = CurrencyForm()
|
|
53
|
+
|
|
54
|
+
if form.validate_on_submit():
|
|
55
|
+
currency = Moneda()
|
|
56
|
+
currency.codigo = form.codigo.data
|
|
57
|
+
currency.nombre = form.nombre.data
|
|
58
|
+
currency.simbolo = form.simbolo.data
|
|
59
|
+
currency.activo = form.activo.data
|
|
60
|
+
currency.creado_por = current_user.usuario
|
|
61
|
+
|
|
62
|
+
db.session.add(currency)
|
|
63
|
+
db.session.commit()
|
|
64
|
+
flash(_("Moneda creada exitosamente."), "success")
|
|
65
|
+
return redirect(url_for("currency.index"))
|
|
66
|
+
|
|
67
|
+
return render_template("modules/currency/form.html", form=form, title=_("Nueva Moneda"))
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@currency_bp.route("/edit/<string:id>", methods=["GET", "POST"])
|
|
71
|
+
@require_write_access()
|
|
72
|
+
def edit(id: str):
|
|
73
|
+
"""Edit an existing currency."""
|
|
74
|
+
currency = db.session.get(Moneda, id)
|
|
75
|
+
if not currency:
|
|
76
|
+
flash(_("Moneda no encontrada."), "error")
|
|
77
|
+
return redirect(url_for("currency.index"))
|
|
78
|
+
|
|
79
|
+
form = CurrencyForm(obj=currency)
|
|
80
|
+
|
|
81
|
+
if form.validate_on_submit():
|
|
82
|
+
currency.codigo = form.codigo.data
|
|
83
|
+
currency.nombre = form.nombre.data
|
|
84
|
+
currency.simbolo = form.simbolo.data
|
|
85
|
+
currency.activo = form.activo.data
|
|
86
|
+
currency.modificado_por = current_user.usuario
|
|
87
|
+
|
|
88
|
+
db.session.commit()
|
|
89
|
+
flash(_("Moneda actualizada exitosamente."), "success")
|
|
90
|
+
return redirect(url_for("currency.index"))
|
|
91
|
+
|
|
92
|
+
return render_template(
|
|
93
|
+
"modules/currency/form.html",
|
|
94
|
+
form=form,
|
|
95
|
+
title=_("Editar Moneda"),
|
|
96
|
+
currency=currency,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@currency_bp.route("/delete/<string:id>", methods=["POST"])
|
|
101
|
+
@require_write_access()
|
|
102
|
+
def delete(id: str):
|
|
103
|
+
"""Delete a currency."""
|
|
104
|
+
currency = db.session.get(Moneda, id)
|
|
105
|
+
if not currency:
|
|
106
|
+
flash(_("Moneda no encontrada."), "error")
|
|
107
|
+
return redirect(url_for("currency.index"))
|
|
108
|
+
|
|
109
|
+
db.session.delete(currency)
|
|
110
|
+
db.session.commit()
|
|
111
|
+
flash(_("Moneda eliminada exitosamente."), "success")
|
|
112
|
+
return redirect(url_for("currency.index"))
|
|
@@ -0,0 +1,120 @@
|
|
|
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
|
+
"""Custom employee field CRUD routes."""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from flask import Blueprint, flash, redirect, render_template, request, url_for
|
|
19
|
+
from flask_login import current_user
|
|
20
|
+
|
|
21
|
+
from coati_payroll.forms import CustomFieldForm
|
|
22
|
+
from coati_payroll.i18n import _
|
|
23
|
+
from coati_payroll.rbac import require_read_access, require_write_access
|
|
24
|
+
from coati_payroll.model import CampoPersonalizado, db
|
|
25
|
+
from coati_payroll.vistas.constants import PER_PAGE
|
|
26
|
+
|
|
27
|
+
custom_field_bp = Blueprint("custom_field", __name__, url_prefix="/custom_field")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@custom_field_bp.route("/")
|
|
31
|
+
@require_read_access()
|
|
32
|
+
def index():
|
|
33
|
+
"""List all custom fields with pagination."""
|
|
34
|
+
page = request.args.get("page", 1, type=int)
|
|
35
|
+
pagination = db.paginate(
|
|
36
|
+
db.select(CampoPersonalizado).order_by(CampoPersonalizado.orden),
|
|
37
|
+
page=page,
|
|
38
|
+
per_page=PER_PAGE,
|
|
39
|
+
error_out=False,
|
|
40
|
+
)
|
|
41
|
+
return render_template(
|
|
42
|
+
"modules/custom_field/index.html",
|
|
43
|
+
custom_fields=pagination.items,
|
|
44
|
+
pagination=pagination,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@custom_field_bp.route("/new", methods=["GET", "POST"])
|
|
49
|
+
@require_write_access()
|
|
50
|
+
def new():
|
|
51
|
+
"""Create a new custom field."""
|
|
52
|
+
form = CustomFieldForm()
|
|
53
|
+
|
|
54
|
+
if form.validate_on_submit():
|
|
55
|
+
custom_field = CampoPersonalizado()
|
|
56
|
+
custom_field.nombre_campo = form.nombre_campo.data
|
|
57
|
+
custom_field.etiqueta = form.etiqueta.data
|
|
58
|
+
custom_field.tipo_dato = form.tipo_dato.data
|
|
59
|
+
custom_field.descripcion = form.descripcion.data
|
|
60
|
+
custom_field.orden = int(form.orden.data or 0)
|
|
61
|
+
custom_field.activo = form.activo.data
|
|
62
|
+
custom_field.creado_por = current_user.usuario
|
|
63
|
+
|
|
64
|
+
db.session.add(custom_field)
|
|
65
|
+
db.session.commit()
|
|
66
|
+
flash(_("Campo personalizado creado exitosamente."), "success")
|
|
67
|
+
return redirect(url_for("custom_field.index"))
|
|
68
|
+
|
|
69
|
+
return render_template(
|
|
70
|
+
"modules/custom_field/form.html",
|
|
71
|
+
form=form,
|
|
72
|
+
title=_("Nuevo Campo Personalizado"),
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@custom_field_bp.route("/edit/<string:id>", methods=["GET", "POST"])
|
|
77
|
+
@require_write_access()
|
|
78
|
+
def edit(id: str):
|
|
79
|
+
"""Edit an existing custom field."""
|
|
80
|
+
custom_field = db.session.get(CampoPersonalizado, id)
|
|
81
|
+
if not custom_field:
|
|
82
|
+
flash(_("Campo personalizado no encontrado."), "error")
|
|
83
|
+
return redirect(url_for("custom_field.index"))
|
|
84
|
+
|
|
85
|
+
form = CustomFieldForm(obj=custom_field)
|
|
86
|
+
|
|
87
|
+
if form.validate_on_submit():
|
|
88
|
+
custom_field.nombre_campo = form.nombre_campo.data
|
|
89
|
+
custom_field.etiqueta = form.etiqueta.data
|
|
90
|
+
custom_field.tipo_dato = form.tipo_dato.data
|
|
91
|
+
custom_field.descripcion = form.descripcion.data
|
|
92
|
+
custom_field.orden = int(form.orden.data or 0)
|
|
93
|
+
custom_field.activo = form.activo.data
|
|
94
|
+
custom_field.modificado_por = current_user.usuario
|
|
95
|
+
|
|
96
|
+
db.session.commit()
|
|
97
|
+
flash(_("Campo personalizado actualizado exitosamente."), "success")
|
|
98
|
+
return redirect(url_for("custom_field.index"))
|
|
99
|
+
|
|
100
|
+
return render_template(
|
|
101
|
+
"modules/custom_field/form.html",
|
|
102
|
+
form=form,
|
|
103
|
+
title=_("Editar Campo Personalizado"),
|
|
104
|
+
custom_field=custom_field,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@custom_field_bp.route("/delete/<string:id>", methods=["POST"])
|
|
109
|
+
@require_write_access()
|
|
110
|
+
def delete(id: str):
|
|
111
|
+
"""Delete a custom field."""
|
|
112
|
+
custom_field = db.session.get(CampoPersonalizado, id)
|
|
113
|
+
if not custom_field:
|
|
114
|
+
flash(_("Campo personalizado no encontrado."), "error")
|
|
115
|
+
return redirect(url_for("custom_field.index"))
|
|
116
|
+
|
|
117
|
+
db.session.delete(custom_field)
|
|
118
|
+
db.session.commit()
|
|
119
|
+
flash(_("Campo personalizado eliminado exitosamente."), "success")
|
|
120
|
+
return redirect(url_for("custom_field.index"))
|
|
@@ -0,0 +1,305 @@
|
|
|
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
|
+
"""Employee CRUD routes."""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from datetime import date
|
|
19
|
+
from decimal import Decimal
|
|
20
|
+
|
|
21
|
+
from flask import Blueprint, flash, redirect, render_template, request, url_for
|
|
22
|
+
from flask_login import current_user
|
|
23
|
+
|
|
24
|
+
from coati_payroll.forms import EmployeeForm
|
|
25
|
+
from coati_payroll.i18n import _
|
|
26
|
+
from coati_payroll.model import CampoPersonalizado, Empleado, Moneda, db
|
|
27
|
+
from coati_payroll.rbac import require_read_access, require_write_access
|
|
28
|
+
from coati_payroll.vistas.constants import PER_PAGE
|
|
29
|
+
|
|
30
|
+
employee_bp = Blueprint("employee", __name__, url_prefix="/employee")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_currency_choices():
|
|
34
|
+
"""Get list of currencies for select fields."""
|
|
35
|
+
currencies = db.session.execute(db.select(Moneda).filter_by(activo=True).order_by(Moneda.codigo)).scalars().all()
|
|
36
|
+
return [("", _("Seleccionar..."))] + [(c.id, f"{c.codigo} - {c.nombre}") for c in currencies]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def get_empresa_choices():
|
|
40
|
+
"""Get list of companies for select fields."""
|
|
41
|
+
from coati_payroll.model import Empresa
|
|
42
|
+
|
|
43
|
+
empresas = (
|
|
44
|
+
db.session.execute(db.select(Empresa).filter_by(activo=True).order_by(Empresa.razon_social)).scalars().all()
|
|
45
|
+
)
|
|
46
|
+
return [("", _("Seleccionar..."))] + [(e.id, f"{e.codigo} - {e.razon_social}") for e in empresas]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def get_custom_fields():
|
|
50
|
+
"""Get all active custom fields ordered by 'orden'."""
|
|
51
|
+
return (
|
|
52
|
+
db.session.execute(db.select(CampoPersonalizado).filter_by(activo=True).order_by(CampoPersonalizado.orden))
|
|
53
|
+
.scalars()
|
|
54
|
+
.all()
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def process_custom_fields_from_request(custom_fields):
|
|
59
|
+
"""Process custom field values from form request and return as dict.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
custom_fields: List of CampoPersonalizado objects
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
Dictionary with custom field names as keys and their converted values
|
|
66
|
+
"""
|
|
67
|
+
datos_adicionales = {}
|
|
68
|
+
for field in custom_fields:
|
|
69
|
+
field_name = f"custom_{field.nombre_campo}"
|
|
70
|
+
raw_value = request.form.get(field_name, "")
|
|
71
|
+
|
|
72
|
+
match field.tipo_dato:
|
|
73
|
+
case "texto":
|
|
74
|
+
stripped = raw_value.strip() if raw_value else ""
|
|
75
|
+
datos_adicionales[field.nombre_campo] = stripped or None
|
|
76
|
+
case "entero":
|
|
77
|
+
try:
|
|
78
|
+
datos_adicionales[field.nombre_campo] = int(raw_value) if raw_value else None
|
|
79
|
+
except ValueError:
|
|
80
|
+
datos_adicionales[field.nombre_campo] = None
|
|
81
|
+
case "decimal":
|
|
82
|
+
try:
|
|
83
|
+
datos_adicionales[field.nombre_campo] = float(raw_value) if raw_value else None
|
|
84
|
+
except ValueError:
|
|
85
|
+
datos_adicionales[field.nombre_campo] = None
|
|
86
|
+
case "booleano":
|
|
87
|
+
# Checkbox will send value only if checked
|
|
88
|
+
datos_adicionales[field.nombre_campo] = field_name in request.form
|
|
89
|
+
case _:
|
|
90
|
+
# Unknown type, store as text
|
|
91
|
+
datos_adicionales[field.nombre_campo] = raw_value or None
|
|
92
|
+
return datos_adicionales
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def process_last_three_salaries(form):
|
|
96
|
+
"""Process last three salary fields from form and return as dict.
|
|
97
|
+
|
|
98
|
+
Stores salaries as strings to preserve Decimal precision in JSON.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
form: EmployeeForm instance with salary fields
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
Dictionary with last three salaries as strings, or None if empty
|
|
105
|
+
"""
|
|
106
|
+
ultimos_salarios = {}
|
|
107
|
+
if form.ultimo_salario_1.data:
|
|
108
|
+
ultimos_salarios["salario_1"] = str(form.ultimo_salario_1.data)
|
|
109
|
+
if form.ultimo_salario_2.data:
|
|
110
|
+
ultimos_salarios["salario_2"] = str(form.ultimo_salario_2.data)
|
|
111
|
+
if form.ultimo_salario_3.data:
|
|
112
|
+
ultimos_salarios["salario_3"] = str(form.ultimo_salario_3.data)
|
|
113
|
+
return ultimos_salarios if ultimos_salarios else None
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@employee_bp.route("/")
|
|
117
|
+
@require_read_access()
|
|
118
|
+
def index():
|
|
119
|
+
"""List all employees with pagination."""
|
|
120
|
+
page = request.args.get("page", 1, type=int)
|
|
121
|
+
pagination = db.paginate(
|
|
122
|
+
db.select(Empleado).order_by(Empleado.primer_apellido, Empleado.primer_nombre),
|
|
123
|
+
page=page,
|
|
124
|
+
per_page=PER_PAGE,
|
|
125
|
+
error_out=False,
|
|
126
|
+
)
|
|
127
|
+
return render_template("modules/employee/index.html", employees=pagination.items, pagination=pagination)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@employee_bp.route("/new", methods=["GET", "POST"])
|
|
131
|
+
@require_write_access()
|
|
132
|
+
def new():
|
|
133
|
+
"""Create a new employee. Admin and HR can create employees."""
|
|
134
|
+
form = EmployeeForm()
|
|
135
|
+
form.moneda_id.choices = get_currency_choices()
|
|
136
|
+
form.empresa_id.choices = get_empresa_choices()
|
|
137
|
+
custom_fields = get_custom_fields()
|
|
138
|
+
|
|
139
|
+
if form.validate_on_submit():
|
|
140
|
+
employee = Empleado()
|
|
141
|
+
# Set codigo_empleado only if provided (otherwise default will be used)
|
|
142
|
+
if form.codigo_empleado.data and form.codigo_empleado.data.strip():
|
|
143
|
+
employee.codigo_empleado = form.codigo_empleado.data.strip()
|
|
144
|
+
employee.primer_nombre = form.primer_nombre.data
|
|
145
|
+
employee.segundo_nombre = form.segundo_nombre.data
|
|
146
|
+
employee.primer_apellido = form.primer_apellido.data
|
|
147
|
+
employee.segundo_apellido = form.segundo_apellido.data
|
|
148
|
+
employee.genero = form.genero.data or None
|
|
149
|
+
employee.nacionalidad = form.nacionalidad.data
|
|
150
|
+
employee.tipo_identificacion = form.tipo_identificacion.data or None
|
|
151
|
+
employee.identificacion_personal = form.identificacion_personal.data
|
|
152
|
+
employee.id_seguridad_social = form.id_seguridad_social.data or None
|
|
153
|
+
employee.id_fiscal = form.id_fiscal.data or None
|
|
154
|
+
employee.tipo_sangre = form.tipo_sangre.data or None
|
|
155
|
+
employee.fecha_nacimiento = form.fecha_nacimiento.data
|
|
156
|
+
employee.fecha_alta = form.fecha_alta.data
|
|
157
|
+
employee.fecha_baja = form.fecha_baja.data
|
|
158
|
+
employee.activo = form.activo.data
|
|
159
|
+
employee.cargo = form.cargo.data
|
|
160
|
+
employee.area = form.area.data
|
|
161
|
+
employee.centro_costos = form.centro_costos.data
|
|
162
|
+
employee.salario_base = form.salario_base.data or Decimal("0.00")
|
|
163
|
+
employee.moneda_id = form.moneda_id.data or None
|
|
164
|
+
employee.empresa_id = form.empresa_id.data or None
|
|
165
|
+
employee.correo = form.correo.data
|
|
166
|
+
employee.telefono = form.telefono.data
|
|
167
|
+
employee.direccion = form.direccion.data
|
|
168
|
+
employee.estado_civil = form.estado_civil.data or None
|
|
169
|
+
employee.banco = form.banco.data
|
|
170
|
+
employee.numero_cuenta_bancaria = form.numero_cuenta_bancaria.data
|
|
171
|
+
employee.tipo_contrato = form.tipo_contrato.data or None
|
|
172
|
+
employee.creado_por = current_user.usuario
|
|
173
|
+
|
|
174
|
+
# Initial implementation data
|
|
175
|
+
employee.anio_implementacion_inicial = form.anio_implementacion_inicial.data
|
|
176
|
+
employee.mes_ultimo_cierre = form.mes_ultimo_cierre.data
|
|
177
|
+
employee.salario_acumulado = form.salario_acumulado.data or Decimal("0.00")
|
|
178
|
+
employee.impuesto_acumulado = form.impuesto_acumulado.data or Decimal("0.00")
|
|
179
|
+
|
|
180
|
+
# Store last three salaries in JSON format using helper function
|
|
181
|
+
employee.ultimos_tres_salarios = process_last_three_salaries(form)
|
|
182
|
+
|
|
183
|
+
# Process custom fields
|
|
184
|
+
employee.datos_adicionales = process_custom_fields_from_request(custom_fields)
|
|
185
|
+
|
|
186
|
+
db.session.add(employee)
|
|
187
|
+
db.session.commit()
|
|
188
|
+
flash(_("Empleado creado exitosamente."), "success")
|
|
189
|
+
return redirect(url_for("employee.index"))
|
|
190
|
+
|
|
191
|
+
# Default date to today
|
|
192
|
+
if not form.fecha_alta.data:
|
|
193
|
+
form.fecha_alta.data = date.today()
|
|
194
|
+
if not form.salario_base.data:
|
|
195
|
+
form.salario_base.data = Decimal("0.00")
|
|
196
|
+
|
|
197
|
+
return render_template(
|
|
198
|
+
"modules/employee/form.html",
|
|
199
|
+
form=form,
|
|
200
|
+
title=_("Nuevo Empleado"),
|
|
201
|
+
custom_fields=custom_fields,
|
|
202
|
+
custom_values={},
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
@employee_bp.route("/edit/<string:id>", methods=["GET", "POST"])
|
|
207
|
+
@require_write_access()
|
|
208
|
+
def edit(id: str):
|
|
209
|
+
"""Edit an existing employee. Admin and HR can edit employees."""
|
|
210
|
+
employee = db.session.get(Empleado, id)
|
|
211
|
+
if not employee:
|
|
212
|
+
flash(_("Empleado no encontrado."), "error")
|
|
213
|
+
return redirect(url_for("employee.index"))
|
|
214
|
+
|
|
215
|
+
form = EmployeeForm(obj=employee)
|
|
216
|
+
form.moneda_id.choices = get_currency_choices()
|
|
217
|
+
form.empresa_id.choices = get_empresa_choices()
|
|
218
|
+
custom_fields = get_custom_fields()
|
|
219
|
+
|
|
220
|
+
if form.validate_on_submit():
|
|
221
|
+
# Update codigo_empleado only if provided
|
|
222
|
+
if form.codigo_empleado.data and form.codigo_empleado.data.strip():
|
|
223
|
+
employee.codigo_empleado = form.codigo_empleado.data.strip()
|
|
224
|
+
employee.primer_nombre = form.primer_nombre.data
|
|
225
|
+
employee.segundo_nombre = form.segundo_nombre.data
|
|
226
|
+
employee.primer_apellido = form.primer_apellido.data
|
|
227
|
+
employee.segundo_apellido = form.segundo_apellido.data
|
|
228
|
+
employee.genero = form.genero.data or None
|
|
229
|
+
employee.nacionalidad = form.nacionalidad.data
|
|
230
|
+
employee.tipo_identificacion = form.tipo_identificacion.data or None
|
|
231
|
+
employee.identificacion_personal = form.identificacion_personal.data
|
|
232
|
+
employee.id_seguridad_social = form.id_seguridad_social.data or None
|
|
233
|
+
employee.id_fiscal = form.id_fiscal.data or None
|
|
234
|
+
employee.tipo_sangre = form.tipo_sangre.data or None
|
|
235
|
+
employee.fecha_nacimiento = form.fecha_nacimiento.data
|
|
236
|
+
employee.fecha_alta = form.fecha_alta.data
|
|
237
|
+
employee.fecha_baja = form.fecha_baja.data
|
|
238
|
+
employee.activo = form.activo.data
|
|
239
|
+
employee.cargo = form.cargo.data
|
|
240
|
+
employee.area = form.area.data
|
|
241
|
+
employee.centro_costos = form.centro_costos.data
|
|
242
|
+
employee.salario_base = form.salario_base.data or Decimal("0.00")
|
|
243
|
+
employee.moneda_id = form.moneda_id.data or None
|
|
244
|
+
employee.empresa_id = form.empresa_id.data or None
|
|
245
|
+
employee.correo = form.correo.data
|
|
246
|
+
employee.telefono = form.telefono.data
|
|
247
|
+
employee.direccion = form.direccion.data
|
|
248
|
+
employee.estado_civil = form.estado_civil.data or None
|
|
249
|
+
employee.banco = form.banco.data
|
|
250
|
+
employee.numero_cuenta_bancaria = form.numero_cuenta_bancaria.data
|
|
251
|
+
employee.tipo_contrato = form.tipo_contrato.data or None
|
|
252
|
+
employee.modificado_por = current_user.usuario
|
|
253
|
+
|
|
254
|
+
# Initial implementation data
|
|
255
|
+
employee.anio_implementacion_inicial = form.anio_implementacion_inicial.data
|
|
256
|
+
employee.mes_ultimo_cierre = form.mes_ultimo_cierre.data
|
|
257
|
+
employee.salario_acumulado = form.salario_acumulado.data or Decimal("0.00")
|
|
258
|
+
employee.impuesto_acumulado = form.impuesto_acumulado.data or Decimal("0.00")
|
|
259
|
+
|
|
260
|
+
# Store last three salaries in JSON format using helper function
|
|
261
|
+
employee.ultimos_tres_salarios = process_last_three_salaries(form)
|
|
262
|
+
|
|
263
|
+
# Process custom fields
|
|
264
|
+
employee.datos_adicionales = process_custom_fields_from_request(custom_fields)
|
|
265
|
+
|
|
266
|
+
db.session.commit()
|
|
267
|
+
flash(_("Empleado actualizado exitosamente."), "success")
|
|
268
|
+
return redirect(url_for("employee.index"))
|
|
269
|
+
|
|
270
|
+
# Pre-populate last three salaries from employee data
|
|
271
|
+
if request.method != "POST":
|
|
272
|
+
ultimos_salarios = employee.ultimos_tres_salarios or {}
|
|
273
|
+
if ultimos_salarios.get("salario_1"):
|
|
274
|
+
form.ultimo_salario_1.data = Decimal(str(ultimos_salarios["salario_1"]))
|
|
275
|
+
if ultimos_salarios.get("salario_2"):
|
|
276
|
+
form.ultimo_salario_2.data = Decimal(str(ultimos_salarios["salario_2"]))
|
|
277
|
+
if ultimos_salarios.get("salario_3"):
|
|
278
|
+
form.ultimo_salario_3.data = Decimal(str(ultimos_salarios["salario_3"]))
|
|
279
|
+
|
|
280
|
+
# Get existing custom field values
|
|
281
|
+
custom_values = employee.datos_adicionales or {}
|
|
282
|
+
|
|
283
|
+
return render_template(
|
|
284
|
+
"modules/employee/form.html",
|
|
285
|
+
form=form,
|
|
286
|
+
title=_("Editar Empleado"),
|
|
287
|
+
employee=employee,
|
|
288
|
+
custom_fields=custom_fields,
|
|
289
|
+
custom_values=custom_values,
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
@employee_bp.route("/delete/<string:id>", methods=["POST"])
|
|
294
|
+
@require_write_access()
|
|
295
|
+
def delete(id: str):
|
|
296
|
+
"""Delete an employee. Admin and HR can delete employees."""
|
|
297
|
+
employee = db.session.get(Empleado, id)
|
|
298
|
+
if not employee:
|
|
299
|
+
flash(_("Empleado no encontrado."), "error")
|
|
300
|
+
return redirect(url_for("employee.index"))
|
|
301
|
+
|
|
302
|
+
db.session.delete(employee)
|
|
303
|
+
db.session.commit()
|
|
304
|
+
flash(_("Empleado eliminado exitosamente."), "success")
|
|
305
|
+
return redirect(url_for("employee.index"))
|
|
@@ -0,0 +1,153 @@
|
|
|
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
|
+
"""Empresa (Company) views module."""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from flask import Blueprint, flash, redirect, render_template, request, url_for
|
|
19
|
+
from flask_login import current_user
|
|
20
|
+
|
|
21
|
+
from coati_payroll.enums import TipoUsuario
|
|
22
|
+
from coati_payroll.i18n import _
|
|
23
|
+
from coati_payroll.model import Empresa, db
|
|
24
|
+
from coati_payroll.rbac import require_role, require_read_access
|
|
25
|
+
|
|
26
|
+
empresa_bp = Blueprint("empresa", __name__, url_prefix="/empresa")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@empresa_bp.route("/")
|
|
30
|
+
@require_read_access()
|
|
31
|
+
def index():
|
|
32
|
+
"""List all companies."""
|
|
33
|
+
page = request.args.get("page", 1, type=int)
|
|
34
|
+
per_page = 20
|
|
35
|
+
|
|
36
|
+
query = db.select(Empresa).order_by(Empresa.razon_social)
|
|
37
|
+
pagination = db.paginate(query, page=page, per_page=per_page, error_out=False)
|
|
38
|
+
empresas = pagination.items
|
|
39
|
+
|
|
40
|
+
return render_template(
|
|
41
|
+
"modules/empresa/index.html",
|
|
42
|
+
empresas=empresas,
|
|
43
|
+
pagination=pagination,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@empresa_bp.route("/new", methods=["GET", "POST"])
|
|
48
|
+
@require_role(TipoUsuario.ADMIN)
|
|
49
|
+
def new():
|
|
50
|
+
"""Create a new company. Only administrators can create companies."""
|
|
51
|
+
from coati_payroll.forms import EmpresaForm
|
|
52
|
+
|
|
53
|
+
form = EmpresaForm()
|
|
54
|
+
|
|
55
|
+
if form.validate_on_submit():
|
|
56
|
+
empresa = Empresa()
|
|
57
|
+
form.populate_obj(empresa)
|
|
58
|
+
empresa.creado_por = current_user.usuario
|
|
59
|
+
|
|
60
|
+
db.session.add(empresa)
|
|
61
|
+
try:
|
|
62
|
+
db.session.commit()
|
|
63
|
+
flash(_("Empresa creada exitosamente."), "success")
|
|
64
|
+
return redirect(url_for("empresa.index"))
|
|
65
|
+
except Exception as e:
|
|
66
|
+
db.session.rollback()
|
|
67
|
+
flash(_("Error al crear la empresa: {}").format(str(e)), "danger")
|
|
68
|
+
|
|
69
|
+
return render_template("modules/empresa/form.html", form=form, titulo=_("Nueva Empresa"))
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@empresa_bp.route("/<string:empresa_id>/edit", methods=["GET", "POST"])
|
|
73
|
+
@require_role(TipoUsuario.ADMIN)
|
|
74
|
+
def edit(empresa_id):
|
|
75
|
+
"""Edit an existing company. Only administrators can edit companies."""
|
|
76
|
+
from coati_payroll.forms import EmpresaForm
|
|
77
|
+
|
|
78
|
+
empresa = db.session.get(Empresa, empresa_id)
|
|
79
|
+
if not empresa:
|
|
80
|
+
flash(_("Empresa no encontrada."), "warning")
|
|
81
|
+
return redirect(url_for("empresa.index"))
|
|
82
|
+
|
|
83
|
+
form = EmpresaForm(obj=empresa)
|
|
84
|
+
|
|
85
|
+
if form.validate_on_submit():
|
|
86
|
+
form.populate_obj(empresa)
|
|
87
|
+
empresa.modificado_por = current_user.usuario
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
db.session.commit()
|
|
91
|
+
flash(_("Empresa actualizada exitosamente."), "success")
|
|
92
|
+
return redirect(url_for("empresa.index"))
|
|
93
|
+
except Exception as e:
|
|
94
|
+
db.session.rollback()
|
|
95
|
+
flash(_("Error al actualizar la empresa: {}").format(str(e)), "danger")
|
|
96
|
+
|
|
97
|
+
return render_template(
|
|
98
|
+
"modules/empresa/form.html",
|
|
99
|
+
form=form,
|
|
100
|
+
empresa=empresa,
|
|
101
|
+
titulo=_("Editar Empresa"),
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@empresa_bp.route("/<string:empresa_id>/delete", methods=["POST"])
|
|
106
|
+
@require_role(TipoUsuario.ADMIN)
|
|
107
|
+
def delete(empresa_id):
|
|
108
|
+
"""Delete a company. Only administrators can delete companies."""
|
|
109
|
+
empresa = db.session.get(Empresa, empresa_id)
|
|
110
|
+
if not empresa:
|
|
111
|
+
flash(_("Empresa no encontrada."), "warning")
|
|
112
|
+
return redirect(url_for("empresa.index"))
|
|
113
|
+
|
|
114
|
+
# Check if company has employees or payrolls
|
|
115
|
+
if empresa.empleados or empresa.planillas:
|
|
116
|
+
flash(
|
|
117
|
+
_("No se puede eliminar la empresa porque tiene empleados o planillas asociadas."),
|
|
118
|
+
"danger",
|
|
119
|
+
)
|
|
120
|
+
return redirect(url_for("empresa.index"))
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
db.session.delete(empresa)
|
|
124
|
+
db.session.commit()
|
|
125
|
+
flash(_("Empresa eliminada exitosamente."), "success")
|
|
126
|
+
except Exception as e:
|
|
127
|
+
db.session.rollback()
|
|
128
|
+
flash(_("Error al eliminar la empresa: {}").format(str(e)), "danger")
|
|
129
|
+
|
|
130
|
+
return redirect(url_for("empresa.index"))
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@empresa_bp.route("/<string:empresa_id>/toggle", methods=["POST"])
|
|
134
|
+
@require_role(TipoUsuario.ADMIN)
|
|
135
|
+
def toggle_active(empresa_id):
|
|
136
|
+
"""Toggle company active status. Only administrators can toggle status."""
|
|
137
|
+
empresa = db.session.get(Empresa, empresa_id)
|
|
138
|
+
if not empresa:
|
|
139
|
+
flash(_("Empresa no encontrada."), "warning")
|
|
140
|
+
return redirect(url_for("empresa.index"))
|
|
141
|
+
|
|
142
|
+
empresa.activo = not empresa.activo
|
|
143
|
+
empresa.modificado_por = current_user.usuario
|
|
144
|
+
|
|
145
|
+
try:
|
|
146
|
+
db.session.commit()
|
|
147
|
+
estado = _("activada") if empresa.activo else _("desactivada")
|
|
148
|
+
flash(_("Empresa {} exitosamente.").format(estado), "success")
|
|
149
|
+
except Exception as e:
|
|
150
|
+
db.session.rollback()
|
|
151
|
+
flash(_("Error al cambiar el estado de la empresa: {}").format(str(e)), "danger")
|
|
152
|
+
|
|
153
|
+
return redirect(url_for("empresa.index"))
|