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,90 @@
|
|
|
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
|
+
"""Accumulation processor for annual accumulated values."""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from datetime import date
|
|
19
|
+
|
|
20
|
+
from coati_payroll.model import db, Deduccion
|
|
21
|
+
from coati_payroll.i18n import _
|
|
22
|
+
from ..domain.employee_calculation import EmpleadoCalculo
|
|
23
|
+
from ..repositories.acumulado_repository import AcumuladoRepository
|
|
24
|
+
from ..validators import ValidationError
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class AccumulationProcessor:
|
|
28
|
+
"""Processor for updating accumulated annual values."""
|
|
29
|
+
|
|
30
|
+
def __init__(self, acumulado_repository: AcumuladoRepository):
|
|
31
|
+
self.acumulado_repo = acumulado_repository
|
|
32
|
+
|
|
33
|
+
def update_accumulations(
|
|
34
|
+
self,
|
|
35
|
+
emp_calculo: EmpleadoCalculo,
|
|
36
|
+
planilla,
|
|
37
|
+
periodo_fin: date,
|
|
38
|
+
fecha_calculo: date,
|
|
39
|
+
) -> None:
|
|
40
|
+
"""Update accumulated annual values for the employee."""
|
|
41
|
+
if not planilla.tipo_planilla:
|
|
42
|
+
return
|
|
43
|
+
|
|
44
|
+
tipo_planilla = planilla.tipo_planilla
|
|
45
|
+
empleado = emp_calculo.empleado
|
|
46
|
+
|
|
47
|
+
# Calculate fiscal period
|
|
48
|
+
anio = fecha_calculo.year
|
|
49
|
+
mes_inicio = tipo_planilla.mes_inicio_fiscal
|
|
50
|
+
dia_inicio = tipo_planilla.dia_inicio_fiscal
|
|
51
|
+
|
|
52
|
+
if fecha_calculo.month < mes_inicio:
|
|
53
|
+
anio -= 1
|
|
54
|
+
|
|
55
|
+
periodo_fiscal_inicio = date(anio, mes_inicio, dia_inicio)
|
|
56
|
+
|
|
57
|
+
# Determine empresa_id
|
|
58
|
+
empresa_id = planilla.empresa_id or empleado.empresa_id
|
|
59
|
+
if not empresa_id:
|
|
60
|
+
raise ValidationError(
|
|
61
|
+
_("No se puede crear acumulado anual: ni la planilla ni el empleado tienen empresa_id asignado")
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# Get or create accumulated record
|
|
65
|
+
acumulado = self.acumulado_repo.get_or_create(empleado, tipo_planilla.id, empresa_id, periodo_fiscal_inicio)
|
|
66
|
+
|
|
67
|
+
# Reset monthly accumulation if entering a new month
|
|
68
|
+
acumulado.reset_mes_acumulado_if_needed(periodo_fin)
|
|
69
|
+
|
|
70
|
+
# Update accumulated values
|
|
71
|
+
acumulado.salario_bruto_acumulado += emp_calculo.salario_bruto
|
|
72
|
+
acumulado.salario_acumulado_mes += emp_calculo.salario_bruto
|
|
73
|
+
acumulado.periodos_procesados += 1
|
|
74
|
+
acumulado.ultimo_periodo_procesado = periodo_fin
|
|
75
|
+
|
|
76
|
+
# Calculate gravable income (perceptions that are gravable)
|
|
77
|
+
salario_gravable = emp_calculo.salario_base
|
|
78
|
+
for percepcion in emp_calculo.percepciones:
|
|
79
|
+
if percepcion.gravable:
|
|
80
|
+
salario_gravable += percepcion.monto
|
|
81
|
+
acumulado.salario_gravable_acumulado += salario_gravable
|
|
82
|
+
|
|
83
|
+
# Sum up before-tax deductions and taxes
|
|
84
|
+
for deduccion in emp_calculo.deducciones:
|
|
85
|
+
deduccion_obj = db.session.get(Deduccion, deduccion.deduccion_id) if deduccion.deduccion_id else None
|
|
86
|
+
if deduccion_obj:
|
|
87
|
+
if deduccion_obj.es_impuesto:
|
|
88
|
+
acumulado.impuesto_retenido_acumulado += deduccion.monto
|
|
89
|
+
elif deduccion_obj.antes_impuesto:
|
|
90
|
+
acumulado.deducciones_antes_impuesto_acumulado += deduccion.monto
|
|
@@ -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
|
+
"""Loan processor for automatic loan and advance deductions."""
|
|
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, Adelanto, AdelantoAbono, Nomina, Liquidacion
|
|
22
|
+
from coati_payroll.enums import AdelantoEstado
|
|
23
|
+
from coati_payroll.i18n import _
|
|
24
|
+
from ..domain.calculation_items import DeduccionItem
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class LoanProcessor:
|
|
28
|
+
"""Processor for automatic loan and advance deductions."""
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
nomina: Nomina | None,
|
|
33
|
+
fecha_calculo: date,
|
|
34
|
+
periodo_inicio: date,
|
|
35
|
+
periodo_fin: date,
|
|
36
|
+
liquidacion: Liquidacion | None = None,
|
|
37
|
+
calcular_interes: bool = True,
|
|
38
|
+
):
|
|
39
|
+
self.nomina = nomina
|
|
40
|
+
self.liquidacion = liquidacion
|
|
41
|
+
self.fecha_calculo = fecha_calculo
|
|
42
|
+
self.periodo_inicio = periodo_inicio
|
|
43
|
+
self.periodo_fin = periodo_fin
|
|
44
|
+
self.calcular_interes = calcular_interes
|
|
45
|
+
|
|
46
|
+
def process_loans(
|
|
47
|
+
self, empleado_id: str, saldo_disponible: Decimal, aplicar_prestamos: bool, prioridad_prestamos: int
|
|
48
|
+
) -> list[DeduccionItem]:
|
|
49
|
+
"""Process loans for an employee."""
|
|
50
|
+
deductions = []
|
|
51
|
+
|
|
52
|
+
if not aplicar_prestamos:
|
|
53
|
+
return deductions
|
|
54
|
+
|
|
55
|
+
# Get active loans
|
|
56
|
+
from sqlalchemy import select
|
|
57
|
+
|
|
58
|
+
prestamos = list(
|
|
59
|
+
db.session.execute(
|
|
60
|
+
select(Adelanto).filter(
|
|
61
|
+
Adelanto.empleado_id == empleado_id,
|
|
62
|
+
Adelanto.estado == AdelantoEstado.APROBADO,
|
|
63
|
+
Adelanto.saldo_pendiente > 0,
|
|
64
|
+
Adelanto.deduccion_id.isnot(None), # Only loans, not advances
|
|
65
|
+
)
|
|
66
|
+
)
|
|
67
|
+
.scalars()
|
|
68
|
+
.all()
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
for prestamo in prestamos:
|
|
72
|
+
if saldo_disponible <= 0:
|
|
73
|
+
break
|
|
74
|
+
|
|
75
|
+
# Calculate and apply interest if applicable
|
|
76
|
+
if self.calcular_interes:
|
|
77
|
+
self._calculate_interest(prestamo)
|
|
78
|
+
|
|
79
|
+
monto_cuota = Decimal(str(prestamo.monto_por_cuota or 0))
|
|
80
|
+
if monto_cuota <= 0:
|
|
81
|
+
continue
|
|
82
|
+
|
|
83
|
+
monto_aplicar = min(monto_cuota, saldo_disponible)
|
|
84
|
+
|
|
85
|
+
item = DeduccionItem(
|
|
86
|
+
codigo=f"PRESTAMO_{prestamo.id[:8]}",
|
|
87
|
+
nombre=f"Cuota préstamo - {prestamo.motivo or 'N/A'}",
|
|
88
|
+
monto=monto_aplicar,
|
|
89
|
+
prioridad=prioridad_prestamos,
|
|
90
|
+
es_obligatoria=False,
|
|
91
|
+
tipo="prestamo",
|
|
92
|
+
)
|
|
93
|
+
deductions.append(item)
|
|
94
|
+
saldo_disponible -= monto_aplicar
|
|
95
|
+
|
|
96
|
+
# Record the payment
|
|
97
|
+
self._record_payment(prestamo, monto_aplicar)
|
|
98
|
+
|
|
99
|
+
return deductions
|
|
100
|
+
|
|
101
|
+
def process_advances(
|
|
102
|
+
self, empleado_id: str, saldo_disponible: Decimal, aplicar_adelantos: bool, prioridad_adelantos: int
|
|
103
|
+
) -> list[DeduccionItem]:
|
|
104
|
+
"""Process salary advances for an employee."""
|
|
105
|
+
deductions = []
|
|
106
|
+
|
|
107
|
+
if not aplicar_adelantos:
|
|
108
|
+
return deductions
|
|
109
|
+
|
|
110
|
+
from sqlalchemy import select
|
|
111
|
+
|
|
112
|
+
adelantos = list(
|
|
113
|
+
db.session.execute(
|
|
114
|
+
select(Adelanto).filter(
|
|
115
|
+
Adelanto.empleado_id == empleado_id,
|
|
116
|
+
Adelanto.estado == AdelantoEstado.APROBADO,
|
|
117
|
+
Adelanto.saldo_pendiente > 0,
|
|
118
|
+
Adelanto.deduccion_id.is_(None), # Only advances, not loans
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
.scalars()
|
|
122
|
+
.all()
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
for adelanto in adelantos:
|
|
126
|
+
if saldo_disponible <= 0:
|
|
127
|
+
break
|
|
128
|
+
|
|
129
|
+
monto_cuota = Decimal(str(adelanto.monto_por_cuota or adelanto.saldo_pendiente))
|
|
130
|
+
monto_aplicar = min(monto_cuota, saldo_disponible)
|
|
131
|
+
|
|
132
|
+
item = DeduccionItem(
|
|
133
|
+
codigo=f"ADELANTO_{adelanto.id[:8]}",
|
|
134
|
+
nombre=f"Adelanto salarial - {adelanto.motivo or 'N/A'}",
|
|
135
|
+
monto=monto_aplicar,
|
|
136
|
+
prioridad=prioridad_adelantos,
|
|
137
|
+
es_obligatoria=False,
|
|
138
|
+
tipo="adelanto",
|
|
139
|
+
)
|
|
140
|
+
deductions.append(item)
|
|
141
|
+
saldo_disponible -= monto_aplicar
|
|
142
|
+
|
|
143
|
+
# Record the payment
|
|
144
|
+
self._record_payment(adelanto, monto_aplicar)
|
|
145
|
+
|
|
146
|
+
return deductions
|
|
147
|
+
|
|
148
|
+
def _calculate_interest(self, prestamo: Adelanto) -> None:
|
|
149
|
+
"""Calculate and apply interest for a loan."""
|
|
150
|
+
from coati_payroll.interes_engine import calcular_interes_periodo
|
|
151
|
+
from coati_payroll.model import InteresAdelanto
|
|
152
|
+
|
|
153
|
+
tasa_interes = prestamo.tasa_interes or Decimal("0.0000")
|
|
154
|
+
if tasa_interes <= 0:
|
|
155
|
+
return
|
|
156
|
+
|
|
157
|
+
if prestamo.saldo_pendiente <= 0:
|
|
158
|
+
return
|
|
159
|
+
|
|
160
|
+
fecha_desde = prestamo.fecha_ultimo_calculo_interes
|
|
161
|
+
if not fecha_desde:
|
|
162
|
+
fecha_desde = prestamo.fecha_desembolso or prestamo.fecha_aprobacion
|
|
163
|
+
|
|
164
|
+
if not fecha_desde:
|
|
165
|
+
return
|
|
166
|
+
|
|
167
|
+
fecha_hasta = self.fecha_calculo
|
|
168
|
+
|
|
169
|
+
if fecha_desde >= fecha_hasta:
|
|
170
|
+
return
|
|
171
|
+
|
|
172
|
+
tipo_interes = prestamo.tipo_interes or "simple"
|
|
173
|
+
interes_calculado, dias = calcular_interes_periodo(
|
|
174
|
+
saldo=prestamo.saldo_pendiente,
|
|
175
|
+
tasa_anual=tasa_interes,
|
|
176
|
+
fecha_desde=fecha_desde,
|
|
177
|
+
fecha_hasta=fecha_hasta,
|
|
178
|
+
tipo_interes=tipo_interes,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
if interes_calculado <= 0:
|
|
182
|
+
return
|
|
183
|
+
|
|
184
|
+
# Record interest in journal
|
|
185
|
+
interes_entrada = InteresAdelanto(
|
|
186
|
+
adelanto_id=prestamo.id,
|
|
187
|
+
nomina_id=self.nomina.id if self.nomina else None,
|
|
188
|
+
fecha_desde=fecha_desde,
|
|
189
|
+
fecha_hasta=fecha_hasta,
|
|
190
|
+
dias_transcurridos=dias,
|
|
191
|
+
saldo_base=prestamo.saldo_pendiente,
|
|
192
|
+
tasa_aplicada=tasa_interes,
|
|
193
|
+
interes_calculado=interes_calculado,
|
|
194
|
+
saldo_anterior=prestamo.saldo_pendiente,
|
|
195
|
+
saldo_posterior=prestamo.saldo_pendiente + interes_calculado,
|
|
196
|
+
observaciones=_("Interés calculado por nómina del {inicio} al {fin}").format(
|
|
197
|
+
inicio=self.periodo_inicio, fin=self.periodo_fin
|
|
198
|
+
),
|
|
199
|
+
)
|
|
200
|
+
db.session.add(interes_entrada)
|
|
201
|
+
|
|
202
|
+
# Update loan with interest
|
|
203
|
+
prestamo.saldo_pendiente += interes_calculado
|
|
204
|
+
prestamo.interes_acumulado = (prestamo.interes_acumulado or Decimal("0.00")) + interes_calculado
|
|
205
|
+
prestamo.fecha_ultimo_calculo_interes = fecha_hasta
|
|
206
|
+
|
|
207
|
+
def _record_payment(self, adelanto: Adelanto, monto: Decimal) -> None:
|
|
208
|
+
"""Record a payment towards a loan/advance."""
|
|
209
|
+
saldo_anterior = Decimal(str(adelanto.saldo_pendiente))
|
|
210
|
+
saldo_posterior = saldo_anterior - monto
|
|
211
|
+
|
|
212
|
+
abono = AdelantoAbono(
|
|
213
|
+
adelanto_id=adelanto.id,
|
|
214
|
+
nomina_id=self.nomina.id if self.nomina else None,
|
|
215
|
+
liquidacion_id=self.liquidacion.id if self.liquidacion else None,
|
|
216
|
+
fecha_abono=self.fecha_calculo,
|
|
217
|
+
monto_abonado=monto,
|
|
218
|
+
saldo_anterior=saldo_anterior,
|
|
219
|
+
saldo_posterior=max(saldo_posterior, Decimal("0.00")),
|
|
220
|
+
tipo_abono="liquidacion" if self.liquidacion else "nomina",
|
|
221
|
+
)
|
|
222
|
+
db.session.add(abono)
|
|
223
|
+
|
|
224
|
+
# Update adelanto balance
|
|
225
|
+
adelanto.saldo_pendiente = max(saldo_posterior, Decimal("0.00"))
|
|
226
|
+
if adelanto.saldo_pendiente <= 0:
|
|
227
|
+
adelanto.estado = AdelantoEstado.PAGADO
|
|
@@ -0,0 +1,42 @@
|
|
|
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
|
+
"""Novelty processor for loading employee novelties."""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from datetime import date
|
|
19
|
+
from decimal import Decimal
|
|
20
|
+
|
|
21
|
+
from coati_payroll.model import Empleado
|
|
22
|
+
from ..repositories.novelty_repository import NoveltyRepository
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class NoveltyProcessor:
|
|
26
|
+
"""Processor for loading employee novelties."""
|
|
27
|
+
|
|
28
|
+
def __init__(self, novelty_repository: NoveltyRepository):
|
|
29
|
+
self.novelty_repo = novelty_repository
|
|
30
|
+
|
|
31
|
+
def load_novelties(self, empleado: Empleado, periodo_inicio: date, periodo_fin: date) -> dict[str, Decimal]:
|
|
32
|
+
"""Load novelties for the employee in this period."""
|
|
33
|
+
novedades: dict[str, Decimal] = {}
|
|
34
|
+
|
|
35
|
+
nomina_novedades = self.novelty_repo.get_by_employee_and_period(empleado.id, periodo_inicio, periodo_fin)
|
|
36
|
+
|
|
37
|
+
for novedad in nomina_novedades:
|
|
38
|
+
codigo = novedad.codigo_concepto
|
|
39
|
+
valor = Decimal(str(novedad.valor_cantidad or 0))
|
|
40
|
+
novedades[codigo] = novedades.get(codigo, Decimal("0")) + valor
|
|
41
|
+
|
|
42
|
+
return novedades
|
|
@@ -0,0 +1,67 @@
|
|
|
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
|
+
"""Vacation processor for vacation accrual and usage."""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from coati_payroll.model import Planilla, Empleado, NominaEmpleado
|
|
19
|
+
from coati_payroll.log import log
|
|
20
|
+
from ..domain.employee_calculation import EmpleadoCalculo
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class VacationProcessor:
|
|
24
|
+
"""Processor for vacation accrual and usage."""
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self, planilla: Planilla, periodo_inicio, periodo_fin, usuario: str | None = None, warnings: list[str] = None
|
|
28
|
+
):
|
|
29
|
+
self.planilla = planilla
|
|
30
|
+
self.periodo_inicio = periodo_inicio
|
|
31
|
+
self.periodo_fin = periodo_fin
|
|
32
|
+
self.usuario = usuario
|
|
33
|
+
self.warnings = warnings or []
|
|
34
|
+
|
|
35
|
+
def process_vacations(
|
|
36
|
+
self, empleado: Empleado, emp_calculo: EmpleadoCalculo, nomina_empleado: NominaEmpleado
|
|
37
|
+
) -> None:
|
|
38
|
+
"""Process vacation accrual and usage for an employee."""
|
|
39
|
+
try:
|
|
40
|
+
from coati_payroll.vacation_service import VacationService
|
|
41
|
+
|
|
42
|
+
vacation_service = VacationService(
|
|
43
|
+
planilla=self.planilla,
|
|
44
|
+
periodo_inicio=self.periodo_inicio,
|
|
45
|
+
periodo_fin=self.periodo_fin,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# Accumulate vacation time
|
|
49
|
+
vacation_service.acumular_vacaciones_empleado(
|
|
50
|
+
empleado=empleado,
|
|
51
|
+
nomina_empleado=nomina_empleado,
|
|
52
|
+
usuario=self.usuario,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# Process vacation novelties (time off taken)
|
|
56
|
+
vacation_service.procesar_novedades_vacaciones(
|
|
57
|
+
empleado=empleado,
|
|
58
|
+
novedades=emp_calculo.novedades,
|
|
59
|
+
usuario=self.usuario,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
except Exception as e:
|
|
63
|
+
log.error(f"Error procesando vacaciones para empleado {empleado.codigo_empleado}: {str(e)}")
|
|
64
|
+
self.warnings.append(
|
|
65
|
+
f"No se pudieron procesar vacaciones para {empleado.primer_nombre} "
|
|
66
|
+
f"{empleado.primer_apellido}: {str(e)}"
|
|
67
|
+
)
|
|
@@ -0,0 +1,32 @@
|
|
|
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
|
+
"""Repository layer for data access."""
|
|
15
|
+
|
|
16
|
+
from .base_repository import BaseRepository
|
|
17
|
+
from .planilla_repository import PlanillaRepository
|
|
18
|
+
from .employee_repository import EmployeeRepository
|
|
19
|
+
from .acumulado_repository import AcumuladoRepository
|
|
20
|
+
from .novelty_repository import NoveltyRepository
|
|
21
|
+
from .exchange_rate_repository import ExchangeRateRepository
|
|
22
|
+
from .config_repository import ConfigRepository
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"BaseRepository",
|
|
26
|
+
"PlanillaRepository",
|
|
27
|
+
"EmployeeRepository",
|
|
28
|
+
"AcumuladoRepository",
|
|
29
|
+
"NoveltyRepository",
|
|
30
|
+
"ExchangeRateRepository",
|
|
31
|
+
"ConfigRepository",
|
|
32
|
+
]
|
|
@@ -0,0 +1,83 @@
|
|
|
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
|
+
"""Repository for AcumuladoAnual operations."""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from datetime import date
|
|
19
|
+
from typing import Optional
|
|
20
|
+
|
|
21
|
+
from coati_payroll.model import AcumuladoAnual, Empleado
|
|
22
|
+
from .base_repository import BaseRepository
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class AcumuladoRepository(BaseRepository[AcumuladoAnual]):
|
|
26
|
+
"""Repository for AcumuladoAnual operations."""
|
|
27
|
+
|
|
28
|
+
def get_by_id(self, acumulado_id: str) -> Optional[AcumuladoAnual]:
|
|
29
|
+
"""Get acumulado by ID."""
|
|
30
|
+
return self.session.get(AcumuladoAnual, acumulado_id)
|
|
31
|
+
|
|
32
|
+
def get_or_create(
|
|
33
|
+
self,
|
|
34
|
+
empleado: Empleado,
|
|
35
|
+
tipo_planilla_id: str,
|
|
36
|
+
empresa_id: str,
|
|
37
|
+
periodo_fiscal_inicio: date,
|
|
38
|
+
) -> AcumuladoAnual:
|
|
39
|
+
"""Get or create acumulado for employee and fiscal period."""
|
|
40
|
+
from sqlalchemy import select
|
|
41
|
+
|
|
42
|
+
acumulado = (
|
|
43
|
+
self.session.execute(
|
|
44
|
+
select(AcumuladoAnual).filter(
|
|
45
|
+
AcumuladoAnual.empleado_id == empleado.id,
|
|
46
|
+
AcumuladoAnual.tipo_planilla_id == tipo_planilla_id,
|
|
47
|
+
AcumuladoAnual.empresa_id == empresa_id,
|
|
48
|
+
AcumuladoAnual.periodo_fiscal_inicio == periodo_fiscal_inicio,
|
|
49
|
+
)
|
|
50
|
+
)
|
|
51
|
+
.unique()
|
|
52
|
+
.scalar_one_or_none()
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
if not acumulado:
|
|
56
|
+
from datetime import date as date_type
|
|
57
|
+
from decimal import Decimal
|
|
58
|
+
|
|
59
|
+
periodo_fiscal_fin = date_type(
|
|
60
|
+
periodo_fiscal_inicio.year + 1, periodo_fiscal_inicio.month, periodo_fiscal_inicio.day
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
acumulado = AcumuladoAnual(
|
|
64
|
+
empleado_id=empleado.id,
|
|
65
|
+
tipo_planilla_id=tipo_planilla_id,
|
|
66
|
+
empresa_id=empresa_id,
|
|
67
|
+
periodo_fiscal_inicio=periodo_fiscal_inicio,
|
|
68
|
+
periodo_fiscal_fin=periodo_fiscal_fin,
|
|
69
|
+
salario_bruto_acumulado=empleado.salario_acumulado or Decimal("0.00"),
|
|
70
|
+
salario_gravable_acumulado=Decimal("0.00"),
|
|
71
|
+
deducciones_antes_impuesto_acumulado=empleado.impuesto_acumulado or Decimal("0.00"),
|
|
72
|
+
impuesto_retenido_acumulado=Decimal("0.00"),
|
|
73
|
+
periodos_procesados=0,
|
|
74
|
+
salario_acumulado_mes=Decimal("0.00"),
|
|
75
|
+
)
|
|
76
|
+
self.session.add(acumulado)
|
|
77
|
+
|
|
78
|
+
return acumulado
|
|
79
|
+
|
|
80
|
+
def save(self, acumulado: AcumuladoAnual) -> AcumuladoAnual:
|
|
81
|
+
"""Save acumulado."""
|
|
82
|
+
self.session.add(acumulado)
|
|
83
|
+
return acumulado
|
|
@@ -0,0 +1,40 @@
|
|
|
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
|
+
"""Base repository for data access."""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from abc import ABC, abstractmethod
|
|
19
|
+
from typing import Generic, TypeVar, Optional
|
|
20
|
+
|
|
21
|
+
from sqlalchemy.orm import Session
|
|
22
|
+
|
|
23
|
+
T = TypeVar("T")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class BaseRepository(ABC, Generic[T]):
|
|
27
|
+
"""Base repository for data access operations."""
|
|
28
|
+
|
|
29
|
+
def __init__(self, session: Session):
|
|
30
|
+
self.session = session
|
|
31
|
+
|
|
32
|
+
@abstractmethod
|
|
33
|
+
def get_by_id(self, id: str) -> Optional[T]:
|
|
34
|
+
"""Get entity by ID."""
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
@abstractmethod
|
|
38
|
+
def save(self, entity: T) -> T:
|
|
39
|
+
"""Save entity."""
|
|
40
|
+
pass
|
|
@@ -0,0 +1,102 @@
|
|
|
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
|
+
"""Repository for ConfiguracionCalculos operations."""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from decimal import Decimal
|
|
19
|
+
from typing import Optional
|
|
20
|
+
|
|
21
|
+
from coati_payroll.model import ConfiguracionCalculos
|
|
22
|
+
from .base_repository import BaseRepository
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ConfigRepository(BaseRepository[ConfiguracionCalculos]):
|
|
26
|
+
"""Repository for ConfiguracionCalculos operations."""
|
|
27
|
+
|
|
28
|
+
def get_by_id(self, config_id: str) -> Optional[ConfiguracionCalculos]:
|
|
29
|
+
"""Get configuration by ID."""
|
|
30
|
+
return self.session.get(ConfiguracionCalculos, config_id)
|
|
31
|
+
|
|
32
|
+
def get_for_empresa(self, empresa_id: Optional[str]) -> ConfiguracionCalculos:
|
|
33
|
+
"""Get configuration for empresa, or global default."""
|
|
34
|
+
from sqlalchemy import select
|
|
35
|
+
|
|
36
|
+
# Try company-specific configuration
|
|
37
|
+
if empresa_id:
|
|
38
|
+
config = (
|
|
39
|
+
self.session.execute(
|
|
40
|
+
select(ConfiguracionCalculos).filter(
|
|
41
|
+
ConfiguracionCalculos.empresa_id == empresa_id,
|
|
42
|
+
ConfiguracionCalculos.activo.is_(True),
|
|
43
|
+
)
|
|
44
|
+
)
|
|
45
|
+
.unique()
|
|
46
|
+
.scalar_one_or_none()
|
|
47
|
+
)
|
|
48
|
+
if config:
|
|
49
|
+
return config
|
|
50
|
+
|
|
51
|
+
# Try global default
|
|
52
|
+
config = (
|
|
53
|
+
self.session.execute(
|
|
54
|
+
select(ConfiguracionCalculos).filter(
|
|
55
|
+
ConfiguracionCalculos.empresa_id.is_(None),
|
|
56
|
+
ConfiguracionCalculos.pais_id.is_(None),
|
|
57
|
+
ConfiguracionCalculos.activo.is_(True),
|
|
58
|
+
)
|
|
59
|
+
)
|
|
60
|
+
.unique()
|
|
61
|
+
.scalar_one_or_none()
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
if config:
|
|
65
|
+
return config
|
|
66
|
+
|
|
67
|
+
# Return default instance (not saved to DB)
|
|
68
|
+
# =====================================================================
|
|
69
|
+
# DEFAULT VALUES DISCLAIMER (Per Social Contract)
|
|
70
|
+
# =====================================================================
|
|
71
|
+
# These default values are provided SOLELY to facilitate initial adoption.
|
|
72
|
+
# They do NOT represent legal rules for any specific jurisdiction.
|
|
73
|
+
# They are completely configurable by the implementer.
|
|
74
|
+
# They should NOT be assumed as correct for any specific jurisdiction.
|
|
75
|
+
#
|
|
76
|
+
# Implementers MUST review and configure these values according to
|
|
77
|
+
# their specific legal and business requirements before production use.
|
|
78
|
+
# =====================================================================
|
|
79
|
+
return ConfiguracionCalculos(
|
|
80
|
+
empresa_id=None,
|
|
81
|
+
pais_id=None,
|
|
82
|
+
dias_mes_nomina=30, # Example default - configure per jurisdiction
|
|
83
|
+
dias_anio_nomina=365, # Example default - configure per jurisdiction
|
|
84
|
+
horas_jornada_diaria=Decimal("8.00"), # Example default - configure per jurisdiction
|
|
85
|
+
dias_mes_vacaciones=30, # Example default - configure per jurisdiction
|
|
86
|
+
dias_anio_vacaciones=365, # Example default - configure per jurisdiction
|
|
87
|
+
considerar_bisiesto_vacaciones=True, # Example default - configure per jurisdiction
|
|
88
|
+
dias_anio_financiero=365, # Example default - configure per jurisdiction
|
|
89
|
+
meses_anio_financiero=12, # Example default - configure per jurisdiction
|
|
90
|
+
dias_quincena=15, # Example default - configure per jurisdiction
|
|
91
|
+
liquidacion_modo_dias="calendario",
|
|
92
|
+
liquidacion_factor_calendario=30,
|
|
93
|
+
liquidacion_factor_laboral=28,
|
|
94
|
+
dias_mes_antiguedad=30, # Example default - configure per jurisdiction
|
|
95
|
+
dias_anio_antiguedad=365, # Example default - configure per jurisdiction
|
|
96
|
+
activo=True,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
def save(self, config: ConfiguracionCalculos) -> ConfiguracionCalculos:
|
|
100
|
+
"""Save configuration."""
|
|
101
|
+
self.session.add(config)
|
|
102
|
+
return config
|