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.

Files changed (243) hide show
  1. coati_payroll/__init__.py +415 -0
  2. coati_payroll/app.py +95 -0
  3. coati_payroll/audit_helpers.py +904 -0
  4. coati_payroll/auth.py +123 -0
  5. coati_payroll/cli.py +1318 -0
  6. coati_payroll/config.py +219 -0
  7. coati_payroll/demo_data.py +813 -0
  8. coati_payroll/enums.py +278 -0
  9. coati_payroll/forms.py +1769 -0
  10. coati_payroll/formula_engine/__init__.py +81 -0
  11. coati_payroll/formula_engine/ast/__init__.py +110 -0
  12. coati_payroll/formula_engine/ast/ast_visitor.py +259 -0
  13. coati_payroll/formula_engine/ast/expression_evaluator.py +228 -0
  14. coati_payroll/formula_engine/ast/safe_operators.py +131 -0
  15. coati_payroll/formula_engine/ast/type_converter.py +172 -0
  16. coati_payroll/formula_engine/data_sources.py +752 -0
  17. coati_payroll/formula_engine/engine.py +247 -0
  18. coati_payroll/formula_engine/exceptions.py +52 -0
  19. coati_payroll/formula_engine/execution/__init__.py +24 -0
  20. coati_payroll/formula_engine/execution/execution_context.py +52 -0
  21. coati_payroll/formula_engine/execution/step_executor.py +62 -0
  22. coati_payroll/formula_engine/execution/variable_store.py +59 -0
  23. coati_payroll/formula_engine/novelty_codes.py +206 -0
  24. coati_payroll/formula_engine/results/__init__.py +20 -0
  25. coati_payroll/formula_engine/results/execution_result.py +59 -0
  26. coati_payroll/formula_engine/steps/__init__.py +30 -0
  27. coati_payroll/formula_engine/steps/assignment_step.py +71 -0
  28. coati_payroll/formula_engine/steps/base_step.py +48 -0
  29. coati_payroll/formula_engine/steps/calculation_step.py +42 -0
  30. coati_payroll/formula_engine/steps/conditional_step.py +122 -0
  31. coati_payroll/formula_engine/steps/step_factory.py +58 -0
  32. coati_payroll/formula_engine/steps/tax_lookup_step.py +45 -0
  33. coati_payroll/formula_engine/tables/__init__.py +24 -0
  34. coati_payroll/formula_engine/tables/bracket_calculator.py +51 -0
  35. coati_payroll/formula_engine/tables/table_lookup.py +161 -0
  36. coati_payroll/formula_engine/tables/tax_table.py +32 -0
  37. coati_payroll/formula_engine/validation/__init__.py +24 -0
  38. coati_payroll/formula_engine/validation/schema_validator.py +37 -0
  39. coati_payroll/formula_engine/validation/security_validator.py +52 -0
  40. coati_payroll/formula_engine/validation/tax_table_validator.py +205 -0
  41. coati_payroll/formula_engine_examples.py +153 -0
  42. coati_payroll/i18n.py +54 -0
  43. coati_payroll/initial_data.py +613 -0
  44. coati_payroll/interes_engine.py +450 -0
  45. coati_payroll/liquidacion_engine/__init__.py +25 -0
  46. coati_payroll/liquidacion_engine/engine.py +267 -0
  47. coati_payroll/locale_config.py +165 -0
  48. coati_payroll/log.py +138 -0
  49. coati_payroll/model.py +2410 -0
  50. coati_payroll/nomina_engine/__init__.py +87 -0
  51. coati_payroll/nomina_engine/calculators/__init__.py +30 -0
  52. coati_payroll/nomina_engine/calculators/benefit_calculator.py +79 -0
  53. coati_payroll/nomina_engine/calculators/concept_calculator.py +254 -0
  54. coati_payroll/nomina_engine/calculators/deduction_calculator.py +105 -0
  55. coati_payroll/nomina_engine/calculators/exchange_rate_calculator.py +51 -0
  56. coati_payroll/nomina_engine/calculators/perception_calculator.py +75 -0
  57. coati_payroll/nomina_engine/calculators/salary_calculator.py +86 -0
  58. coati_payroll/nomina_engine/domain/__init__.py +27 -0
  59. coati_payroll/nomina_engine/domain/calculation_items.py +52 -0
  60. coati_payroll/nomina_engine/domain/employee_calculation.py +53 -0
  61. coati_payroll/nomina_engine/domain/payroll_context.py +44 -0
  62. coati_payroll/nomina_engine/engine.py +188 -0
  63. coati_payroll/nomina_engine/processors/__init__.py +28 -0
  64. coati_payroll/nomina_engine/processors/accounting_processor.py +171 -0
  65. coati_payroll/nomina_engine/processors/accumulation_processor.py +90 -0
  66. coati_payroll/nomina_engine/processors/loan_processor.py +227 -0
  67. coati_payroll/nomina_engine/processors/novelty_processor.py +42 -0
  68. coati_payroll/nomina_engine/processors/vacation_processor.py +67 -0
  69. coati_payroll/nomina_engine/repositories/__init__.py +32 -0
  70. coati_payroll/nomina_engine/repositories/acumulado_repository.py +83 -0
  71. coati_payroll/nomina_engine/repositories/base_repository.py +40 -0
  72. coati_payroll/nomina_engine/repositories/config_repository.py +102 -0
  73. coati_payroll/nomina_engine/repositories/employee_repository.py +34 -0
  74. coati_payroll/nomina_engine/repositories/exchange_rate_repository.py +58 -0
  75. coati_payroll/nomina_engine/repositories/novelty_repository.py +54 -0
  76. coati_payroll/nomina_engine/repositories/planilla_repository.py +52 -0
  77. coati_payroll/nomina_engine/results/__init__.py +24 -0
  78. coati_payroll/nomina_engine/results/error_result.py +28 -0
  79. coati_payroll/nomina_engine/results/payroll_result.py +53 -0
  80. coati_payroll/nomina_engine/results/validation_result.py +39 -0
  81. coati_payroll/nomina_engine/services/__init__.py +22 -0
  82. coati_payroll/nomina_engine/services/accounting_voucher_service.py +708 -0
  83. coati_payroll/nomina_engine/services/employee_processing_service.py +173 -0
  84. coati_payroll/nomina_engine/services/payroll_execution_service.py +374 -0
  85. coati_payroll/nomina_engine/services/snapshot_service.py +295 -0
  86. coati_payroll/nomina_engine/validators/__init__.py +31 -0
  87. coati_payroll/nomina_engine/validators/base_validator.py +48 -0
  88. coati_payroll/nomina_engine/validators/currency_validator.py +50 -0
  89. coati_payroll/nomina_engine/validators/employee_validator.py +87 -0
  90. coati_payroll/nomina_engine/validators/period_validator.py +44 -0
  91. coati_payroll/nomina_engine/validators/planilla_validator.py +136 -0
  92. coati_payroll/plugin_manager.py +176 -0
  93. coati_payroll/queue/__init__.py +33 -0
  94. coati_payroll/queue/driver.py +127 -0
  95. coati_payroll/queue/drivers/__init__.py +22 -0
  96. coati_payroll/queue/drivers/dramatiq_driver.py +268 -0
  97. coati_payroll/queue/drivers/huey_driver.py +390 -0
  98. coati_payroll/queue/drivers/noop_driver.py +54 -0
  99. coati_payroll/queue/selector.py +121 -0
  100. coati_payroll/queue/tasks.py +764 -0
  101. coati_payroll/rate_limiting.py +83 -0
  102. coati_payroll/rbac.py +183 -0
  103. coati_payroll/report_engine.py +512 -0
  104. coati_payroll/report_export.py +208 -0
  105. coati_payroll/schema_validator.py +167 -0
  106. coati_payroll/security.py +77 -0
  107. coati_payroll/static/styles.css +1044 -0
  108. coati_payroll/system_reports.py +573 -0
  109. coati_payroll/templates/auth/login.html +189 -0
  110. coati_payroll/templates/base.html +283 -0
  111. coati_payroll/templates/index.html +227 -0
  112. coati_payroll/templates/macros.html +146 -0
  113. coati_payroll/templates/modules/calculation_rule/form.html +78 -0
  114. coati_payroll/templates/modules/calculation_rule/index.html +102 -0
  115. coati_payroll/templates/modules/calculation_rule/schema_editor.html +1159 -0
  116. coati_payroll/templates/modules/carga_inicial_prestacion/form.html +170 -0
  117. coati_payroll/templates/modules/carga_inicial_prestacion/index.html +170 -0
  118. coati_payroll/templates/modules/carga_inicial_prestacion/reporte.html +193 -0
  119. coati_payroll/templates/modules/config_calculos/index.html +44 -0
  120. coati_payroll/templates/modules/configuracion/index.html +90 -0
  121. coati_payroll/templates/modules/currency/form.html +47 -0
  122. coati_payroll/templates/modules/currency/index.html +64 -0
  123. coati_payroll/templates/modules/custom_field/form.html +62 -0
  124. coati_payroll/templates/modules/custom_field/index.html +78 -0
  125. coati_payroll/templates/modules/deduccion/form.html +1 -0
  126. coati_payroll/templates/modules/deduccion/index.html +1 -0
  127. coati_payroll/templates/modules/employee/form.html +254 -0
  128. coati_payroll/templates/modules/employee/index.html +76 -0
  129. coati_payroll/templates/modules/empresa/form.html +74 -0
  130. coati_payroll/templates/modules/empresa/index.html +71 -0
  131. coati_payroll/templates/modules/exchange_rate/form.html +47 -0
  132. coati_payroll/templates/modules/exchange_rate/import.html +93 -0
  133. coati_payroll/templates/modules/exchange_rate/index.html +114 -0
  134. coati_payroll/templates/modules/liquidacion/index.html +58 -0
  135. coati_payroll/templates/modules/liquidacion/nueva.html +51 -0
  136. coati_payroll/templates/modules/liquidacion/ver.html +91 -0
  137. coati_payroll/templates/modules/payroll_concepts/audit_log.html +146 -0
  138. coati_payroll/templates/modules/percepcion/form.html +1 -0
  139. coati_payroll/templates/modules/percepcion/index.html +1 -0
  140. coati_payroll/templates/modules/planilla/config.html +190 -0
  141. coati_payroll/templates/modules/planilla/config_deducciones.html +129 -0
  142. coati_payroll/templates/modules/planilla/config_empleados.html +116 -0
  143. coati_payroll/templates/modules/planilla/config_percepciones.html +113 -0
  144. coati_payroll/templates/modules/planilla/config_prestaciones.html +118 -0
  145. coati_payroll/templates/modules/planilla/config_reglas.html +120 -0
  146. coati_payroll/templates/modules/planilla/ejecutar_nomina.html +106 -0
  147. coati_payroll/templates/modules/planilla/form.html +197 -0
  148. coati_payroll/templates/modules/planilla/index.html +144 -0
  149. coati_payroll/templates/modules/planilla/listar_nominas.html +91 -0
  150. coati_payroll/templates/modules/planilla/log_nomina.html +135 -0
  151. coati_payroll/templates/modules/planilla/novedades/form.html +177 -0
  152. coati_payroll/templates/modules/planilla/novedades/index.html +170 -0
  153. coati_payroll/templates/modules/planilla/ver_nomina.html +477 -0
  154. coati_payroll/templates/modules/planilla/ver_nomina_empleado.html +231 -0
  155. coati_payroll/templates/modules/plugins/index.html +71 -0
  156. coati_payroll/templates/modules/prestacion/form.html +1 -0
  157. coati_payroll/templates/modules/prestacion/index.html +1 -0
  158. coati_payroll/templates/modules/prestacion_management/dashboard.html +150 -0
  159. coati_payroll/templates/modules/prestacion_management/initial_balance_bulk.html +195 -0
  160. coati_payroll/templates/modules/prestamo/approve.html +156 -0
  161. coati_payroll/templates/modules/prestamo/condonacion.html +249 -0
  162. coati_payroll/templates/modules/prestamo/detail.html +443 -0
  163. coati_payroll/templates/modules/prestamo/form.html +203 -0
  164. coati_payroll/templates/modules/prestamo/index.html +150 -0
  165. coati_payroll/templates/modules/prestamo/pago_extraordinario.html +211 -0
  166. coati_payroll/templates/modules/prestamo/tabla_pago_pdf.html +181 -0
  167. coati_payroll/templates/modules/report/admin_index.html +125 -0
  168. coati_payroll/templates/modules/report/detail.html +129 -0
  169. coati_payroll/templates/modules/report/execute.html +266 -0
  170. coati_payroll/templates/modules/report/index.html +95 -0
  171. coati_payroll/templates/modules/report/permissions.html +64 -0
  172. coati_payroll/templates/modules/settings/index.html +274 -0
  173. coati_payroll/templates/modules/shared/concept_form.html +201 -0
  174. coati_payroll/templates/modules/shared/concept_index.html +145 -0
  175. coati_payroll/templates/modules/tipo_planilla/form.html +70 -0
  176. coati_payroll/templates/modules/tipo_planilla/index.html +68 -0
  177. coati_payroll/templates/modules/user/form.html +65 -0
  178. coati_payroll/templates/modules/user/index.html +76 -0
  179. coati_payroll/templates/modules/user/profile.html +81 -0
  180. coati_payroll/templates/modules/vacation/account_detail.html +149 -0
  181. coati_payroll/templates/modules/vacation/account_form.html +52 -0
  182. coati_payroll/templates/modules/vacation/account_index.html +68 -0
  183. coati_payroll/templates/modules/vacation/dashboard.html +156 -0
  184. coati_payroll/templates/modules/vacation/initial_balance_bulk.html +149 -0
  185. coati_payroll/templates/modules/vacation/initial_balance_form.html +93 -0
  186. coati_payroll/templates/modules/vacation/leave_request_detail.html +158 -0
  187. coati_payroll/templates/modules/vacation/leave_request_form.html +61 -0
  188. coati_payroll/templates/modules/vacation/leave_request_index.html +98 -0
  189. coati_payroll/templates/modules/vacation/policy_detail.html +176 -0
  190. coati_payroll/templates/modules/vacation/policy_form.html +152 -0
  191. coati_payroll/templates/modules/vacation/policy_index.html +79 -0
  192. coati_payroll/templates/modules/vacation/register_taken_form.html +178 -0
  193. coati_payroll/translations/en/LC_MESSAGES/messages.mo +0 -0
  194. coati_payroll/translations/en/LC_MESSAGES/messages.po +7283 -0
  195. coati_payroll/translations/es/LC_MESSAGES/messages.mo +0 -0
  196. coati_payroll/translations/es/LC_MESSAGES/messages.po +7374 -0
  197. coati_payroll/vacation_service.py +451 -0
  198. coati_payroll/version.py +18 -0
  199. coati_payroll/vistas/__init__.py +64 -0
  200. coati_payroll/vistas/calculation_rule.py +307 -0
  201. coati_payroll/vistas/carga_inicial_prestacion.py +423 -0
  202. coati_payroll/vistas/config_calculos.py +72 -0
  203. coati_payroll/vistas/configuracion.py +87 -0
  204. coati_payroll/vistas/constants.py +17 -0
  205. coati_payroll/vistas/currency.py +112 -0
  206. coati_payroll/vistas/custom_field.py +120 -0
  207. coati_payroll/vistas/employee.py +305 -0
  208. coati_payroll/vistas/empresa.py +153 -0
  209. coati_payroll/vistas/exchange_rate.py +341 -0
  210. coati_payroll/vistas/liquidacion.py +205 -0
  211. coati_payroll/vistas/payroll_concepts.py +580 -0
  212. coati_payroll/vistas/planilla/__init__.py +38 -0
  213. coati_payroll/vistas/planilla/association_routes.py +238 -0
  214. coati_payroll/vistas/planilla/config_routes.py +158 -0
  215. coati_payroll/vistas/planilla/export_routes.py +175 -0
  216. coati_payroll/vistas/planilla/helpers/__init__.py +34 -0
  217. coati_payroll/vistas/planilla/helpers/association_helpers.py +161 -0
  218. coati_payroll/vistas/planilla/helpers/excel_helpers.py +29 -0
  219. coati_payroll/vistas/planilla/helpers/form_helpers.py +97 -0
  220. coati_payroll/vistas/planilla/nomina_routes.py +488 -0
  221. coati_payroll/vistas/planilla/novedad_routes.py +227 -0
  222. coati_payroll/vistas/planilla/routes.py +145 -0
  223. coati_payroll/vistas/planilla/services/__init__.py +26 -0
  224. coati_payroll/vistas/planilla/services/export_service.py +687 -0
  225. coati_payroll/vistas/planilla/services/nomina_service.py +233 -0
  226. coati_payroll/vistas/planilla/services/novedad_service.py +126 -0
  227. coati_payroll/vistas/planilla/services/planilla_service.py +34 -0
  228. coati_payroll/vistas/planilla/validators/__init__.py +18 -0
  229. coati_payroll/vistas/planilla/validators/planilla_validators.py +40 -0
  230. coati_payroll/vistas/plugins.py +45 -0
  231. coati_payroll/vistas/prestacion.py +272 -0
  232. coati_payroll/vistas/prestamo.py +808 -0
  233. coati_payroll/vistas/report.py +432 -0
  234. coati_payroll/vistas/settings.py +29 -0
  235. coati_payroll/vistas/tipo_planilla.py +134 -0
  236. coati_payroll/vistas/user.py +172 -0
  237. coati_payroll/vistas/vacation.py +1045 -0
  238. coati_payroll-0.0.2.dist-info/LICENSE +201 -0
  239. coati_payroll-0.0.2.dist-info/METADATA +581 -0
  240. coati_payroll-0.0.2.dist-info/RECORD +243 -0
  241. coati_payroll-0.0.2.dist-info/WHEEL +5 -0
  242. coati_payroll-0.0.2.dist-info/entry_points.txt +2 -0
  243. coati_payroll-0.0.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,227 @@
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
+ """Routes for managing novedades (novelties)."""
15
+
16
+ from flask import flash, redirect, render_template, request, url_for
17
+ from flask_login import current_user
18
+
19
+ from coati_payroll.model import db, Planilla, Nomina, NominaNovedad
20
+ from coati_payroll.forms import NominaNovedadForm
21
+ from coati_payroll.i18n import _
22
+ from coati_payroll.rbac import require_read_access, require_write_access
23
+ from coati_payroll.vistas.planilla import planilla_bp
24
+ from coati_payroll.vistas.planilla.helpers import populate_novedad_form_choices
25
+ from coati_payroll.vistas.planilla.services import NovedadService
26
+
27
+ # Constants
28
+ ROUTE_LISTAR_NOVEDADES = "planilla.listar_novedades"
29
+ ROUTE_LISTAR_NOMINAS = "planilla.listar_nominas"
30
+ ERROR_NOMINA_NO_PERTENECE = "La nómina no pertenece a esta planilla."
31
+ TEMPLATE_NOVEDAD_FORM = "modules/planilla/novedades/form.html"
32
+
33
+
34
+ @planilla_bp.route("/<planilla_id>/nomina/<nomina_id>/novedades")
35
+ @require_read_access()
36
+ def listar_novedades(planilla_id: str, nomina_id: str):
37
+ """List all novedades (novelties) for a specific nomina."""
38
+ planilla = db.get_or_404(Planilla, planilla_id)
39
+ nomina = db.get_or_404(Nomina, nomina_id)
40
+
41
+ if nomina.planilla_id != planilla_id:
42
+ flash(_(ERROR_NOMINA_NO_PERTENECE), "error")
43
+ return redirect(url_for(ROUTE_LISTAR_NOMINAS, planilla_id=planilla_id))
44
+
45
+ novedades = NovedadService.listar_novedades(planilla, nomina)
46
+
47
+ return render_template(
48
+ "modules/planilla/novedades/index.html",
49
+ planilla=planilla,
50
+ nomina=nomina,
51
+ novedades=novedades,
52
+ )
53
+
54
+
55
+ @planilla_bp.route("/<planilla_id>/nomina/<nomina_id>/novedades/new", methods=["GET", "POST"])
56
+ @require_write_access()
57
+ def nueva_novedad(planilla_id: str, nomina_id: str):
58
+ """Add a new novedad to a nomina."""
59
+ planilla = db.get_or_404(Planilla, planilla_id)
60
+ nomina = db.get_or_404(Nomina, nomina_id)
61
+
62
+ if nomina.planilla_id != planilla_id:
63
+ flash(_(ERROR_NOMINA_NO_PERTENECE), "error")
64
+ return redirect(url_for(ROUTE_LISTAR_NOMINAS, planilla_id=planilla_id))
65
+
66
+ if nomina.estado == "aplicado":
67
+ flash(_("No se pueden agregar novedades a una nómina aplicada."), "error")
68
+ return redirect(
69
+ url_for(
70
+ ROUTE_LISTAR_NOVEDADES,
71
+ planilla_id=planilla_id,
72
+ nomina_id=nomina_id,
73
+ )
74
+ )
75
+
76
+ form = NominaNovedadForm()
77
+ populate_novedad_form_choices(form, nomina_id)
78
+
79
+ if form.validate_on_submit():
80
+ # Validate that fecha_novedad falls within the nomina period
81
+ is_valid, error_message = NovedadService.validar_fecha_novedad(form.fecha_novedad.data, nomina)
82
+ if not is_valid:
83
+ flash(error_message, "error")
84
+ return render_template(
85
+ TEMPLATE_NOVEDAD_FORM,
86
+ form=form,
87
+ planilla=planilla,
88
+ nomina=nomina,
89
+ )
90
+
91
+ NovedadService.crear_novedad(nomina, form, current_user.usuario)
92
+ flash(_("Novedad agregada exitosamente."), "success")
93
+ return redirect(
94
+ url_for(
95
+ ROUTE_LISTAR_NOVEDADES,
96
+ planilla_id=planilla_id,
97
+ nomina_id=nomina_id,
98
+ )
99
+ )
100
+
101
+ return render_template(
102
+ TEMPLATE_NOVEDAD_FORM,
103
+ planilla=planilla,
104
+ nomina=nomina,
105
+ form=form,
106
+ is_edit=False,
107
+ )
108
+
109
+
110
+ @planilla_bp.route(
111
+ "/<planilla_id>/nomina/<nomina_id>/novedades/<novedad_id>/edit",
112
+ methods=["GET", "POST"],
113
+ )
114
+ @require_write_access()
115
+ def editar_novedad(planilla_id: str, nomina_id: str, novedad_id: str):
116
+ """Edit an existing novedad."""
117
+ planilla = db.get_or_404(Planilla, planilla_id)
118
+ nomina = db.get_or_404(Nomina, nomina_id)
119
+ novedad = db.get_or_404(NominaNovedad, novedad_id)
120
+
121
+ if nomina.planilla_id != planilla_id:
122
+ flash(_(ERROR_NOMINA_NO_PERTENECE), "error")
123
+ return redirect(url_for(ROUTE_LISTAR_NOMINAS, planilla_id=planilla_id))
124
+
125
+ if novedad.nomina_id != nomina_id:
126
+ flash(_("La novedad no pertenece a esta nómina."), "error")
127
+ return redirect(
128
+ url_for(
129
+ ROUTE_LISTAR_NOVEDADES,
130
+ planilla_id=planilla_id,
131
+ nomina_id=nomina_id,
132
+ )
133
+ )
134
+
135
+ if nomina.estado == "aplicado":
136
+ flash(_("No se pueden editar novedades de una nómina aplicada."), "error")
137
+ return redirect(
138
+ url_for(
139
+ ROUTE_LISTAR_NOVEDADES,
140
+ planilla_id=planilla_id,
141
+ nomina_id=nomina_id,
142
+ )
143
+ )
144
+
145
+ form = NominaNovedadForm(obj=novedad)
146
+ populate_novedad_form_choices(form, nomina_id)
147
+
148
+ # Set tipo_concepto based on existing data
149
+ if request.method == "GET":
150
+ if novedad.percepcion_id:
151
+ form.tipo_concepto.data = "percepcion"
152
+ form.percepcion_id.data = novedad.percepcion_id
153
+ elif novedad.deduccion_id:
154
+ form.tipo_concepto.data = "deduccion"
155
+ form.deduccion_id.data = novedad.deduccion_id
156
+
157
+ if form.validate_on_submit():
158
+ # Validate that fecha_novedad falls within the nomina period
159
+ is_valid, error_message = NovedadService.validar_fecha_novedad(form.fecha_novedad.data, nomina)
160
+ if not is_valid:
161
+ flash(error_message, "error")
162
+ return render_template(
163
+ TEMPLATE_NOVEDAD_FORM,
164
+ form=form,
165
+ planilla=planilla,
166
+ nomina=nomina,
167
+ novedad=novedad,
168
+ )
169
+
170
+ NovedadService.actualizar_novedad(novedad, form, current_user.usuario)
171
+ flash(_("Novedad actualizada exitosamente."), "success")
172
+ return redirect(
173
+ url_for(
174
+ ROUTE_LISTAR_NOVEDADES,
175
+ planilla_id=planilla_id,
176
+ nomina_id=nomina_id,
177
+ )
178
+ )
179
+
180
+ return render_template(
181
+ TEMPLATE_NOVEDAD_FORM,
182
+ planilla=planilla,
183
+ nomina=nomina,
184
+ form=form,
185
+ novedad=novedad,
186
+ is_edit=True,
187
+ )
188
+
189
+
190
+ @planilla_bp.route(
191
+ "/<planilla_id>/nomina/<nomina_id>/novedades/<novedad_id>/delete",
192
+ methods=["POST"],
193
+ )
194
+ @require_write_access()
195
+ def eliminar_novedad(planilla_id: str, nomina_id: str, novedad_id: str):
196
+ """Delete a novedad from a nomina."""
197
+ nomina = db.get_or_404(Nomina, nomina_id)
198
+ novedad = db.get_or_404(NominaNovedad, novedad_id)
199
+
200
+ if nomina.planilla_id != planilla_id:
201
+ flash(_(ERROR_NOMINA_NO_PERTENECE), "error")
202
+ return redirect(url_for(ROUTE_LISTAR_NOMINAS, planilla_id=planilla_id))
203
+
204
+ if novedad.nomina_id != nomina_id:
205
+ flash(_("La novedad no pertenece a esta nómina."), "error")
206
+ return redirect(
207
+ url_for(
208
+ ROUTE_LISTAR_NOVEDADES,
209
+ planilla_id=planilla_id,
210
+ nomina_id=nomina_id,
211
+ )
212
+ )
213
+
214
+ if nomina.estado == "aplicado":
215
+ flash(_("No se pueden eliminar novedades de una nómina aplicada."), "error")
216
+ return redirect(
217
+ url_for(
218
+ ROUTE_LISTAR_NOVEDADES,
219
+ planilla_id=planilla_id,
220
+ nomina_id=nomina_id,
221
+ )
222
+ )
223
+
224
+ db.session.delete(novedad)
225
+ db.session.commit()
226
+ flash(_("Novedad eliminada exitosamente."), "success")
227
+ return redirect(url_for(ROUTE_LISTAR_NOVEDADES, planilla_id=planilla_id, nomina_id=nomina_id))
@@ -0,0 +1,145 @@
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
+ """Main routes for planilla management."""
15
+
16
+ from flask import flash, redirect, render_template, url_for
17
+ from flask_login import current_user
18
+
19
+ from coati_payroll.model import db, Planilla
20
+ from coati_payroll.forms import PlanillaForm
21
+ from coati_payroll.i18n import _
22
+ from coati_payroll.rbac import require_read_access, require_write_access
23
+ from coati_payroll.vistas.planilla.helpers import populate_form_choices, get_planilla_component_counts
24
+ from coati_payroll.vistas.planilla.services import PlanillaService
25
+
26
+ # Import blueprint from __init__.py
27
+ from coati_payroll.vistas.planilla import planilla_bp
28
+
29
+
30
+ @planilla_bp.route("/")
31
+ @require_read_access()
32
+ def index():
33
+ """List all planillas."""
34
+ planillas = db.session.execute(db.select(Planilla).order_by(Planilla.nombre)).scalars().all()
35
+ return render_template("modules/planilla/index.html", planillas=planillas)
36
+
37
+
38
+ @planilla_bp.route("/new", methods=["GET", "POST"])
39
+ @require_write_access()
40
+ def new():
41
+ """Create a new planilla. Admin and HR can create planillas."""
42
+ form = PlanillaForm()
43
+ populate_form_choices(form)
44
+
45
+ if form.validate_on_submit():
46
+ planilla = Planilla(
47
+ nombre=form.nombre.data,
48
+ descripcion=form.descripcion.data,
49
+ tipo_planilla_id=form.tipo_planilla_id.data,
50
+ moneda_id=form.moneda_id.data,
51
+ empresa_id=form.empresa_id.data or None,
52
+ periodo_fiscal_inicio=form.periodo_fiscal_inicio.data,
53
+ periodo_fiscal_fin=form.periodo_fiscal_fin.data,
54
+ prioridad_prestamos=form.prioridad_prestamos.data or 250,
55
+ prioridad_adelantos=form.prioridad_adelantos.data or 251,
56
+ aplicar_prestamos_automatico=form.aplicar_prestamos_automatico.data,
57
+ aplicar_adelantos_automatico=form.aplicar_adelantos_automatico.data,
58
+ codigo_cuenta_debe_salario=form.codigo_cuenta_debe_salario.data,
59
+ descripcion_cuenta_debe_salario=form.descripcion_cuenta_debe_salario.data,
60
+ codigo_cuenta_haber_salario=form.codigo_cuenta_haber_salario.data,
61
+ descripcion_cuenta_haber_salario=form.descripcion_cuenta_haber_salario.data,
62
+ activo=form.activo.data,
63
+ creado_por=current_user.usuario,
64
+ )
65
+ db.session.add(planilla)
66
+ db.session.commit()
67
+ flash(_("Planilla creada exitosamente."), "success")
68
+ return redirect(url_for("planilla.edit", planilla_id=planilla.id))
69
+
70
+ return render_template("modules/planilla/form.html", form=form, is_edit=False)
71
+
72
+
73
+ @planilla_bp.route("/<planilla_id>/edit", methods=["GET", "POST"])
74
+ @require_write_access()
75
+ def edit(planilla_id: str):
76
+ """Edit basic planilla configuration."""
77
+ planilla = db.get_or_404(Planilla, planilla_id)
78
+ form = PlanillaForm(obj=planilla)
79
+ populate_form_choices(form)
80
+
81
+ if form.validate_on_submit():
82
+ planilla.nombre = form.nombre.data
83
+ planilla.descripcion = form.descripcion.data
84
+ planilla.tipo_planilla_id = form.tipo_planilla_id.data
85
+ planilla.moneda_id = form.moneda_id.data
86
+ planilla.empresa_id = form.empresa_id.data or None
87
+ planilla.periodo_fiscal_inicio = form.periodo_fiscal_inicio.data
88
+ planilla.periodo_fiscal_fin = form.periodo_fiscal_fin.data
89
+ planilla.prioridad_prestamos = form.prioridad_prestamos.data or 250
90
+ planilla.prioridad_adelantos = form.prioridad_adelantos.data or 251
91
+ planilla.aplicar_prestamos_automatico = form.aplicar_prestamos_automatico.data
92
+ planilla.aplicar_adelantos_automatico = form.aplicar_adelantos_automatico.data
93
+ planilla.codigo_cuenta_debe_salario = form.codigo_cuenta_debe_salario.data
94
+ planilla.descripcion_cuenta_debe_salario = form.descripcion_cuenta_debe_salario.data
95
+ planilla.codigo_cuenta_haber_salario = form.codigo_cuenta_haber_salario.data
96
+ planilla.descripcion_cuenta_haber_salario = form.descripcion_cuenta_haber_salario.data
97
+ planilla.activo = form.activo.data
98
+ planilla.modificado_por = current_user.usuario
99
+ db.session.commit()
100
+ flash(_("Planilla actualizada exitosamente."), "success")
101
+ return redirect(url_for("planilla.config", planilla_id=planilla.id))
102
+
103
+ # Get association counts for the summary
104
+ counts = get_planilla_component_counts(planilla_id)
105
+
106
+ return render_template(
107
+ "modules/planilla/form.html",
108
+ form=form,
109
+ planilla=planilla,
110
+ is_edit=True,
111
+ **counts,
112
+ )
113
+
114
+
115
+ @planilla_bp.route("/<planilla_id>/config")
116
+ @require_read_access()
117
+ def config(planilla_id: str):
118
+ """Configuration overview page for a planilla."""
119
+ planilla = db.get_or_404(Planilla, planilla_id)
120
+
121
+ # Get association counts for the summary
122
+ counts = get_planilla_component_counts(planilla_id)
123
+
124
+ return render_template(
125
+ "modules/planilla/config.html",
126
+ planilla=planilla,
127
+ **counts,
128
+ )
129
+
130
+
131
+ @planilla_bp.route("/<planilla_id>/delete", methods=["POST"])
132
+ @require_write_access()
133
+ def delete(planilla_id: str):
134
+ """Delete a planilla."""
135
+ planilla = db.get_or_404(Planilla, planilla_id)
136
+
137
+ can_delete, error_message = PlanillaService.can_delete(planilla)
138
+ if not can_delete:
139
+ flash(_(error_message), "error")
140
+ return redirect(url_for("planilla.index"))
141
+
142
+ db.session.delete(planilla)
143
+ db.session.commit()
144
+ flash(_("Planilla eliminada exitosamente."), "success")
145
+ return redirect(url_for("planilla.index"))
@@ -0,0 +1,26 @@
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
+ """Services for planilla business logic."""
15
+
16
+ from coati_payroll.vistas.planilla.services.planilla_service import PlanillaService
17
+ from coati_payroll.vistas.planilla.services.nomina_service import NominaService
18
+ from coati_payroll.vistas.planilla.services.export_service import ExportService
19
+ from coati_payroll.vistas.planilla.services.novedad_service import NovedadService
20
+
21
+ __all__ = [
22
+ "PlanillaService",
23
+ "NominaService",
24
+ "ExportService",
25
+ "NovedadService",
26
+ ]