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,173 @@
|
|
|
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 processing service for building calculation variables."""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from datetime import date
|
|
19
|
+
from decimal import Decimal
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
from coati_payroll.model import Empleado, Planilla, AcumuladoAnual
|
|
23
|
+
from ..domain.employee_calculation import EmpleadoCalculo
|
|
24
|
+
from ..repositories.acumulado_repository import AcumuladoRepository
|
|
25
|
+
from ..repositories.config_repository import ConfigRepository
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class EmployeeProcessingService:
|
|
29
|
+
"""Service for processing employee calculations and building variables."""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
config_repository: ConfigRepository,
|
|
34
|
+
acumulado_repository: AcumuladoRepository,
|
|
35
|
+
):
|
|
36
|
+
self.config_repo = config_repository
|
|
37
|
+
self.acumulado_repo = acumulado_repository
|
|
38
|
+
|
|
39
|
+
def build_calculation_variables(
|
|
40
|
+
self,
|
|
41
|
+
emp_calculo: EmpleadoCalculo,
|
|
42
|
+
planilla: Planilla,
|
|
43
|
+
periodo_inicio: date,
|
|
44
|
+
periodo_fin: date,
|
|
45
|
+
fecha_calculo: date,
|
|
46
|
+
) -> dict[str, Any]:
|
|
47
|
+
"""Build the calculation variables for an employee."""
|
|
48
|
+
empleado = emp_calculo.empleado
|
|
49
|
+
tipo_planilla = planilla.tipo_planilla
|
|
50
|
+
|
|
51
|
+
config = self.config_repo.get_for_empresa(planilla.empresa_id)
|
|
52
|
+
|
|
53
|
+
# Calculate days in period
|
|
54
|
+
dias_periodo = (periodo_fin - periodo_inicio).days + 1
|
|
55
|
+
|
|
56
|
+
# Calculate seniority using configuration
|
|
57
|
+
fecha_alta = empleado.fecha_alta or date.today()
|
|
58
|
+
antiguedad_dias = (fecha_calculo - fecha_alta).days
|
|
59
|
+
antiguedad_meses = antiguedad_dias // config.dias_mes_antiguedad
|
|
60
|
+
antiguedad_anios = antiguedad_dias // config.dias_anio_antiguedad
|
|
61
|
+
|
|
62
|
+
# Calculate remaining months in fiscal year
|
|
63
|
+
mes_inicio_fiscal = tipo_planilla.mes_inicio_fiscal if tipo_planilla else 1
|
|
64
|
+
meses_restantes = config.meses_anio_financiero - fecha_calculo.month + mes_inicio_fiscal
|
|
65
|
+
if meses_restantes > config.meses_anio_financiero:
|
|
66
|
+
meses_restantes -= config.meses_anio_financiero
|
|
67
|
+
if meses_restantes <= 0:
|
|
68
|
+
meses_restantes = 1
|
|
69
|
+
|
|
70
|
+
# Build variables dictionary
|
|
71
|
+
variables = {
|
|
72
|
+
# Employee base data
|
|
73
|
+
"salario_base": emp_calculo.salario_base,
|
|
74
|
+
"salario_mensual": emp_calculo.salario_mensual,
|
|
75
|
+
"tipo_cambio": emp_calculo.tipo_cambio,
|
|
76
|
+
# Period data
|
|
77
|
+
"fecha_calculo": fecha_calculo,
|
|
78
|
+
"periodo_inicio": periodo_inicio,
|
|
79
|
+
"periodo_fin": periodo_fin,
|
|
80
|
+
"dias_periodo": Decimal(str(dias_periodo)),
|
|
81
|
+
# Seniority
|
|
82
|
+
"fecha_alta": fecha_alta,
|
|
83
|
+
"antiguedad_dias": Decimal(str(antiguedad_dias)),
|
|
84
|
+
"antiguedad_meses": Decimal(str(antiguedad_meses)),
|
|
85
|
+
"antiguedad_anios": Decimal(str(antiguedad_anios)),
|
|
86
|
+
# Fiscal calculations
|
|
87
|
+
"meses_restantes": Decimal(str(meses_restantes)),
|
|
88
|
+
"periodos_por_anio": Decimal(
|
|
89
|
+
str(tipo_planilla.periodos_por_anio if tipo_planilla else config.meses_anio_financiero)
|
|
90
|
+
),
|
|
91
|
+
# Accumulated values (will be populated from AcumuladoAnual)
|
|
92
|
+
"salario_acumulado": Decimal("0.00"),
|
|
93
|
+
"impuesto_acumulado": Decimal("0.00"),
|
|
94
|
+
"ir_retenido_acumulado": Decimal("0.00"),
|
|
95
|
+
"salario_acumulado_mes": Decimal("0.00"),
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
# Add employee implementation initial values
|
|
99
|
+
if empleado.salario_acumulado:
|
|
100
|
+
variables["salario_acumulado"] = Decimal(str(empleado.salario_acumulado))
|
|
101
|
+
if empleado.impuesto_acumulado:
|
|
102
|
+
variables["impuesto_acumulado"] = Decimal(str(empleado.impuesto_acumulado))
|
|
103
|
+
variables["ir_retenido_acumulado"] = Decimal(str(empleado.impuesto_acumulado))
|
|
104
|
+
|
|
105
|
+
# Add novelties
|
|
106
|
+
for codigo, valor in emp_calculo.novedades.items():
|
|
107
|
+
variables[f"novedad_{codigo}"] = valor
|
|
108
|
+
|
|
109
|
+
# Load accumulated annual values
|
|
110
|
+
acumulado = self._get_acumulado_anual(empleado, planilla, fecha_calculo)
|
|
111
|
+
if acumulado:
|
|
112
|
+
variables["salario_acumulado"] += Decimal(str(acumulado.salario_bruto_acumulado or 0))
|
|
113
|
+
variables["impuesto_acumulado"] += Decimal(str(acumulado.impuesto_retenido_acumulado or 0))
|
|
114
|
+
variables["ir_retenido_acumulado"] += Decimal(str(acumulado.impuesto_retenido_acumulado or 0))
|
|
115
|
+
variables["salario_acumulado_mes"] = Decimal(str(acumulado.salario_acumulado_mes or 0))
|
|
116
|
+
|
|
117
|
+
# Additional accumulated values for progressive tax calculations
|
|
118
|
+
variables["salario_bruto_acumulado"] = Decimal(str(acumulado.salario_bruto_acumulado or 0))
|
|
119
|
+
variables["salario_gravable_acumulado"] = Decimal(str(acumulado.salario_gravable_acumulado or 0))
|
|
120
|
+
variables["deducciones_antes_impuesto_acumulado"] = Decimal(
|
|
121
|
+
str(acumulado.deducciones_antes_impuesto_acumulado or 0)
|
|
122
|
+
)
|
|
123
|
+
variables["periodos_procesados"] = Decimal(str(acumulado.periodos_procesados or 0))
|
|
124
|
+
variables["meses_trabajados"] = Decimal(str(acumulado.periodos_procesados or 0))
|
|
125
|
+
|
|
126
|
+
# Calculate net accumulated salary
|
|
127
|
+
variables["salario_neto_acumulado"] = Decimal(str(acumulado.salario_bruto_acumulado or 0)) - Decimal(
|
|
128
|
+
str(acumulado.deducciones_antes_impuesto_acumulado or 0)
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
# Include initial accumulated values from employee
|
|
132
|
+
variables["salario_inicial_acumulado"] = Decimal(str(empleado.salario_acumulado or 0))
|
|
133
|
+
variables["impuesto_inicial_acumulado"] = Decimal(str(empleado.impuesto_acumulado or 0))
|
|
134
|
+
|
|
135
|
+
return variables
|
|
136
|
+
|
|
137
|
+
def _get_acumulado_anual(
|
|
138
|
+
self, empleado: Empleado, planilla: Planilla, fecha_calculo: date
|
|
139
|
+
) -> AcumuladoAnual | None:
|
|
140
|
+
"""Get accumulated annual values for employee."""
|
|
141
|
+
if not planilla.tipo_planilla:
|
|
142
|
+
return None
|
|
143
|
+
|
|
144
|
+
tipo_planilla = planilla.tipo_planilla
|
|
145
|
+
|
|
146
|
+
# Calculate fiscal period
|
|
147
|
+
anio = fecha_calculo.year
|
|
148
|
+
mes_inicio = tipo_planilla.mes_inicio_fiscal
|
|
149
|
+
dia_inicio = tipo_planilla.dia_inicio_fiscal
|
|
150
|
+
|
|
151
|
+
if fecha_calculo.month < mes_inicio:
|
|
152
|
+
anio -= 1
|
|
153
|
+
|
|
154
|
+
periodo_fiscal_inicio = date(anio, mes_inicio, dia_inicio)
|
|
155
|
+
|
|
156
|
+
# Look up existing accumulated record
|
|
157
|
+
from sqlalchemy import select
|
|
158
|
+
from coati_payroll.model import db
|
|
159
|
+
|
|
160
|
+
acumulado = (
|
|
161
|
+
db.session.execute(
|
|
162
|
+
select(AcumuladoAnual).filter(
|
|
163
|
+
AcumuladoAnual.empleado_id == empleado.id,
|
|
164
|
+
AcumuladoAnual.tipo_planilla_id == tipo_planilla.id,
|
|
165
|
+
AcumuladoAnual.empresa_id == planilla.empresa_id,
|
|
166
|
+
AcumuladoAnual.periodo_fiscal_inicio == periodo_fiscal_inicio,
|
|
167
|
+
)
|
|
168
|
+
)
|
|
169
|
+
.unique()
|
|
170
|
+
.scalar_one_or_none()
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
return acumulado
|
|
@@ -0,0 +1,374 @@
|
|
|
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 service - main business logic orchestrator."""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from datetime import date, datetime, timezone
|
|
19
|
+
from decimal import Decimal, ROUND_HALF_UP
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
from coati_payroll.model import db, Planilla, Empleado, Nomina
|
|
23
|
+
from coati_payroll.enums import NominaEstado
|
|
24
|
+
from coati_payroll.formula_engine import FormulaEngineError
|
|
25
|
+
from ..domain.employee_calculation import EmpleadoCalculo
|
|
26
|
+
from ..repositories.planilla_repository import PlanillaRepository
|
|
27
|
+
from ..repositories.config_repository import ConfigRepository
|
|
28
|
+
from ..repositories.exchange_rate_repository import ExchangeRateRepository
|
|
29
|
+
from ..repositories.novelty_repository import NoveltyRepository
|
|
30
|
+
from ..repositories.acumulado_repository import AcumuladoRepository
|
|
31
|
+
from ..validators.planilla_validator import PlanillaValidator
|
|
32
|
+
from ..validators.employee_validator import EmployeeValidator
|
|
33
|
+
from ..validators import ValidationError, NominaEngineError
|
|
34
|
+
from ..calculators.salary_calculator import SalaryCalculator
|
|
35
|
+
from ..calculators.exchange_rate_calculator import ExchangeRateCalculator
|
|
36
|
+
from ..calculators.concept_calculator import ConceptCalculator
|
|
37
|
+
from ..calculators.perception_calculator import PerceptionCalculator
|
|
38
|
+
from ..calculators.deduction_calculator import DeductionCalculator
|
|
39
|
+
from ..calculators.benefit_calculator import BenefitCalculator
|
|
40
|
+
from ..processors.loan_processor import LoanProcessor
|
|
41
|
+
from ..processors.accumulation_processor import AccumulationProcessor
|
|
42
|
+
from ..processors.vacation_processor import VacationProcessor
|
|
43
|
+
from ..processors.novelty_processor import NoveltyProcessor
|
|
44
|
+
from ..processors.accounting_processor import AccountingProcessor
|
|
45
|
+
from ..services.employee_processing_service import EmployeeProcessingService
|
|
46
|
+
from ..services.snapshot_service import SnapshotService
|
|
47
|
+
from ..services.accounting_voucher_service import AccountingVoucherService
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class PayrollExecutionService:
|
|
51
|
+
"""Main service for executing payroll runs."""
|
|
52
|
+
|
|
53
|
+
def __init__(self, session):
|
|
54
|
+
self.session = session
|
|
55
|
+
|
|
56
|
+
# Initialize repositories
|
|
57
|
+
self.planilla_repo = PlanillaRepository(session)
|
|
58
|
+
self.config_repo = ConfigRepository(session)
|
|
59
|
+
self.exchange_rate_repo = ExchangeRateRepository(session)
|
|
60
|
+
self.novelty_repo = NoveltyRepository(session)
|
|
61
|
+
self.acumulado_repo = AcumuladoRepository(session)
|
|
62
|
+
|
|
63
|
+
# Initialize validators
|
|
64
|
+
self.planilla_validator = PlanillaValidator(self.planilla_repo)
|
|
65
|
+
self.employee_validator = EmployeeValidator()
|
|
66
|
+
|
|
67
|
+
# Initialize calculators (warnings list will be set later)
|
|
68
|
+
self.salary_calculator = SalaryCalculator(self.config_repo)
|
|
69
|
+
self.exchange_rate_calculator = ExchangeRateCalculator(self.exchange_rate_repo)
|
|
70
|
+
self.concept_calculator = ConceptCalculator(self.config_repo, []) # warnings set in execute_payroll
|
|
71
|
+
self.perception_calculator = PerceptionCalculator(self.concept_calculator)
|
|
72
|
+
self.deduction_calculator = DeductionCalculator(self.concept_calculator, []) # warnings set in execute_payroll
|
|
73
|
+
self.benefit_calculator = BenefitCalculator(self.concept_calculator)
|
|
74
|
+
|
|
75
|
+
# Initialize processors
|
|
76
|
+
self.novelty_processor = NoveltyProcessor(self.novelty_repo)
|
|
77
|
+
self.accumulation_processor = AccumulationProcessor(self.acumulado_repo)
|
|
78
|
+
self.accounting_processor = AccountingProcessor()
|
|
79
|
+
|
|
80
|
+
# Initialize services
|
|
81
|
+
self.employee_processing_service = EmployeeProcessingService(self.config_repo, self.acumulado_repo)
|
|
82
|
+
self.snapshot_service = SnapshotService(session)
|
|
83
|
+
self.accounting_voucher_service = AccountingVoucherService(session)
|
|
84
|
+
|
|
85
|
+
def execute_payroll(
|
|
86
|
+
self,
|
|
87
|
+
planilla: Planilla,
|
|
88
|
+
periodo_inicio: date,
|
|
89
|
+
periodo_fin: date,
|
|
90
|
+
fecha_calculo: date,
|
|
91
|
+
usuario: str | None,
|
|
92
|
+
) -> tuple[Nomina | None, list[EmpleadoCalculo], list[str], list[str]]:
|
|
93
|
+
"""Execute a complete payroll run."""
|
|
94
|
+
errors: list[str] = []
|
|
95
|
+
warnings: list[str] = []
|
|
96
|
+
|
|
97
|
+
# Update warnings list for calculators (they need shared reference)
|
|
98
|
+
self.concept_calculator.warnings = warnings
|
|
99
|
+
self.deduction_calculator.warnings = warnings
|
|
100
|
+
|
|
101
|
+
# Validate planilla
|
|
102
|
+
from ..domain.payroll_context import PayrollContext
|
|
103
|
+
|
|
104
|
+
context = PayrollContext(
|
|
105
|
+
planilla_id=planilla.id,
|
|
106
|
+
periodo_inicio=periodo_inicio,
|
|
107
|
+
periodo_fin=periodo_fin,
|
|
108
|
+
fecha_calculo=fecha_calculo,
|
|
109
|
+
usuario=usuario,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
validation_result = self.planilla_validator.validate(context)
|
|
113
|
+
if not validation_result.is_valid:
|
|
114
|
+
errors.extend(validation_result.errors)
|
|
115
|
+
return None, [], errors, warnings
|
|
116
|
+
|
|
117
|
+
# Capture configuration snapshots for recalculation consistency
|
|
118
|
+
snapshot = self.snapshot_service.capture_complete_snapshot(planilla, fecha_calculo)
|
|
119
|
+
|
|
120
|
+
# Create the Nomina record
|
|
121
|
+
nomina = Nomina(
|
|
122
|
+
planilla_id=planilla.id,
|
|
123
|
+
periodo_inicio=periodo_inicio,
|
|
124
|
+
periodo_fin=periodo_fin,
|
|
125
|
+
generado_por=usuario,
|
|
126
|
+
estado=NominaEstado.GENERADO,
|
|
127
|
+
total_bruto=Decimal("0.00"),
|
|
128
|
+
total_deducciones=Decimal("0.00"),
|
|
129
|
+
total_neto=Decimal("0.00"),
|
|
130
|
+
fecha_calculo_original=fecha_calculo,
|
|
131
|
+
configuracion_snapshot=snapshot["configuracion"],
|
|
132
|
+
tipos_cambio_snapshot=snapshot["tipos_cambio"],
|
|
133
|
+
catalogos_snapshot=snapshot["catalogos"],
|
|
134
|
+
)
|
|
135
|
+
db.session.add(nomina)
|
|
136
|
+
db.session.flush()
|
|
137
|
+
|
|
138
|
+
# Initialize processors that need nomina
|
|
139
|
+
loan_processor = LoanProcessor(nomina, fecha_calculo, periodo_inicio, periodo_fin)
|
|
140
|
+
vacation_processor = VacationProcessor(planilla, periodo_inicio, periodo_fin, usuario, warnings)
|
|
141
|
+
|
|
142
|
+
# Update warnings reference for calculators (shared list)
|
|
143
|
+
self.concept_calculator.warnings = warnings
|
|
144
|
+
self.deduction_calculator.warnings = warnings
|
|
145
|
+
|
|
146
|
+
# Process each employee
|
|
147
|
+
empleados_calculo: list[EmpleadoCalculo] = []
|
|
148
|
+
|
|
149
|
+
for planilla_empleado in planilla.planilla_empleados:
|
|
150
|
+
if not planilla_empleado.activo:
|
|
151
|
+
continue
|
|
152
|
+
|
|
153
|
+
empleado = planilla_empleado.empleado
|
|
154
|
+
if not empleado.activo:
|
|
155
|
+
warnings.append(
|
|
156
|
+
f"Empleado {empleado.primer_nombre} {empleado.primer_apellido} no está activo y será omitido."
|
|
157
|
+
)
|
|
158
|
+
continue
|
|
159
|
+
|
|
160
|
+
try:
|
|
161
|
+
emp_calculo = self._process_employee(
|
|
162
|
+
empleado,
|
|
163
|
+
planilla,
|
|
164
|
+
periodo_inicio,
|
|
165
|
+
periodo_fin,
|
|
166
|
+
fecha_calculo,
|
|
167
|
+
nomina,
|
|
168
|
+
loan_processor,
|
|
169
|
+
vacation_processor,
|
|
170
|
+
)
|
|
171
|
+
empleados_calculo.append(emp_calculo)
|
|
172
|
+
except (NominaEngineError, FormulaEngineError) as e:
|
|
173
|
+
# Capture all payroll engine and formula errors
|
|
174
|
+
errors.append(
|
|
175
|
+
f"Error procesando empleado {empleado.primer_nombre} {empleado.primer_apellido}: {str(e)}"
|
|
176
|
+
)
|
|
177
|
+
except Exception as e:
|
|
178
|
+
# Capture any unexpected error to prevent 500 errors
|
|
179
|
+
errors.append(
|
|
180
|
+
f"Error inesperado procesando empleado {empleado.primer_nombre} {empleado.primer_apellido}: "
|
|
181
|
+
f"{type(e).__name__}: {str(e)}"
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
# Calculate totals
|
|
185
|
+
self._calculate_totals(nomina, empleados_calculo)
|
|
186
|
+
|
|
187
|
+
# Update planilla last execution
|
|
188
|
+
planilla.ultima_ejecucion = datetime.now(timezone.utc)
|
|
189
|
+
|
|
190
|
+
# Save errors and warnings to log_procesamiento for transparency
|
|
191
|
+
self._save_log_entries(nomina, errors, warnings, empleados_calculo)
|
|
192
|
+
|
|
193
|
+
# Generate accounting voucher
|
|
194
|
+
try:
|
|
195
|
+
self.accounting_voucher_service.generate_accounting_voucher(nomina, planilla, fecha_calculo, usuario)
|
|
196
|
+
db.session.flush()
|
|
197
|
+
except Exception as e:
|
|
198
|
+
# Don't fail the payroll if voucher generation fails
|
|
199
|
+
warnings.append(f"Advertencia al generar comprobante contable: {str(e)}")
|
|
200
|
+
|
|
201
|
+
return nomina, empleados_calculo, errors, warnings
|
|
202
|
+
|
|
203
|
+
def _save_log_entries(
|
|
204
|
+
self,
|
|
205
|
+
nomina: Nomina,
|
|
206
|
+
errors: list[str],
|
|
207
|
+
warnings: list[str],
|
|
208
|
+
empleados_calculo: list[EmpleadoCalculo],
|
|
209
|
+
) -> None:
|
|
210
|
+
"""Save errors, warnings and processing info to nomina.log_procesamiento.
|
|
211
|
+
|
|
212
|
+
This ensures all processing issues are visible in the nomina log,
|
|
213
|
+
not just as flash messages.
|
|
214
|
+
"""
|
|
215
|
+
log_entries: list[dict[str, Any]] = []
|
|
216
|
+
timestamp = datetime.now(timezone.utc).isoformat()
|
|
217
|
+
|
|
218
|
+
# Log successful employee processing
|
|
219
|
+
for emp_calculo in empleados_calculo:
|
|
220
|
+
empleado = emp_calculo.empleado
|
|
221
|
+
log_entries.append(
|
|
222
|
+
{
|
|
223
|
+
"timestamp": timestamp,
|
|
224
|
+
"empleado": f"{empleado.primer_nombre} {empleado.primer_apellido}",
|
|
225
|
+
"status": "success",
|
|
226
|
+
"message": f"Procesado correctamente. Salario neto: {emp_calculo.salario_neto}",
|
|
227
|
+
}
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
# Log errors
|
|
231
|
+
for error in errors:
|
|
232
|
+
log_entries.append(
|
|
233
|
+
{
|
|
234
|
+
"timestamp": timestamp,
|
|
235
|
+
"empleado": "SISTEMA",
|
|
236
|
+
"status": "error",
|
|
237
|
+
"message": error,
|
|
238
|
+
}
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
# Log warnings
|
|
242
|
+
for warning in warnings:
|
|
243
|
+
log_entries.append(
|
|
244
|
+
{
|
|
245
|
+
"timestamp": timestamp,
|
|
246
|
+
"empleado": "SISTEMA",
|
|
247
|
+
"status": "warning",
|
|
248
|
+
"message": warning,
|
|
249
|
+
}
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
nomina.log_procesamiento = log_entries
|
|
253
|
+
|
|
254
|
+
def _process_employee(
|
|
255
|
+
self,
|
|
256
|
+
empleado: Empleado,
|
|
257
|
+
planilla: Planilla,
|
|
258
|
+
periodo_inicio: date,
|
|
259
|
+
periodo_fin: date,
|
|
260
|
+
fecha_calculo: date,
|
|
261
|
+
nomina: Nomina,
|
|
262
|
+
loan_processor: LoanProcessor,
|
|
263
|
+
vacation_processor: VacationProcessor,
|
|
264
|
+
) -> EmpleadoCalculo:
|
|
265
|
+
"""Process a single employee's payroll."""
|
|
266
|
+
# Validate employee
|
|
267
|
+
employee_validation = self.employee_validator.validate_employee(
|
|
268
|
+
empleado, planilla.empresa_id, periodo_inicio, periodo_fin
|
|
269
|
+
)
|
|
270
|
+
if not employee_validation.is_valid:
|
|
271
|
+
# Include specific validation errors (errors is already a list of strings)
|
|
272
|
+
error_messages = employee_validation.errors
|
|
273
|
+
raise ValidationError(f"Empleado {empleado.codigo_empleado}: {'; '.join(error_messages)}")
|
|
274
|
+
|
|
275
|
+
emp_calculo = EmpleadoCalculo(empleado, planilla)
|
|
276
|
+
|
|
277
|
+
# Get exchange rate
|
|
278
|
+
emp_calculo.tipo_cambio = self.exchange_rate_calculator.get_exchange_rate(empleado, planilla, fecha_calculo)
|
|
279
|
+
|
|
280
|
+
# Apply exchange rate to convert employee salary to planilla currency
|
|
281
|
+
salario_mensual = emp_calculo.salario_base
|
|
282
|
+
if emp_calculo.tipo_cambio != Decimal("1.00"):
|
|
283
|
+
salario_mensual = (salario_mensual * emp_calculo.tipo_cambio).quantize(
|
|
284
|
+
Decimal("0.01"), rounding=ROUND_HALF_UP
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
# Calculate salary for the pay period
|
|
288
|
+
emp_calculo.salario_base = self.salary_calculator.calculate_period_salary(
|
|
289
|
+
salario_mensual, planilla, periodo_inicio, periodo_fin, fecha_calculo
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
# Store the monthly salary for use in calculations
|
|
293
|
+
emp_calculo.salario_mensual = salario_mensual
|
|
294
|
+
|
|
295
|
+
# Load employee novelties
|
|
296
|
+
emp_calculo.novedades = self.novelty_processor.load_novelties(empleado, periodo_inicio, periodo_fin)
|
|
297
|
+
|
|
298
|
+
# Build calculation variables
|
|
299
|
+
emp_calculo.variables_calculo = self.employee_processing_service.build_calculation_variables(
|
|
300
|
+
emp_calculo, planilla, periodo_inicio, periodo_fin, fecha_calculo
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
# Process perceptions
|
|
304
|
+
percepciones = self.perception_calculator.calculate(emp_calculo, planilla, fecha_calculo)
|
|
305
|
+
emp_calculo.percepciones = percepciones
|
|
306
|
+
emp_calculo.total_percepciones = sum(p.monto for p in percepciones)
|
|
307
|
+
|
|
308
|
+
# Calculate gross salary
|
|
309
|
+
emp_calculo.salario_bruto = emp_calculo.salario_base + emp_calculo.total_percepciones
|
|
310
|
+
|
|
311
|
+
# Process deductions
|
|
312
|
+
deducciones = self.deduction_calculator.calculate(emp_calculo, planilla, fecha_calculo)
|
|
313
|
+
emp_calculo.deducciones = deducciones
|
|
314
|
+
emp_calculo.total_deducciones = sum(d.monto for d in deducciones)
|
|
315
|
+
|
|
316
|
+
# Apply automatic loan/advance deductions
|
|
317
|
+
saldo_disponible = emp_calculo.salario_bruto - emp_calculo.total_deducciones
|
|
318
|
+
|
|
319
|
+
loan_deductions = loan_processor.process_loans(
|
|
320
|
+
empleado.id, saldo_disponible, planilla.aplicar_prestamos_automatico, planilla.prioridad_prestamos
|
|
321
|
+
)
|
|
322
|
+
emp_calculo.deducciones.extend(loan_deductions)
|
|
323
|
+
emp_calculo.total_deducciones += sum(d.monto for d in loan_deductions)
|
|
324
|
+
saldo_disponible -= sum(d.monto for d in loan_deductions)
|
|
325
|
+
|
|
326
|
+
advance_deductions = loan_processor.process_advances(
|
|
327
|
+
empleado.id, saldo_disponible, planilla.aplicar_adelantos_automatico, planilla.prioridad_adelantos
|
|
328
|
+
)
|
|
329
|
+
emp_calculo.deducciones.extend(advance_deductions)
|
|
330
|
+
emp_calculo.total_deducciones += sum(d.monto for d in advance_deductions)
|
|
331
|
+
|
|
332
|
+
# Calculate net salary
|
|
333
|
+
emp_calculo.salario_neto = emp_calculo.salario_bruto - emp_calculo.total_deducciones
|
|
334
|
+
|
|
335
|
+
# Ensure net salary is not negative
|
|
336
|
+
# Note: warnings list is shared via loan_processor context, so warnings will be added there
|
|
337
|
+
if emp_calculo.salario_neto < 0:
|
|
338
|
+
emp_calculo.salario_neto = Decimal("0.00")
|
|
339
|
+
|
|
340
|
+
# Process employer benefits
|
|
341
|
+
prestaciones = self.benefit_calculator.calculate(emp_calculo, planilla, fecha_calculo)
|
|
342
|
+
emp_calculo.prestaciones = prestaciones
|
|
343
|
+
emp_calculo.total_prestaciones = sum(p.monto for p in prestaciones)
|
|
344
|
+
|
|
345
|
+
# Create NominaEmpleado record and update accumulados
|
|
346
|
+
nomina_empleado = self.accounting_processor.create_nomina_empleado(emp_calculo, nomina)
|
|
347
|
+
|
|
348
|
+
# Update accumulated annual values
|
|
349
|
+
self.accumulation_processor.update_accumulations(emp_calculo, planilla, periodo_fin, fecha_calculo)
|
|
350
|
+
|
|
351
|
+
# Create prestacion accumulation transactions
|
|
352
|
+
self.accounting_processor.create_prestacion_transactions(
|
|
353
|
+
emp_calculo, nomina, planilla, periodo_fin, fecha_calculo
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
# Process vacation accrual and usage
|
|
357
|
+
vacation_processor.process_vacations(empleado, emp_calculo, nomina_empleado)
|
|
358
|
+
|
|
359
|
+
return emp_calculo
|
|
360
|
+
|
|
361
|
+
def _calculate_totals(self, nomina: Nomina, empleados_calculo: list[EmpleadoCalculo]) -> None:
|
|
362
|
+
"""Calculate grand totals for the nomina."""
|
|
363
|
+
total_bruto = Decimal("0.00")
|
|
364
|
+
total_deducciones = Decimal("0.00")
|
|
365
|
+
total_neto = Decimal("0.00")
|
|
366
|
+
|
|
367
|
+
for emp_calculo in empleados_calculo:
|
|
368
|
+
total_bruto += emp_calculo.salario_bruto
|
|
369
|
+
total_deducciones += emp_calculo.total_deducciones
|
|
370
|
+
total_neto += emp_calculo.salario_neto
|
|
371
|
+
|
|
372
|
+
nomina.total_bruto = total_bruto
|
|
373
|
+
nomina.total_deducciones = total_deducciones
|
|
374
|
+
nomina.total_neto = total_neto
|