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,86 @@
|
|
|
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
|
+
"""Salary calculator for payroll processing."""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from datetime import date, timedelta
|
|
19
|
+
from decimal import Decimal, ROUND_HALF_UP
|
|
20
|
+
|
|
21
|
+
from coati_payroll.model import Planilla, ConfiguracionCalculos
|
|
22
|
+
from ..repositories.config_repository import ConfigRepository
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class SalaryCalculator:
|
|
26
|
+
"""Calculator for salary period calculations."""
|
|
27
|
+
|
|
28
|
+
def __init__(self, config_repository: ConfigRepository):
|
|
29
|
+
self.config_repo = config_repository
|
|
30
|
+
|
|
31
|
+
def calculate_period_salary(
|
|
32
|
+
self, salario_mensual: Decimal, planilla: Planilla, periodo_inicio: date, periodo_fin: date, fecha_calculo: date
|
|
33
|
+
) -> Decimal:
|
|
34
|
+
"""Calculate salary for the pay period based on actual days."""
|
|
35
|
+
if not planilla or not planilla.tipo_planilla:
|
|
36
|
+
return salario_mensual
|
|
37
|
+
|
|
38
|
+
if not periodo_fin or not periodo_inicio:
|
|
39
|
+
return salario_mensual
|
|
40
|
+
|
|
41
|
+
dias_periodo = (periodo_fin - periodo_inicio).days + 1
|
|
42
|
+
|
|
43
|
+
if dias_periodo <= 0:
|
|
44
|
+
from ..validators import ValidationError
|
|
45
|
+
|
|
46
|
+
raise ValidationError(f"Período inválido: inicio ({periodo_inicio}) posterior a fin ({periodo_fin})")
|
|
47
|
+
if dias_periodo > 366:
|
|
48
|
+
from ..validators import ValidationError
|
|
49
|
+
|
|
50
|
+
raise ValidationError(
|
|
51
|
+
f"Período excesivamente largo: {dias_periodo} días. Los períodos no deben exceder 366 días."
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
tipo_planilla = planilla.tipo_planilla
|
|
55
|
+
periodicidad = tipo_planilla.periodicidad.lower() if tipo_planilla.periodicidad else ""
|
|
56
|
+
|
|
57
|
+
if periodicidad == "mensual":
|
|
58
|
+
is_first_of_month = periodo_inicio.day == 1
|
|
59
|
+
next_day = periodo_fin + timedelta(days=1)
|
|
60
|
+
is_last_of_month = next_day.day == 1
|
|
61
|
+
same_month = periodo_inicio.year == periodo_fin.year and periodo_inicio.month == periodo_fin.month
|
|
62
|
+
|
|
63
|
+
if is_first_of_month and is_last_of_month and same_month:
|
|
64
|
+
return salario_mensual
|
|
65
|
+
|
|
66
|
+
elif periodicidad == "quincenal":
|
|
67
|
+
config = self.config_repo.get_for_empresa(planilla.empresa_id)
|
|
68
|
+
dias_configurados = tipo_planilla.dias or config.dias_quincena
|
|
69
|
+
if dias_periodo == dias_configurados:
|
|
70
|
+
divisor = Decimal(str(config.dias_mes_nomina)) / Decimal(str(config.dias_quincena))
|
|
71
|
+
return (salario_mensual / divisor).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
|
|
72
|
+
|
|
73
|
+
config = self.config_repo.get_for_empresa(planilla.empresa_id)
|
|
74
|
+
dias_base = Decimal(str(config.dias_mes_nomina))
|
|
75
|
+
salario_diario = (salario_mensual / dias_base).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
|
|
76
|
+
salario_periodo = (salario_diario * Decimal(str(dias_periodo))).quantize(
|
|
77
|
+
Decimal("0.01"), rounding=ROUND_HALF_UP
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
return salario_periodo
|
|
81
|
+
|
|
82
|
+
def calculate_hourly_rate(self, salario_mensual: Decimal, config: ConfiguracionCalculos) -> Decimal:
|
|
83
|
+
"""Calculate hourly rate from monthly salary."""
|
|
84
|
+
dias_base = Decimal(str(config.dias_mes_nomina))
|
|
85
|
+
horas_dia = Decimal(str(config.horas_jornada_diaria))
|
|
86
|
+
return (salario_mensual / dias_base / horas_dia).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
|
|
@@ -0,0 +1,27 @@
|
|
|
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
|
+
"""Domain models for payroll processing."""
|
|
15
|
+
|
|
16
|
+
from .payroll_context import PayrollContext
|
|
17
|
+
from .employee_calculation import EmployeeCalculation, EmpleadoCalculo
|
|
18
|
+
from .calculation_items import DeduccionItem, PercepcionItem, PrestacionItem
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"PayrollContext",
|
|
22
|
+
"EmployeeCalculation",
|
|
23
|
+
"EmpleadoCalculo",
|
|
24
|
+
"DeduccionItem",
|
|
25
|
+
"PercepcionItem",
|
|
26
|
+
"PrestacionItem",
|
|
27
|
+
]
|
|
@@ -0,0 +1,52 @@
|
|
|
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
|
+
"""Calculation items - immutable domain models for payroll items."""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from decimal import Decimal
|
|
19
|
+
from typing import NamedTuple
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class DeduccionItem(NamedTuple):
|
|
23
|
+
"""Represents a deduction to be applied."""
|
|
24
|
+
|
|
25
|
+
codigo: str
|
|
26
|
+
nombre: str
|
|
27
|
+
monto: Decimal
|
|
28
|
+
prioridad: int
|
|
29
|
+
es_obligatoria: bool
|
|
30
|
+
deduccion_id: str | None = None
|
|
31
|
+
tipo: str = "deduccion" # deduccion, prestamo, adelanto
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class PercepcionItem(NamedTuple):
|
|
35
|
+
"""Represents a perception to be applied."""
|
|
36
|
+
|
|
37
|
+
codigo: str
|
|
38
|
+
nombre: str
|
|
39
|
+
monto: Decimal
|
|
40
|
+
orden: int
|
|
41
|
+
gravable: bool
|
|
42
|
+
percepcion_id: str | None = None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class PrestacionItem(NamedTuple):
|
|
46
|
+
"""Represents an employer benefit to be calculated."""
|
|
47
|
+
|
|
48
|
+
codigo: str
|
|
49
|
+
nombre: str
|
|
50
|
+
monto: Decimal
|
|
51
|
+
orden: int
|
|
52
|
+
prestacion_id: str | None = None
|
|
@@ -0,0 +1,53 @@
|
|
|
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 calculation domain models."""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from decimal import Decimal
|
|
19
|
+
from typing import Any
|
|
20
|
+
|
|
21
|
+
from coati_payroll.model import Empleado, Planilla
|
|
22
|
+
from .calculation_items import DeduccionItem, PercepcionItem, PrestacionItem
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class EmpleadoCalculo:
|
|
26
|
+
"""Container for employee calculation data during payroll processing.
|
|
27
|
+
|
|
28
|
+
This is a mutable container used during payroll processing.
|
|
29
|
+
For backward compatibility, this class maintains the same interface
|
|
30
|
+
as the original implementation.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(self, empleado: Empleado, planilla: Planilla):
|
|
34
|
+
self.empleado = empleado
|
|
35
|
+
self.planilla = planilla
|
|
36
|
+
self.salario_base = Decimal(str(empleado.salario_base or 0))
|
|
37
|
+
self.salario_mensual = Decimal(str(empleado.salario_base or 0))
|
|
38
|
+
self.percepciones: list[PercepcionItem] = []
|
|
39
|
+
self.deducciones: list[DeduccionItem] = []
|
|
40
|
+
self.prestaciones: list[PrestacionItem] = []
|
|
41
|
+
self.total_percepciones = Decimal("0.00")
|
|
42
|
+
self.total_deducciones = Decimal("0.00")
|
|
43
|
+
self.total_prestaciones = Decimal("0.00")
|
|
44
|
+
self.salario_bruto = Decimal("0.00")
|
|
45
|
+
self.salario_neto = Decimal("0.00")
|
|
46
|
+
self.tipo_cambio = Decimal("1.00")
|
|
47
|
+
self.moneda_origen_id = empleado.moneda_id
|
|
48
|
+
self.novedades: dict[str, Decimal] = {}
|
|
49
|
+
self.variables_calculo: dict[str, Any] = {}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# Alias for backward compatibility
|
|
53
|
+
EmployeeCalculation = EmpleadoCalculo
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Copyright 2025 BMO Soluciones, S.A.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
"""Payroll execution context - immutable domain model."""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
|
+
from datetime import date
|
|
20
|
+
from typing import Optional, TYPE_CHECKING
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from ..results.validation_result import ValidationResult
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass(frozen=True)
|
|
27
|
+
class PayrollContext:
|
|
28
|
+
"""Immutable context for payroll execution."""
|
|
29
|
+
|
|
30
|
+
planilla_id: str
|
|
31
|
+
periodo_inicio: date
|
|
32
|
+
periodo_fin: date
|
|
33
|
+
fecha_calculo: date
|
|
34
|
+
usuario: Optional[str] = None
|
|
35
|
+
validation_result: "ValidationResult | None" = None
|
|
36
|
+
errors: list[str] = field(default_factory=list)
|
|
37
|
+
warnings: list[str] = field(default_factory=list)
|
|
38
|
+
|
|
39
|
+
def __post_init__(self):
|
|
40
|
+
"""Initialize default validation_result if None."""
|
|
41
|
+
if self.validation_result is None:
|
|
42
|
+
from ..results.validation_result import ValidationResult
|
|
43
|
+
|
|
44
|
+
object.__setattr__(self, "validation_result", ValidationResult())
|
|
@@ -0,0 +1,188 @@
|
|
|
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 payroll engine orchestrator - refactored implementation."""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from datetime import date
|
|
19
|
+
|
|
20
|
+
from coati_payroll.model import db, Planilla, Nomina
|
|
21
|
+
from coati_payroll.log import TRACE_LEVEL_NUM, is_trace_enabled, log
|
|
22
|
+
from .domain.employee_calculation import EmpleadoCalculo
|
|
23
|
+
from .services.payroll_execution_service import PayrollExecutionService
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class NominaEngine:
|
|
27
|
+
"""Engine for executing payroll runs.
|
|
28
|
+
|
|
29
|
+
This engine processes a Planilla configuration and generates a complete
|
|
30
|
+
Nomina with all employee calculations. It handles:
|
|
31
|
+
|
|
32
|
+
1. Perceptions (ingresos) - add to gross salary
|
|
33
|
+
2. Deductions (deducciones) - subtract from net salary, in priority order
|
|
34
|
+
3. Benefits (prestaciones) - employer costs, don't affect employee pay
|
|
35
|
+
4. Automatic deductions - loans and advances from Adelanto table
|
|
36
|
+
5. Accumulated annual values - for progressive tax calculations
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
planilla: Planilla,
|
|
42
|
+
periodo_inicio: date,
|
|
43
|
+
periodo_fin: date,
|
|
44
|
+
fecha_calculo: date | None = None,
|
|
45
|
+
usuario: str | None = None,
|
|
46
|
+
):
|
|
47
|
+
"""Initialize the payroll engine.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
planilla: The Planilla to execute
|
|
51
|
+
periodo_inicio: Start date of the payroll period
|
|
52
|
+
periodo_fin: End date of the payroll period
|
|
53
|
+
fecha_calculo: Date of calculation (defaults to today)
|
|
54
|
+
usuario: Username executing the payroll
|
|
55
|
+
"""
|
|
56
|
+
self.planilla = planilla
|
|
57
|
+
self.periodo_inicio = periodo_inicio
|
|
58
|
+
self.periodo_fin = periodo_fin
|
|
59
|
+
self.fecha_calculo = fecha_calculo or date.today()
|
|
60
|
+
self.usuario = usuario
|
|
61
|
+
self.nomina: Nomina | None = None
|
|
62
|
+
self.empleados_calculo: list[EmpleadoCalculo] = []
|
|
63
|
+
self.errors: list[str] = []
|
|
64
|
+
self.warnings: list[str] = []
|
|
65
|
+
|
|
66
|
+
# Initialize execution service
|
|
67
|
+
self.execution_service = PayrollExecutionService(db.session)
|
|
68
|
+
|
|
69
|
+
def _trace(self, message: str) -> None:
|
|
70
|
+
"""Trace helper for logging."""
|
|
71
|
+
if is_trace_enabled():
|
|
72
|
+
log.log(TRACE_LEVEL_NUM, message)
|
|
73
|
+
|
|
74
|
+
def validar_planilla(self) -> bool:
|
|
75
|
+
"""Validate that the planilla is ready for execution.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
True if valid, False otherwise
|
|
79
|
+
"""
|
|
80
|
+
from .domain.payroll_context import PayrollContext
|
|
81
|
+
|
|
82
|
+
context = PayrollContext(
|
|
83
|
+
planilla_id=self.planilla.id,
|
|
84
|
+
periodo_inicio=self.periodo_inicio,
|
|
85
|
+
periodo_fin=self.periodo_fin,
|
|
86
|
+
fecha_calculo=self.fecha_calculo,
|
|
87
|
+
usuario=self.usuario,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
validation_result = self.execution_service.planilla_validator.validate(context)
|
|
91
|
+
if not validation_result.is_valid:
|
|
92
|
+
self.errors.extend(validation_result.errors)
|
|
93
|
+
return False
|
|
94
|
+
|
|
95
|
+
return True
|
|
96
|
+
|
|
97
|
+
def ejecutar(self) -> Nomina | None:
|
|
98
|
+
"""Execute the payroll run.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
The generated Nomina record, or None if execution failed
|
|
102
|
+
"""
|
|
103
|
+
# Validate planilla
|
|
104
|
+
if not self.validar_planilla():
|
|
105
|
+
return None
|
|
106
|
+
|
|
107
|
+
# Execute payroll using service
|
|
108
|
+
nomina, empleados_calculo, errors, warnings = self.execution_service.execute_payroll(
|
|
109
|
+
self.planilla,
|
|
110
|
+
self.periodo_inicio,
|
|
111
|
+
self.periodo_fin,
|
|
112
|
+
self.fecha_calculo,
|
|
113
|
+
self.usuario,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
self.nomina = nomina
|
|
117
|
+
self.empleados_calculo = empleados_calculo
|
|
118
|
+
self.errors = errors
|
|
119
|
+
self.warnings = warnings
|
|
120
|
+
|
|
121
|
+
if nomina:
|
|
122
|
+
# Commit the transaction
|
|
123
|
+
db.session.commit()
|
|
124
|
+
return nomina
|
|
125
|
+
else:
|
|
126
|
+
# Rollback on failure
|
|
127
|
+
db.session.rollback()
|
|
128
|
+
return None
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def ejecutar_nomina(
|
|
132
|
+
planilla_id: str,
|
|
133
|
+
periodo_inicio: date,
|
|
134
|
+
periodo_fin: date,
|
|
135
|
+
fecha_calculo: date | None = None,
|
|
136
|
+
usuario: str | None = None,
|
|
137
|
+
) -> tuple[Nomina | None, list[str], list[str]]:
|
|
138
|
+
"""Execute a payroll run for a planilla.
|
|
139
|
+
|
|
140
|
+
Convenience function for executing a payroll run.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
planilla_id: ID of the Planilla to execute
|
|
144
|
+
periodo_inicio: Start date of the payroll period
|
|
145
|
+
periodo_fin: End date of the payroll period
|
|
146
|
+
fecha_calculo: Date of calculation (defaults to today)
|
|
147
|
+
usuario: Username executing the payroll
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
Tuple of (Nomina or None, list of errors, list of warnings)
|
|
151
|
+
"""
|
|
152
|
+
# Eagerly load all relationships needed for payroll processing
|
|
153
|
+
from sqlalchemy.orm import joinedload
|
|
154
|
+
from sqlalchemy import select
|
|
155
|
+
from coati_payroll.model import PlanillaIngreso, PlanillaDeduccion, PlanillaPrestacion, PlanillaEmpleado
|
|
156
|
+
|
|
157
|
+
planilla = (
|
|
158
|
+
db.session.execute(
|
|
159
|
+
select(Planilla)
|
|
160
|
+
.options(
|
|
161
|
+
joinedload(Planilla.planilla_percepciones).joinedload(PlanillaIngreso.percepcion),
|
|
162
|
+
joinedload(Planilla.planilla_deducciones).joinedload(PlanillaDeduccion.deduccion),
|
|
163
|
+
joinedload(Planilla.planilla_prestaciones).joinedload(PlanillaPrestacion.prestacion),
|
|
164
|
+
joinedload(Planilla.planilla_empleados).joinedload(PlanillaEmpleado.empleado),
|
|
165
|
+
joinedload(Planilla.planilla_reglas_calculo),
|
|
166
|
+
joinedload(Planilla.tipo_planilla),
|
|
167
|
+
joinedload(Planilla.moneda),
|
|
168
|
+
)
|
|
169
|
+
.filter(Planilla.id == planilla_id)
|
|
170
|
+
)
|
|
171
|
+
.unique()
|
|
172
|
+
.scalar_one_or_none()
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
if not planilla:
|
|
176
|
+
return None, ["Planilla no encontrada."], []
|
|
177
|
+
|
|
178
|
+
engine = NominaEngine(
|
|
179
|
+
planilla=planilla,
|
|
180
|
+
periodo_inicio=periodo_inicio,
|
|
181
|
+
periodo_fin=periodo_fin,
|
|
182
|
+
fecha_calculo=fecha_calculo,
|
|
183
|
+
usuario=usuario,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
nomina = engine.ejecutar()
|
|
187
|
+
|
|
188
|
+
return nomina, engine.errors, engine.warnings
|
|
@@ -0,0 +1,28 @@
|
|
|
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
|
+
"""Processors for specialized payroll operations."""
|
|
15
|
+
|
|
16
|
+
from .loan_processor import LoanProcessor
|
|
17
|
+
from .accumulation_processor import AccumulationProcessor
|
|
18
|
+
from .vacation_processor import VacationProcessor
|
|
19
|
+
from .novelty_processor import NoveltyProcessor
|
|
20
|
+
from .accounting_processor import AccountingProcessor
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"LoanProcessor",
|
|
24
|
+
"AccumulationProcessor",
|
|
25
|
+
"VacationProcessor",
|
|
26
|
+
"NoveltyProcessor",
|
|
27
|
+
"AccountingProcessor",
|
|
28
|
+
]
|
|
@@ -0,0 +1,171 @@
|
|
|
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
|
+
"""Accounting processor for creating payroll records."""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from datetime import date
|
|
19
|
+
from decimal import Decimal
|
|
20
|
+
|
|
21
|
+
from coati_payroll.model import db, Nomina, NominaEmpleado, NominaDetalle, Prestacion, PrestacionAcumulada
|
|
22
|
+
from ..domain.employee_calculation import EmpleadoCalculo
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class AccountingProcessor:
|
|
26
|
+
"""Processor for creating accounting records (NominaEmpleado, NominaDetalle, PrestacionAcumulada)."""
|
|
27
|
+
|
|
28
|
+
def create_nomina_empleado(self, emp_calculo: EmpleadoCalculo, nomina: Nomina) -> NominaEmpleado:
|
|
29
|
+
"""Create NominaEmpleado record with all details."""
|
|
30
|
+
empleado = emp_calculo.empleado
|
|
31
|
+
|
|
32
|
+
nomina_empleado = NominaEmpleado(
|
|
33
|
+
nomina_id=nomina.id,
|
|
34
|
+
empleado_id=empleado.id,
|
|
35
|
+
salario_bruto=emp_calculo.salario_bruto,
|
|
36
|
+
total_ingresos=emp_calculo.total_percepciones,
|
|
37
|
+
total_deducciones=emp_calculo.total_deducciones,
|
|
38
|
+
salario_neto=emp_calculo.salario_neto,
|
|
39
|
+
moneda_origen_id=emp_calculo.moneda_origen_id,
|
|
40
|
+
tipo_cambio_aplicado=emp_calculo.tipo_cambio,
|
|
41
|
+
cargo_snapshot=empleado.cargo,
|
|
42
|
+
area_snapshot=empleado.area,
|
|
43
|
+
centro_costos_snapshot=empleado.centro_costos,
|
|
44
|
+
sueldo_base_historico=emp_calculo.salario_base,
|
|
45
|
+
)
|
|
46
|
+
db.session.add(nomina_empleado)
|
|
47
|
+
db.session.flush()
|
|
48
|
+
|
|
49
|
+
# Create detail records for perceptions
|
|
50
|
+
orden = 0
|
|
51
|
+
for percepcion in emp_calculo.percepciones:
|
|
52
|
+
orden += 1
|
|
53
|
+
detalle = NominaDetalle(
|
|
54
|
+
nomina_empleado_id=nomina_empleado.id,
|
|
55
|
+
tipo="ingreso",
|
|
56
|
+
codigo=percepcion.codigo,
|
|
57
|
+
descripcion=percepcion.nombre,
|
|
58
|
+
monto=percepcion.monto,
|
|
59
|
+
orden=orden,
|
|
60
|
+
percepcion_id=percepcion.percepcion_id,
|
|
61
|
+
)
|
|
62
|
+
db.session.add(detalle)
|
|
63
|
+
|
|
64
|
+
# Create detail records for deductions
|
|
65
|
+
for deduccion in emp_calculo.deducciones:
|
|
66
|
+
orden += 1
|
|
67
|
+
detalle = NominaDetalle(
|
|
68
|
+
nomina_empleado_id=nomina_empleado.id,
|
|
69
|
+
tipo="deduccion",
|
|
70
|
+
codigo=deduccion.codigo,
|
|
71
|
+
descripcion=deduccion.nombre,
|
|
72
|
+
monto=deduccion.monto,
|
|
73
|
+
orden=orden,
|
|
74
|
+
deduccion_id=deduccion.deduccion_id,
|
|
75
|
+
)
|
|
76
|
+
db.session.add(detalle)
|
|
77
|
+
|
|
78
|
+
# Create detail records for benefits
|
|
79
|
+
for prestacion in emp_calculo.prestaciones:
|
|
80
|
+
orden += 1
|
|
81
|
+
detalle = NominaDetalle(
|
|
82
|
+
nomina_empleado_id=nomina_empleado.id,
|
|
83
|
+
tipo="prestacion",
|
|
84
|
+
codigo=prestacion.codigo,
|
|
85
|
+
descripcion=prestacion.nombre,
|
|
86
|
+
monto=prestacion.monto,
|
|
87
|
+
orden=orden,
|
|
88
|
+
prestacion_id=prestacion.prestacion_id,
|
|
89
|
+
)
|
|
90
|
+
db.session.add(detalle)
|
|
91
|
+
|
|
92
|
+
return nomina_empleado
|
|
93
|
+
|
|
94
|
+
def create_prestacion_transactions(
|
|
95
|
+
self,
|
|
96
|
+
emp_calculo: EmpleadoCalculo,
|
|
97
|
+
nomina: Nomina,
|
|
98
|
+
planilla,
|
|
99
|
+
periodo_fin: date,
|
|
100
|
+
fecha_calculo: date,
|
|
101
|
+
) -> None:
|
|
102
|
+
"""Create transactional records for accumulated benefits."""
|
|
103
|
+
empleado = emp_calculo.empleado
|
|
104
|
+
periodo_anio = periodo_fin.year
|
|
105
|
+
periodo_mes = periodo_fin.month
|
|
106
|
+
moneda_id = planilla.moneda_id
|
|
107
|
+
|
|
108
|
+
for prestacion_item in emp_calculo.prestaciones:
|
|
109
|
+
if not prestacion_item.prestacion_id:
|
|
110
|
+
continue
|
|
111
|
+
|
|
112
|
+
prestacion = db.session.get(Prestacion, prestacion_item.prestacion_id)
|
|
113
|
+
if not prestacion:
|
|
114
|
+
continue
|
|
115
|
+
|
|
116
|
+
# Get the previous balance
|
|
117
|
+
from sqlalchemy import select
|
|
118
|
+
|
|
119
|
+
ultima_transaccion = (
|
|
120
|
+
db.session.execute(
|
|
121
|
+
select(PrestacionAcumulada)
|
|
122
|
+
.filter(
|
|
123
|
+
PrestacionAcumulada.empleado_id == empleado.id,
|
|
124
|
+
PrestacionAcumulada.prestacion_id == prestacion.id,
|
|
125
|
+
)
|
|
126
|
+
.order_by(
|
|
127
|
+
PrestacionAcumulada.fecha_transaccion.desc(),
|
|
128
|
+
PrestacionAcumulada.creado.desc(),
|
|
129
|
+
)
|
|
130
|
+
.limit(1)
|
|
131
|
+
)
|
|
132
|
+
.unique()
|
|
133
|
+
.scalars()
|
|
134
|
+
.first()
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
saldo_anterior = ultima_transaccion.saldo_nuevo if ultima_transaccion else Decimal("0.00")
|
|
138
|
+
|
|
139
|
+
# For monthly settlement benefits, reset balance if new month
|
|
140
|
+
if prestacion.tipo_acumulacion == "mensual":
|
|
141
|
+
if ultima_transaccion and (
|
|
142
|
+
ultima_transaccion.anio != periodo_anio or ultima_transaccion.mes != periodo_mes
|
|
143
|
+
):
|
|
144
|
+
saldo_anterior = Decimal("0.00")
|
|
145
|
+
|
|
146
|
+
# Calculate new balance
|
|
147
|
+
monto_transaccion = prestacion_item.monto
|
|
148
|
+
saldo_nuevo = saldo_anterior + monto_transaccion
|
|
149
|
+
|
|
150
|
+
# Create the transaction record
|
|
151
|
+
transaccion = PrestacionAcumulada(
|
|
152
|
+
empleado_id=empleado.id,
|
|
153
|
+
prestacion_id=prestacion.id,
|
|
154
|
+
fecha_transaccion=fecha_calculo,
|
|
155
|
+
tipo_transaccion="adicion",
|
|
156
|
+
anio=periodo_anio,
|
|
157
|
+
mes=periodo_mes,
|
|
158
|
+
moneda_id=moneda_id,
|
|
159
|
+
monto_transaccion=monto_transaccion,
|
|
160
|
+
saldo_anterior=saldo_anterior,
|
|
161
|
+
saldo_nuevo=saldo_nuevo,
|
|
162
|
+
nomina_id=nomina.id,
|
|
163
|
+
observaciones=(
|
|
164
|
+
f"Provisión nómina {nomina.periodo_inicio.strftime('%Y-%m-%d')} - "
|
|
165
|
+
f"{nomina.periodo_fin.strftime('%Y-%m-%d')}"
|
|
166
|
+
),
|
|
167
|
+
procesado_por=nomina.generado_por,
|
|
168
|
+
creado_por=nomina.generado_por,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
db.session.add(transaccion)
|