coati-payroll 0.0.12__py3-none-any.whl → 0.0.13__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 +7 -17
- coati_payroll/app.py +2 -13
- coati_payroll/audit_helpers.py +2 -13
- coati_payroll/auth.py +2 -13
- coati_payroll/cli.py +17 -14
- coati_payroll/config.py +2 -13
- coati_payroll/demo_data.py +2 -13
- coati_payroll/enums.py +2 -13
- coati_payroll/forms.py +7 -13
- coati_payroll/formula_engine/__init__.py +14 -13
- coati_payroll/formula_engine/ast/__init__.py +16 -1
- coati_payroll/formula_engine/ast/ast_visitor.py +15 -17
- coati_payroll/formula_engine/ast/expression_evaluator.py +12 -14
- coati_payroll/formula_engine/ast/safe_operators.py +16 -15
- coati_payroll/formula_engine/ast/type_converter.py +17 -17
- coati_payroll/formula_engine/data_sources.py +3 -13
- coati_payroll/formula_engine/engine.py +12 -13
- coati_payroll/formula_engine/exceptions.py +13 -13
- coati_payroll/formula_engine/execution/__init__.py +15 -13
- coati_payroll/formula_engine/execution/execution_context.py +12 -13
- coati_payroll/formula_engine/execution/step_executor.py +12 -14
- coati_payroll/formula_engine/execution/variable_store.py +13 -13
- coati_payroll/formula_engine/novelty_codes.py +3 -13
- coati_payroll/formula_engine/results/__init__.py +14 -13
- coati_payroll/formula_engine/results/execution_result.py +47 -35
- coati_payroll/formula_engine/steps/__init__.py +14 -14
- coati_payroll/formula_engine/steps/assignment_step.py +12 -13
- coati_payroll/formula_engine/steps/base_step.py +13 -13
- coati_payroll/formula_engine/steps/calculation_step.py +12 -13
- coati_payroll/formula_engine/steps/conditional_step.py +12 -13
- coati_payroll/formula_engine/steps/step_factory.py +11 -13
- coati_payroll/formula_engine/steps/tax_lookup_step.py +12 -13
- coati_payroll/formula_engine/tables/__init__.py +14 -13
- coati_payroll/formula_engine/tables/bracket_calculator.py +12 -13
- coati_payroll/formula_engine/tables/table_lookup.py +12 -14
- coati_payroll/formula_engine/tables/tax_table.py +13 -13
- coati_payroll/formula_engine/validation/__init__.py +14 -13
- coati_payroll/formula_engine/validation/schema_validator.py +12 -14
- coati_payroll/formula_engine/validation/security_validator.py +12 -13
- coati_payroll/formula_engine/validation/tax_table_validator.py +12 -13
- coati_payroll/formula_engine_examples.py +2 -13
- coati_payroll/i18n.py +2 -13
- coati_payroll/initial_data.py +2 -13
- coati_payroll/interes_engine.py +2 -13
- coati_payroll/liquidacion_engine/__init__.py +2 -13
- coati_payroll/liquidacion_engine/engine.py +2 -13
- coati_payroll/locale_config.py +2 -13
- coati_payroll/migrations/20260125_032900_initial_migration.py +2 -14
- coati_payroll/migrations/__init__.py +2 -13
- coati_payroll/model.py +83 -82
- coati_payroll/nomina_engine/__init__.py +2 -13
- coati_payroll/nomina_engine/calculators/__init__.py +2 -13
- coati_payroll/nomina_engine/calculators/benefit_calculator.py +2 -13
- coati_payroll/nomina_engine/calculators/concept_calculator.py +2 -1
- coati_payroll/nomina_engine/calculators/deduction_calculator.py +2 -13
- coati_payroll/nomina_engine/calculators/exchange_rate_calculator.py +2 -13
- coati_payroll/nomina_engine/calculators/perception_calculator.py +2 -13
- coati_payroll/nomina_engine/calculators/salary_calculator.py +2 -13
- coati_payroll/nomina_engine/domain/__init__.py +2 -13
- coati_payroll/nomina_engine/domain/calculation_items.py +2 -13
- coati_payroll/nomina_engine/domain/employee_calculation.py +2 -13
- coati_payroll/nomina_engine/domain/payroll_context.py +2 -13
- coati_payroll/nomina_engine/engine.py +2 -13
- coati_payroll/nomina_engine/processors/__init__.py +2 -13
- coati_payroll/nomina_engine/processors/accounting_processor.py +2 -13
- coati_payroll/nomina_engine/processors/accumulation_processor.py +2 -13
- coati_payroll/nomina_engine/processors/loan_processor.py +2 -13
- coati_payroll/nomina_engine/processors/novelty_processor.py +2 -13
- coati_payroll/nomina_engine/processors/vacation_processor.py +2 -13
- coati_payroll/nomina_engine/repositories/__init__.py +2 -13
- coati_payroll/nomina_engine/repositories/acumulado_repository.py +2 -13
- coati_payroll/nomina_engine/repositories/base_repository.py +2 -13
- coati_payroll/nomina_engine/repositories/config_repository.py +2 -13
- coati_payroll/nomina_engine/repositories/employee_repository.py +2 -13
- coati_payroll/nomina_engine/repositories/exchange_rate_repository.py +2 -13
- coati_payroll/nomina_engine/repositories/novelty_repository.py +2 -13
- coati_payroll/nomina_engine/repositories/planilla_repository.py +2 -13
- coati_payroll/nomina_engine/results/__init__.py +2 -13
- coati_payroll/nomina_engine/results/error_result.py +2 -13
- coati_payroll/nomina_engine/results/payroll_result.py +2 -13
- coati_payroll/nomina_engine/results/validation_result.py +2 -13
- coati_payroll/nomina_engine/services/__init__.py +2 -13
- coati_payroll/nomina_engine/services/accounting_voucher_service.py +2 -13
- coati_payroll/nomina_engine/services/employee_processing_service.py +2 -13
- coati_payroll/nomina_engine/services/payroll_execution_service.py +2 -13
- coati_payroll/nomina_engine/validators/__init__.py +2 -13
- coati_payroll/nomina_engine/validators/base_validator.py +2 -13
- coati_payroll/nomina_engine/validators/currency_validator.py +2 -13
- coati_payroll/nomina_engine/validators/employee_validator.py +2 -13
- coati_payroll/nomina_engine/validators/period_validator.py +2 -13
- coati_payroll/nomina_engine/validators/planilla_validator.py +2 -13
- coati_payroll/queue/__init__.py +2 -13
- coati_payroll/queue/driver.py +2 -13
- coati_payroll/queue/drivers/__init__.py +2 -13
- coati_payroll/queue/drivers/dramatiq_driver.py +9 -16
- coati_payroll/queue/drivers/huey_driver.py +2 -13
- coati_payroll/queue/drivers/noop_driver.py +2 -13
- coati_payroll/queue/selector.py +2 -13
- coati_payroll/queue/tasks.py +13 -20
- coati_payroll/rate_limiting.py +2 -13
- coati_payroll/rbac.py +12 -19
- coati_payroll/report_engine.py +2 -13
- coati_payroll/report_export.py +2 -13
- coati_payroll/schema_validator.py +2 -13
- coati_payroll/security.py +2 -13
- coati_payroll/static/schema_editor.js +862 -0
- coati_payroll/static/styles.css +5 -0
- coati_payroll/system_reports.py +2 -13
- coati_payroll/templates/auth/login.html +5 -0
- coati_payroll/templates/base.html +5 -1
- coati_payroll/templates/index.html +5 -0
- coati_payroll/templates/macros.html +5 -0
- coati_payroll/templates/modules/calculation_rule/form.html +5 -0
- coati_payroll/templates/modules/calculation_rule/index.html +5 -0
- coati_payroll/templates/modules/calculation_rule/schema_editor.html +292 -974
- coati_payroll/templates/modules/carga_inicial_prestacion/form.html +5 -0
- coati_payroll/templates/modules/carga_inicial_prestacion/index.html +5 -0
- coati_payroll/templates/modules/carga_inicial_prestacion/reporte.html +5 -0
- coati_payroll/templates/modules/config_calculos/index.html +5 -0
- coati_payroll/templates/modules/configuracion/index.html +24 -27
- coati_payroll/templates/modules/currency/form.html +5 -0
- coati_payroll/templates/modules/currency/index.html +5 -0
- coati_payroll/templates/modules/custom_field/form.html +5 -0
- coati_payroll/templates/modules/custom_field/index.html +5 -0
- coati_payroll/templates/modules/deduccion/form.html +5 -0
- coati_payroll/templates/modules/deduccion/index.html +5 -0
- coati_payroll/templates/modules/employee/form.html +5 -0
- coati_payroll/templates/modules/employee/index.html +5 -0
- coati_payroll/templates/modules/empresa/form.html +5 -0
- coati_payroll/templates/modules/empresa/index.html +5 -0
- coati_payroll/templates/modules/exchange_rate/form.html +5 -0
- coati_payroll/templates/modules/exchange_rate/import.html +5 -0
- coati_payroll/templates/modules/exchange_rate/index.html +5 -0
- coati_payroll/templates/modules/liquidacion/index.html +5 -0
- coati_payroll/templates/modules/liquidacion/nueva.html +5 -0
- coati_payroll/templates/modules/liquidacion/ver.html +5 -0
- coati_payroll/templates/modules/payroll_concepts/audit_log.html +5 -0
- coati_payroll/templates/modules/percepcion/form.html +5 -0
- coati_payroll/templates/modules/percepcion/index.html +5 -0
- coati_payroll/templates/modules/planilla/config.html +5 -0
- coati_payroll/templates/modules/planilla/config_deducciones.html +5 -0
- coati_payroll/templates/modules/planilla/config_empleados.html +5 -0
- coati_payroll/templates/modules/planilla/config_percepciones.html +5 -0
- coati_payroll/templates/modules/planilla/config_prestaciones.html +5 -0
- coati_payroll/templates/modules/planilla/config_reglas.html +5 -0
- coati_payroll/templates/modules/planilla/ejecutar_nomina.html +5 -0
- coati_payroll/templates/modules/planilla/form.html +5 -0
- coati_payroll/templates/modules/planilla/index.html +5 -0
- coati_payroll/templates/modules/planilla/listar_nominas.html +5 -0
- coati_payroll/templates/modules/planilla/log_nomina.html +5 -0
- coati_payroll/templates/modules/planilla/novedades/form.html +5 -0
- coati_payroll/templates/modules/planilla/novedades/index.html +5 -0
- coati_payroll/templates/modules/planilla/ver_nomina.html +5 -0
- coati_payroll/templates/modules/planilla/ver_nomina_empleado.html +5 -0
- coati_payroll/templates/modules/plugins/index.html +5 -0
- coati_payroll/templates/modules/prestacion/form.html +5 -0
- coati_payroll/templates/modules/prestacion/index.html +5 -0
- coati_payroll/templates/modules/prestacion_management/dashboard.html +5 -0
- coati_payroll/templates/modules/prestacion_management/initial_balance_bulk.html +5 -0
- coati_payroll/templates/modules/prestamo/approve.html +5 -0
- coati_payroll/templates/modules/prestamo/condonacion.html +5 -0
- coati_payroll/templates/modules/prestamo/detail.html +5 -0
- coati_payroll/templates/modules/prestamo/form.html +5 -0
- coati_payroll/templates/modules/prestamo/index.html +5 -0
- coati_payroll/templates/modules/prestamo/pago_extraordinario.html +5 -0
- coati_payroll/templates/modules/prestamo/tabla_pago_pdf.html +5 -0
- coati_payroll/templates/modules/report/admin_index.html +5 -0
- coati_payroll/templates/modules/report/detail.html +5 -0
- coati_payroll/templates/modules/report/execute.html +5 -0
- coati_payroll/templates/modules/report/index.html +5 -0
- coati_payroll/templates/modules/report/permissions.html +5 -0
- coati_payroll/templates/modules/settings/index.html +5 -0
- coati_payroll/templates/modules/shared/concept_form.html +19 -12
- coati_payroll/templates/modules/shared/concept_index.html +39 -27
- coati_payroll/templates/modules/tipo_planilla/form.html +5 -0
- coati_payroll/templates/modules/tipo_planilla/index.html +5 -0
- coati_payroll/templates/modules/user/form.html +5 -0
- coati_payroll/templates/modules/user/index.html +5 -0
- coati_payroll/templates/modules/user/profile.html +5 -0
- coati_payroll/templates/modules/vacation/account_detail.html +5 -0
- coati_payroll/templates/modules/vacation/account_form.html +5 -0
- coati_payroll/templates/modules/vacation/account_index.html +5 -0
- coati_payroll/templates/modules/vacation/dashboard.html +5 -0
- coati_payroll/templates/modules/vacation/initial_balance_bulk.html +5 -0
- coati_payroll/templates/modules/vacation/initial_balance_form.html +5 -0
- coati_payroll/templates/modules/vacation/leave_request_detail.html +5 -0
- coati_payroll/templates/modules/vacation/leave_request_form.html +5 -0
- coati_payroll/templates/modules/vacation/leave_request_index.html +5 -0
- coati_payroll/templates/modules/vacation/policy_detail.html +5 -0
- coati_payroll/templates/modules/vacation/policy_form.html +5 -0
- coati_payroll/templates/modules/vacation/policy_index.html +5 -0
- coati_payroll/templates/modules/vacation/register_taken_form.html +5 -0
- coati_payroll/translations/en/LC_MESSAGES/messages.mo +0 -0
- coati_payroll/translations/en/LC_MESSAGES/messages.po +2963 -3561
- coati_payroll/vacation_service.py +2 -13
- coati_payroll/version.py +3 -14
- coati_payroll/vistas/__init__.py +2 -13
- coati_payroll/vistas/calculation_rule.py +14 -24
- coati_payroll/vistas/carga_inicial_prestacion.py +2 -13
- coati_payroll/vistas/config_calculos.py +2 -13
- coati_payroll/vistas/configuracion.py +29 -39
- coati_payroll/vistas/constants.py +2 -13
- coati_payroll/vistas/currency.py +2 -13
- coati_payroll/vistas/custom_field.py +2 -13
- coati_payroll/vistas/employee.py +2 -13
- coati_payroll/vistas/empresa.py +2 -13
- coati_payroll/vistas/exchange_rate.py +2 -13
- coati_payroll/vistas/liquidacion.py +2 -13
- coati_payroll/vistas/payroll_concepts.py +2 -13
- coati_payroll/vistas/planilla/__init__.py +2 -13
- coati_payroll/vistas/planilla/association_routes.py +8 -17
- coati_payroll/vistas/planilla/config_routes.py +2 -13
- coati_payroll/vistas/planilla/export_routes.py +28 -35
- coati_payroll/vistas/planilla/helpers/__init__.py +2 -13
- coati_payroll/vistas/planilla/helpers/association_helpers.py +2 -13
- coati_payroll/vistas/planilla/helpers/excel_helpers.py +2 -13
- coati_payroll/vistas/planilla/helpers/form_helpers.py +2 -13
- coati_payroll/vistas/planilla/nomina_routes.py +2 -13
- coati_payroll/vistas/planilla/novedad_routes.py +2 -13
- coati_payroll/vistas/planilla/routes.py +2 -13
- coati_payroll/vistas/planilla/services/__init__.py +2 -13
- coati_payroll/vistas/planilla/services/export_service.py +4 -15
- coati_payroll/vistas/planilla/services/nomina_service.py +2 -13
- coati_payroll/vistas/planilla/services/novedad_service.py +3 -16
- coati_payroll/vistas/planilla/services/planilla_service.py +2 -13
- coati_payroll/vistas/planilla/validators/__init__.py +2 -13
- coati_payroll/vistas/planilla/validators/planilla_validators.py +2 -13
- coati_payroll/vistas/prestacion.py +2 -13
- coati_payroll/vistas/prestamo.py +2 -13
- coati_payroll/vistas/report.py +2 -13
- coati_payroll/vistas/settings.py +2 -13
- coati_payroll/vistas/tipo_planilla.py +2 -13
- coati_payroll/vistas/user.py +2 -13
- coati_payroll/vistas/vacation.py +15 -13
- coati_payroll/wsgi_server.py +37 -0
- {coati_payroll-0.0.12.dist-info → coati_payroll-0.0.13.dist-info}/METADATA +11 -4
- coati_payroll-0.0.13.dist-info/RECORD +248 -0
- coati_payroll/translations/es/LC_MESSAGES/messages.mo +0 -0
- coati_payroll/translations/es/LC_MESSAGES/messages.po +0 -7374
- coati_payroll-0.0.12.dist-info/RECORD +0 -248
- {coati_payroll-0.0.12.dist-info → coati_payroll-0.0.13.dist-info}/LICENSE +0 -0
- {coati_payroll-0.0.12.dist-info → coati_payroll-0.0.13.dist-info}/WHEEL +0 -0
- {coati_payroll-0.0.12.dist-info → coati_payroll-0.0.13.dist-info}/entry_points.txt +0 -0
- {coati_payroll-0.0.12.dist-info → coati_payroll-0.0.13.dist-info}/top_level.txt +0 -0
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
{
|
|
2
|
-
|
|
1
|
+
{#-
|
|
2
|
+
SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
SPDX-FileCopyrightText: 2025 - 2026 BMO Soluciones, S.A.
|
|
4
|
+
-#}
|
|
3
5
|
|
|
4
|
-
{%
|
|
6
|
+
{% extends 'base.html' %} {% from 'macros.html' import render_messages %} {%
|
|
7
|
+
block content %}
|
|
5
8
|
<div class="container-fluid">
|
|
6
9
|
{{ render_messages() }}
|
|
7
|
-
|
|
10
|
+
|
|
8
11
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
9
12
|
<div>
|
|
10
13
|
<h2>
|
|
@@ -12,14 +15,21 @@
|
|
|
12
15
|
{{ _('Editor de Esquema') }}: {{ rule.nombre }}
|
|
13
16
|
</h2>
|
|
14
17
|
<small class="text-muted">
|
|
15
|
-
{{ rule.codigo }} v{{ rule.version }} | {{ rule.jurisdiccion or
|
|
18
|
+
{{ rule.codigo }} v{{ rule.version }} | {{ rule.jurisdiccion or
|
|
19
|
+
_('Sin jurisdicción') }}
|
|
16
20
|
</small>
|
|
17
21
|
</div>
|
|
18
22
|
<div>
|
|
19
|
-
<a
|
|
23
|
+
<a
|
|
24
|
+
href="{{ url_for('calculation_rule.edit', id=rule.id) }}"
|
|
25
|
+
class="btn btn-secondary"
|
|
26
|
+
>
|
|
20
27
|
<i class="bi bi-pencil me-1"></i>{{ _('Editar Metadatos') }}
|
|
21
28
|
</a>
|
|
22
|
-
<a
|
|
29
|
+
<a
|
|
30
|
+
href="{{ url_for('calculation_rule.index') }}"
|
|
31
|
+
class="btn btn-outline-secondary"
|
|
32
|
+
>
|
|
23
33
|
<i class="bi bi-arrow-left me-1"></i>{{ _('Volver') }}
|
|
24
34
|
</a>
|
|
25
35
|
</div>
|
|
@@ -36,42 +46,71 @@
|
|
|
36
46
|
<!-- Meta Section -->
|
|
37
47
|
<div class="schema-section mb-4">
|
|
38
48
|
<h5 class="section-title">
|
|
39
|
-
<i class="bi bi-info-circle me-2"></i>{{
|
|
49
|
+
<i class="bi bi-info-circle me-2"></i>{{
|
|
50
|
+
_('Configuración') }}
|
|
40
51
|
</h5>
|
|
41
52
|
<div class="row">
|
|
42
53
|
<div class="col-md-6">
|
|
43
54
|
<div class="mb-3">
|
|
44
|
-
<label class="form-label"
|
|
45
|
-
|
|
46
|
-
|
|
55
|
+
<label class="form-label"
|
|
56
|
+
>{{ _('Nombre de la regla') }}</label
|
|
57
|
+
>
|
|
58
|
+
<input
|
|
59
|
+
type="text"
|
|
60
|
+
class="form-control"
|
|
61
|
+
id="meta-name"
|
|
62
|
+
placeholder="{{ _('Ej: Income Tax Rule') }}"
|
|
63
|
+
/>
|
|
47
64
|
</div>
|
|
48
65
|
</div>
|
|
49
66
|
<div class="col-md-6">
|
|
50
67
|
<div class="mb-3">
|
|
51
|
-
<label class="form-label"
|
|
52
|
-
|
|
53
|
-
|
|
68
|
+
<label class="form-label"
|
|
69
|
+
>{{ _('Moneda de Referencia') }}</label
|
|
70
|
+
>
|
|
71
|
+
<input
|
|
72
|
+
type="text"
|
|
73
|
+
class="form-control"
|
|
74
|
+
id="meta-reference-currency"
|
|
75
|
+
placeholder="{{ _('Ej: NIO, USD') }}"
|
|
76
|
+
/>
|
|
54
77
|
<div class="form-text">
|
|
55
|
-
{{ _('Moneda base para los cálculos. La
|
|
78
|
+
{{ _('Moneda base para los cálculos. La
|
|
79
|
+
moneda de la planilla se define en el
|
|
80
|
+
Tipo de Planilla.') }}
|
|
56
81
|
</div>
|
|
57
82
|
</div>
|
|
58
83
|
</div>
|
|
59
84
|
</div>
|
|
60
85
|
<div class="mb-3">
|
|
61
|
-
<label class="form-label"
|
|
62
|
-
|
|
63
|
-
|
|
86
|
+
<label class="form-label"
|
|
87
|
+
>{{ _('Descripción') }}</label
|
|
88
|
+
>
|
|
89
|
+
<textarea
|
|
90
|
+
class="form-control"
|
|
91
|
+
id="meta-description"
|
|
92
|
+
rows="2"
|
|
93
|
+
placeholder="{{ _('Descripción de lo que calcula esta regla') }}"
|
|
94
|
+
></textarea>
|
|
64
95
|
</div>
|
|
65
96
|
</div>
|
|
66
97
|
|
|
67
98
|
<!-- Inputs Section -->
|
|
68
99
|
<div class="schema-section mb-4">
|
|
69
|
-
<div
|
|
100
|
+
<div
|
|
101
|
+
class="d-flex justify-content-between align-items-center mb-3"
|
|
102
|
+
>
|
|
70
103
|
<h5 class="section-title mb-0">
|
|
71
|
-
<i class="bi bi-box-arrow-in-right me-2"></i>{{
|
|
104
|
+
<i class="bi bi-box-arrow-in-right me-2"></i>{{
|
|
105
|
+
_('Variables de Entrada') }}
|
|
72
106
|
</h5>
|
|
73
|
-
<button
|
|
74
|
-
|
|
107
|
+
<button
|
|
108
|
+
type="button"
|
|
109
|
+
class="btn btn-sm btn-primary"
|
|
110
|
+
onclick="addInput()"
|
|
111
|
+
>
|
|
112
|
+
<i class="bi bi-plus-lg me-1"></i>{{
|
|
113
|
+
_('Agregar') }}
|
|
75
114
|
</button>
|
|
76
115
|
</div>
|
|
77
116
|
<div id="inputs-container">
|
|
@@ -79,33 +118,75 @@
|
|
|
79
118
|
</div>
|
|
80
119
|
<div class="text-muted small mt-2">
|
|
81
120
|
<i class="bi bi-info-circle me-1"></i>
|
|
82
|
-
{{ _('Las variables de entrada pueden venir de la
|
|
121
|
+
{{ _('Las variables de entrada pueden venir de la
|
|
122
|
+
base de datos (empleado.salario_base) o ser valores
|
|
123
|
+
estáticos.') }}
|
|
83
124
|
</div>
|
|
84
125
|
</div>
|
|
85
126
|
|
|
86
127
|
<!-- Steps Section -->
|
|
87
128
|
<div class="schema-section mb-4">
|
|
88
|
-
<div
|
|
129
|
+
<div
|
|
130
|
+
class="d-flex justify-content-between align-items-center mb-3"
|
|
131
|
+
>
|
|
89
132
|
<h5 class="section-title mb-0">
|
|
90
|
-
<i class="bi bi-list-ol me-2"></i>{{ _('Pasos de
|
|
133
|
+
<i class="bi bi-list-ol me-2"></i>{{ _('Pasos de
|
|
134
|
+
Cálculo') }}
|
|
91
135
|
</h5>
|
|
92
136
|
<div class="btn-group btn-group-sm">
|
|
93
|
-
<button
|
|
94
|
-
|
|
137
|
+
<button
|
|
138
|
+
type="button"
|
|
139
|
+
class="btn btn-primary dropdown-toggle"
|
|
140
|
+
data-bs-toggle="dropdown"
|
|
141
|
+
>
|
|
142
|
+
<i class="bi bi-plus-lg me-1"></i>{{
|
|
143
|
+
_('Agregar Paso') }}
|
|
95
144
|
</button>
|
|
96
145
|
<ul class="dropdown-menu">
|
|
97
|
-
<li
|
|
98
|
-
<
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
146
|
+
<li>
|
|
147
|
+
<a
|
|
148
|
+
class="dropdown-item"
|
|
149
|
+
href="#"
|
|
150
|
+
onclick="addStep('calculation')"
|
|
151
|
+
>
|
|
152
|
+
<i class="bi bi-calculator me-2"></i
|
|
153
|
+
>{{ _('Cálculo') }}
|
|
154
|
+
</a>
|
|
155
|
+
</li>
|
|
156
|
+
<li>
|
|
157
|
+
<a
|
|
158
|
+
class="dropdown-item"
|
|
159
|
+
href="#"
|
|
160
|
+
onclick="addStep('conditional')"
|
|
161
|
+
>
|
|
162
|
+
<i
|
|
163
|
+
class="bi bi-signpost-split me-2"
|
|
164
|
+
></i
|
|
165
|
+
>{{ _('Condicional') }}
|
|
166
|
+
</a>
|
|
167
|
+
</li>
|
|
168
|
+
<li>
|
|
169
|
+
<a
|
|
170
|
+
class="dropdown-item"
|
|
171
|
+
href="#"
|
|
172
|
+
onclick="addStep('tax_lookup')"
|
|
173
|
+
>
|
|
174
|
+
<i class="bi bi-table me-2"></i>{{
|
|
175
|
+
_('Búsqueda en Tabla') }}
|
|
176
|
+
</a>
|
|
177
|
+
</li>
|
|
178
|
+
<li>
|
|
179
|
+
<a
|
|
180
|
+
class="dropdown-item"
|
|
181
|
+
href="#"
|
|
182
|
+
onclick="addStep('assignment')"
|
|
183
|
+
>
|
|
184
|
+
<i
|
|
185
|
+
class="bi bi-arrow-right-circle me-2"
|
|
186
|
+
></i
|
|
187
|
+
>{{ _('Asignación') }}
|
|
188
|
+
</a>
|
|
189
|
+
</li>
|
|
109
190
|
</ul>
|
|
110
191
|
</div>
|
|
111
192
|
</div>
|
|
@@ -116,12 +197,20 @@
|
|
|
116
197
|
|
|
117
198
|
<!-- Tax Tables Section -->
|
|
118
199
|
<div class="schema-section mb-4">
|
|
119
|
-
<div
|
|
200
|
+
<div
|
|
201
|
+
class="d-flex justify-content-between align-items-center mb-3"
|
|
202
|
+
>
|
|
120
203
|
<h5 class="section-title mb-0">
|
|
121
|
-
<i class="bi bi-table me-2"></i>{{ _('Tablas de
|
|
204
|
+
<i class="bi bi-table me-2"></i>{{ _('Tablas de
|
|
205
|
+
Impuestos') }}
|
|
122
206
|
</h5>
|
|
123
|
-
<button
|
|
124
|
-
|
|
207
|
+
<button
|
|
208
|
+
type="button"
|
|
209
|
+
class="btn btn-sm btn-primary"
|
|
210
|
+
onclick="addTaxTable()"
|
|
211
|
+
>
|
|
212
|
+
<i class="bi bi-plus-lg me-1"></i>{{ _('Agregar
|
|
213
|
+
Tabla') }}
|
|
125
214
|
</button>
|
|
126
215
|
</div>
|
|
127
216
|
<div id="tax-tables-container">
|
|
@@ -132,15 +221,22 @@
|
|
|
132
221
|
<!-- Output Section -->
|
|
133
222
|
<div class="schema-section">
|
|
134
223
|
<h5 class="section-title">
|
|
135
|
-
<i class="bi bi-box-arrow-right me-2"></i>{{
|
|
224
|
+
<i class="bi bi-box-arrow-right me-2"></i>{{
|
|
225
|
+
_('Resultado Final') }}
|
|
136
226
|
</h5>
|
|
137
227
|
<div class="mb-3">
|
|
138
|
-
<label class="form-label"
|
|
228
|
+
<label class="form-label"
|
|
229
|
+
>{{ _('Variable de salida') }}</label
|
|
230
|
+
>
|
|
139
231
|
<select class="form-select" id="output-variable">
|
|
140
|
-
<option value="">
|
|
232
|
+
<option value="">
|
|
233
|
+
{{ _('Seleccionar variable de resultado...')
|
|
234
|
+
}}
|
|
235
|
+
</option>
|
|
141
236
|
</select>
|
|
142
237
|
<div class="form-text">
|
|
143
|
-
{{ _('Seleccione la variable que contiene el
|
|
238
|
+
{{ _('Seleccione la variable que contiene el
|
|
239
|
+
resultado final del cálculo.') }}
|
|
144
240
|
</div>
|
|
145
241
|
</div>
|
|
146
242
|
</div>
|
|
@@ -152,38 +248,64 @@
|
|
|
152
248
|
<div class="col-lg-5">
|
|
153
249
|
<!-- JSON Preview -->
|
|
154
250
|
<div class="card mb-4">
|
|
155
|
-
<div
|
|
156
|
-
|
|
251
|
+
<div
|
|
252
|
+
class="card-header bg-dark text-white d-flex justify-content-between align-items-center"
|
|
253
|
+
>
|
|
254
|
+
<span
|
|
255
|
+
><i class="bi bi-code-slash me-2"></i>{{ _('Vista Previa
|
|
256
|
+
JSON') }}</span
|
|
257
|
+
>
|
|
157
258
|
<div class="btn-group btn-group-sm">
|
|
158
|
-
<button
|
|
259
|
+
<button
|
|
260
|
+
type="button"
|
|
261
|
+
class="btn btn-outline-light"
|
|
262
|
+
onclick="copyJson()"
|
|
263
|
+
>
|
|
159
264
|
<i class="bi bi-clipboard"></i>
|
|
160
265
|
</button>
|
|
161
|
-
<button
|
|
266
|
+
<button
|
|
267
|
+
type="button"
|
|
268
|
+
class="btn btn-outline-light"
|
|
269
|
+
onclick="formatJson()"
|
|
270
|
+
>
|
|
162
271
|
<i class="bi bi-magic"></i>
|
|
163
272
|
</button>
|
|
164
273
|
</div>
|
|
165
274
|
</div>
|
|
166
275
|
<div class="card-body p-0">
|
|
167
|
-
<textarea
|
|
168
|
-
|
|
276
|
+
<textarea
|
|
277
|
+
id="json-preview"
|
|
278
|
+
class="form-control font-monospace"
|
|
279
|
+
rows="15"
|
|
280
|
+
style="border: none; border-radius: 0; resize: vertical"
|
|
281
|
+
></textarea>
|
|
169
282
|
</div>
|
|
170
283
|
</div>
|
|
171
284
|
|
|
172
285
|
<!-- Test Panel -->
|
|
173
286
|
<div class="card mb-4">
|
|
174
287
|
<div class="card-header bg-info text-white">
|
|
175
|
-
<i class="bi bi-play-circle me-2"></i>{{ _('Probar Cálculo')
|
|
288
|
+
<i class="bi bi-play-circle me-2"></i>{{ _('Probar Cálculo')
|
|
289
|
+
}}
|
|
176
290
|
</div>
|
|
177
291
|
<div class="card-body">
|
|
178
292
|
<div id="test-inputs-container">
|
|
179
293
|
<!-- Dynamic test inputs will be generated -->
|
|
180
294
|
</div>
|
|
181
|
-
<button
|
|
182
|
-
|
|
295
|
+
<button
|
|
296
|
+
type="button"
|
|
297
|
+
class="btn btn-info w-100 mt-3"
|
|
298
|
+
onclick="testCalculation()"
|
|
299
|
+
>
|
|
300
|
+
<i class="bi bi-play-fill me-1"></i>{{ _('Ejecutar
|
|
301
|
+
Prueba') }}
|
|
183
302
|
</button>
|
|
184
|
-
<div id="test-result" class="mt-3" style="display: none
|
|
303
|
+
<div id="test-result" class="mt-3" style="display: none">
|
|
185
304
|
<h6>{{ _('Resultado:') }}</h6>
|
|
186
|
-
<pre
|
|
305
|
+
<pre
|
|
306
|
+
class="bg-light p-3 rounded"
|
|
307
|
+
id="test-result-content"
|
|
308
|
+
></pre>
|
|
187
309
|
</div>
|
|
188
310
|
</div>
|
|
189
311
|
</div>
|
|
@@ -192,15 +314,36 @@
|
|
|
192
314
|
<div class="card">
|
|
193
315
|
<div class="card-body">
|
|
194
316
|
<div class="d-grid gap-2">
|
|
195
|
-
<button
|
|
196
|
-
|
|
317
|
+
<button
|
|
318
|
+
type="button"
|
|
319
|
+
class="btn btn-success btn-lg"
|
|
320
|
+
onclick="saveSchema()"
|
|
321
|
+
>
|
|
322
|
+
<i class="bi bi-check-lg me-2"></i>{{ _('Guardar
|
|
323
|
+
Esquema') }}
|
|
197
324
|
</button>
|
|
198
|
-
<button
|
|
199
|
-
|
|
325
|
+
<button
|
|
326
|
+
type="button"
|
|
327
|
+
class="btn btn-outline-primary"
|
|
328
|
+
onclick="document.getElementById('json-file-input').click()"
|
|
329
|
+
>
|
|
330
|
+
<i class="bi bi-upload me-2"></i>{{ _('Cargar desde
|
|
331
|
+
Archivo JSON') }}
|
|
200
332
|
</button>
|
|
201
|
-
<input
|
|
202
|
-
|
|
203
|
-
|
|
333
|
+
<input
|
|
334
|
+
type="file"
|
|
335
|
+
id="json-file-input"
|
|
336
|
+
accept=".json"
|
|
337
|
+
style="display: none"
|
|
338
|
+
onchange="loadJsonFile(event)"
|
|
339
|
+
/>
|
|
340
|
+
<button
|
|
341
|
+
type="button"
|
|
342
|
+
class="btn btn-outline-secondary"
|
|
343
|
+
onclick="loadExample()"
|
|
344
|
+
>
|
|
345
|
+
<i class="bi bi-file-earmark-code me-2"></i>{{
|
|
346
|
+
_('Cargar Ejemplo de Impuesto Progresivo') }}
|
|
204
347
|
</button>
|
|
205
348
|
</div>
|
|
206
349
|
</div>
|
|
@@ -210,949 +353,124 @@
|
|
|
210
353
|
</div>
|
|
211
354
|
|
|
212
355
|
<style>
|
|
213
|
-
.schema-section {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
.section-title {
|
|
221
|
-
color: #495057;
|
|
222
|
-
font-weight: 600;
|
|
223
|
-
margin-bottom: 1rem;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
.input-item, .step-item, .tax-table-item {
|
|
227
|
-
background: white;
|
|
228
|
-
border: 1px solid #dee2e6;
|
|
229
|
-
border-radius: 6px;
|
|
230
|
-
padding: 1rem;
|
|
231
|
-
margin-bottom: 0.75rem;
|
|
232
|
-
position: relative;
|
|
233
|
-
cursor: move;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
.input-item .drag-handle, .step-item .drag-handle {
|
|
237
|
-
position: absolute;
|
|
238
|
-
left: 0.5rem;
|
|
239
|
-
top: 50%;
|
|
240
|
-
transform: translateY(-50%);
|
|
241
|
-
color: #6c757d;
|
|
242
|
-
cursor: grab;
|
|
243
|
-
font-size: 1.2rem;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
.input-item .drag-handle:active, .step-item .drag-handle:active {
|
|
247
|
-
cursor: grabbing;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
.reorder-buttons {
|
|
251
|
-
position: absolute;
|
|
252
|
-
right: 3rem;
|
|
253
|
-
top: 0.5rem;
|
|
254
|
-
display: flex;
|
|
255
|
-
gap: 0.25rem;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
.reorder-buttons .btn {
|
|
259
|
-
padding: 0.125rem 0.375rem;
|
|
260
|
-
font-size: 0.75rem;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
.input-item:hover, .step-item:hover, .tax-table-item:hover {
|
|
264
|
-
border-color: #0d6efd;
|
|
265
|
-
box-shadow: 0 0 0 2px rgba(13, 110, 253, 0.1);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
.remove-btn {
|
|
269
|
-
position: absolute;
|
|
270
|
-
top: 0.5rem;
|
|
271
|
-
right: 0.5rem;
|
|
272
|
-
padding: 0.25rem 0.5rem;
|
|
273
|
-
font-size: 0.875rem;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
.step-type-badge {
|
|
277
|
-
font-size: 0.75rem;
|
|
278
|
-
font-weight: 600;
|
|
279
|
-
text-transform: uppercase;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
.bracket-row {
|
|
283
|
-
background: #f8f9fa;
|
|
284
|
-
border-radius: 4px;
|
|
285
|
-
padding: 0.75rem;
|
|
286
|
-
margin-bottom: 0.5rem;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
#json-preview {
|
|
290
|
-
font-size: 0.85rem;
|
|
291
|
-
background: #1e1e1e;
|
|
292
|
-
color: #d4d4d4;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
.sortable-ghost {
|
|
296
|
-
opacity: 0.4;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
.sortable-drag {
|
|
300
|
-
opacity: 0.8;
|
|
301
|
-
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
|
|
302
|
-
}
|
|
303
|
-
</style>
|
|
304
|
-
|
|
305
|
-
<script>
|
|
306
|
-
// Initialize with existing schema
|
|
307
|
-
let schema = {{ schema_json|safe }} || {
|
|
308
|
-
meta: {},
|
|
309
|
-
inputs: [],
|
|
310
|
-
steps: [],
|
|
311
|
-
tax_tables: {},
|
|
312
|
-
output: ''
|
|
313
|
-
};
|
|
314
|
-
|
|
315
|
-
const exampleSchema = {{ example_schema|safe }};
|
|
316
|
-
|
|
317
|
-
// Available data sources from the database
|
|
318
|
-
const availableSources = {{ available_sources|safe }};
|
|
319
|
-
|
|
320
|
-
// Counter for unique IDs
|
|
321
|
-
let inputCounter = 0;
|
|
322
|
-
let stepCounter = 0;
|
|
323
|
-
let tableCounter = 0;
|
|
324
|
-
|
|
325
|
-
// Sortable instances
|
|
326
|
-
let inputsSortable = null;
|
|
327
|
-
let stepsSortable = null;
|
|
328
|
-
|
|
329
|
-
document.addEventListener('DOMContentLoaded', function() {
|
|
330
|
-
loadSchemaToEditor();
|
|
331
|
-
updateJsonPreview();
|
|
332
|
-
initializeSortable();
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
function initializeSortable() {
|
|
336
|
-
// Initialize sortable for inputs
|
|
337
|
-
const inputsContainer = document.getElementById('inputs-container');
|
|
338
|
-
if (inputsContainer && typeof Sortable !== 'undefined') {
|
|
339
|
-
inputsSortable = Sortable.create(inputsContainer, {
|
|
340
|
-
animation: 150,
|
|
341
|
-
handle: '.drag-handle',
|
|
342
|
-
ghostClass: 'sortable-ghost',
|
|
343
|
-
dragClass: 'sortable-drag',
|
|
344
|
-
onEnd: function() {
|
|
345
|
-
updateJsonPreview();
|
|
346
|
-
}
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
// Initialize sortable for steps
|
|
351
|
-
const stepsContainer = document.getElementById('steps-container');
|
|
352
|
-
if (stepsContainer && typeof Sortable !== 'undefined') {
|
|
353
|
-
stepsSortable = Sortable.create(stepsContainer, {
|
|
354
|
-
animation: 150,
|
|
355
|
-
handle: '.drag-handle',
|
|
356
|
-
ghostClass: 'sortable-ghost',
|
|
357
|
-
dragClass: 'sortable-drag',
|
|
358
|
-
onEnd: function() {
|
|
359
|
-
updateJsonPreview();
|
|
360
|
-
}
|
|
361
|
-
});
|
|
356
|
+
.schema-section {
|
|
357
|
+
background: #f8f9fa;
|
|
358
|
+
border-radius: 8px;
|
|
359
|
+
padding: 1.25rem;
|
|
360
|
+
border: 1px solid #e9ecef;
|
|
362
361
|
}
|
|
363
|
-
}
|
|
364
362
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
document.getElementById('meta-reference-currency').value = schema.meta.reference_currency || schema.meta.currency || '';
|
|
370
|
-
document.getElementById('meta-description').value = schema.meta.description || '';
|
|
363
|
+
.section-title {
|
|
364
|
+
color: #495057;
|
|
365
|
+
font-weight: 600;
|
|
366
|
+
margin-bottom: 1rem;
|
|
371
367
|
}
|
|
372
368
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
369
|
+
.input-item,
|
|
370
|
+
.step-item,
|
|
371
|
+
.tax-table-item {
|
|
372
|
+
background: white;
|
|
373
|
+
border: 1px solid #dee2e6;
|
|
374
|
+
border-radius: 6px;
|
|
375
|
+
padding: 1rem;
|
|
376
|
+
margin-bottom: 0.75rem;
|
|
377
|
+
position: relative;
|
|
378
|
+
cursor: move;
|
|
378
379
|
}
|
|
379
380
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
381
|
+
.input-item .drag-handle,
|
|
382
|
+
.step-item .drag-handle {
|
|
383
|
+
position: absolute;
|
|
384
|
+
left: 0.5rem;
|
|
385
|
+
top: 50%;
|
|
386
|
+
transform: translateY(-50%);
|
|
387
|
+
color: #6c757d;
|
|
388
|
+
cursor: grab;
|
|
389
|
+
font-size: 1.2rem;
|
|
385
390
|
}
|
|
386
391
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
if (schema.tax_tables) {
|
|
391
|
-
Object.entries(schema.tax_tables).forEach(([name, brackets]) => {
|
|
392
|
-
addTaxTable(name, brackets);
|
|
393
|
-
});
|
|
392
|
+
.input-item .drag-handle:active,
|
|
393
|
+
.step-item .drag-handle:active {
|
|
394
|
+
cursor: grabbing;
|
|
394
395
|
}
|
|
395
396
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
const container = document.getElementById('inputs-container');
|
|
403
|
-
const id = inputCounter++;
|
|
404
|
-
|
|
405
|
-
// Build source options grouped by category
|
|
406
|
-
let sourceOptionsHtml = '<option value="">{{ _("Seleccionar origen o escribir valor...") }}</option>';
|
|
407
|
-
let currentCategory = '';
|
|
408
|
-
availableSources.forEach(source => {
|
|
409
|
-
if (source.category !== currentCategory) {
|
|
410
|
-
if (currentCategory !== '') {
|
|
411
|
-
sourceOptionsHtml += '</optgroup>';
|
|
412
|
-
}
|
|
413
|
-
sourceOptionsHtml += `<optgroup label="${source.category}">`;
|
|
414
|
-
currentCategory = source.category;
|
|
415
|
-
}
|
|
416
|
-
sourceOptionsHtml += `<option value="${source.value}" title="${source.description}">${source.label}</option>`;
|
|
417
|
-
});
|
|
418
|
-
if (currentCategory !== '') {
|
|
419
|
-
sourceOptionsHtml += '</optgroup>';
|
|
397
|
+
.reorder-buttons {
|
|
398
|
+
position: absolute;
|
|
399
|
+
right: 3rem;
|
|
400
|
+
top: 0.5rem;
|
|
401
|
+
display: flex;
|
|
402
|
+
gap: 0.25rem;
|
|
420
403
|
}
|
|
421
|
-
|
|
422
|
-
const currentSourceValue = data?.source || data?.default || '';
|
|
423
|
-
|
|
424
|
-
const html = `
|
|
425
|
-
<div class="input-item" id="input-${id}">
|
|
426
|
-
<div class="drag-handle">
|
|
427
|
-
<i class="bi bi-grip-vertical"></i>
|
|
428
|
-
</div>
|
|
429
|
-
<div class="reorder-buttons">
|
|
430
|
-
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="moveInputUp(${id})" title="{{ _('Subir') }}">
|
|
431
|
-
<i class="bi bi-arrow-up"></i>
|
|
432
|
-
</button>
|
|
433
|
-
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="moveInputDown(${id})" title="{{ _('Bajar') }}">
|
|
434
|
-
<i class="bi bi-arrow-down"></i>
|
|
435
|
-
</button>
|
|
436
|
-
</div>
|
|
437
|
-
<button type="button" class="btn btn-danger btn-sm remove-btn" onclick="removeInput(${id})">
|
|
438
|
-
<i class="bi bi-x"></i>
|
|
439
|
-
</button>
|
|
440
|
-
<div class="row">
|
|
441
|
-
<div class="col-md-4">
|
|
442
|
-
<label class="form-label small">{{ _('Nombre') }}</label>
|
|
443
|
-
<input type="text" class="form-control form-control-sm input-name"
|
|
444
|
-
value="${data?.name || ''}" placeholder="{{ _('Ej: salario_mensual') }}"
|
|
445
|
-
onchange="updateJsonPreview()">
|
|
446
|
-
</div>
|
|
447
|
-
<div class="col-md-3">
|
|
448
|
-
<label class="form-label small">{{ _('Tipo') }}</label>
|
|
449
|
-
<select class="form-select form-select-sm input-type" onchange="updateJsonPreview()">
|
|
450
|
-
<option value="decimal" ${data?.type === 'decimal' ? 'selected' : ''}>{{ _('Decimal') }}</option>
|
|
451
|
-
<option value="integer" ${data?.type === 'integer' ? 'selected' : ''}>{{ _('Entero') }}</option>
|
|
452
|
-
<option value="string" ${data?.type === 'string' ? 'selected' : ''}>{{ _('Texto') }}</option>
|
|
453
|
-
<option value="boolean" ${data?.type === 'boolean' ? 'selected' : ''}>{{ _('Booleano') }}</option>
|
|
454
|
-
<option value="date" ${data?.type === 'date' ? 'selected' : ''}>{{ _('Fecha') }}</option>
|
|
455
|
-
</select>
|
|
456
|
-
</div>
|
|
457
|
-
<div class="col-md-5">
|
|
458
|
-
<label class="form-label small">{{ _('Origen de Datos') }}</label>
|
|
459
|
-
<select class="form-select form-select-sm input-source-select"
|
|
460
|
-
onchange="handleSourceChange(this, ${id}); updateJsonPreview()">
|
|
461
|
-
${sourceOptionsHtml}
|
|
462
|
-
<option value="__custom__">{{ _('Valor personalizado...') }}</option>
|
|
463
|
-
</select>
|
|
464
|
-
</div>
|
|
465
|
-
</div>
|
|
466
|
-
<div class="row mt-2">
|
|
467
|
-
<div class="col-md-6">
|
|
468
|
-
<input type="text" class="form-control form-control-sm input-source-custom"
|
|
469
|
-
value="${currentSourceValue}"
|
|
470
|
-
placeholder="{{ _('Origen (empleado.campo) o valor default') }}"
|
|
471
|
-
onchange="updateJsonPreview()"
|
|
472
|
-
style="${currentSourceValue && !availableSources.find(s => s.value === currentSourceValue) ? '' : 'display:none'}">
|
|
473
|
-
</div>
|
|
474
|
-
<div class="col-md-6">
|
|
475
|
-
<input type="text" class="form-control form-control-sm input-description"
|
|
476
|
-
value="${data?.description || ''}"
|
|
477
|
-
placeholder="{{ _('Descripción (opcional)') }}"
|
|
478
|
-
onchange="updateJsonPreview()">
|
|
479
|
-
</div>
|
|
480
|
-
</div>
|
|
481
|
-
</div>
|
|
482
|
-
`;
|
|
483
|
-
container.insertAdjacentHTML('beforeend', html);
|
|
484
|
-
|
|
485
|
-
// Set the select value if it matches an available source
|
|
486
|
-
const selectEl = document.querySelector(`#input-${id} .input-source-select`);
|
|
487
|
-
if (currentSourceValue) {
|
|
488
|
-
const matchingSource = availableSources.find(s => s.value === currentSourceValue);
|
|
489
|
-
if (matchingSource) {
|
|
490
|
-
selectEl.value = currentSourceValue;
|
|
491
|
-
} else {
|
|
492
|
-
selectEl.value = '__custom__';
|
|
493
|
-
document.querySelector(`#input-${id} .input-source-custom`).style.display = '';
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
updateJsonPreview();
|
|
498
|
-
}
|
|
499
404
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
customInput.style.display = '';
|
|
504
|
-
customInput.focus();
|
|
505
|
-
} else if (selectEl.value === '') {
|
|
506
|
-
customInput.style.display = 'none';
|
|
507
|
-
customInput.value = '';
|
|
508
|
-
} else {
|
|
509
|
-
customInput.style.display = 'none';
|
|
510
|
-
customInput.value = selectEl.value;
|
|
405
|
+
.reorder-buttons .btn {
|
|
406
|
+
padding: 0.125rem 0.375rem;
|
|
407
|
+
font-size: 0.75rem;
|
|
511
408
|
}
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
function removeInput(id) {
|
|
515
|
-
document.getElementById(`input-${id}`).remove();
|
|
516
|
-
updateJsonPreview();
|
|
517
|
-
}
|
|
518
409
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
updateJsonPreview();
|
|
410
|
+
.input-item:hover,
|
|
411
|
+
.step-item:hover,
|
|
412
|
+
.tax-table-item:hover {
|
|
413
|
+
border-color: #0d6efd;
|
|
414
|
+
box-shadow: 0 0 0 2px rgba(13, 110, 253, 0.1);
|
|
525
415
|
}
|
|
526
|
-
}
|
|
527
416
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
417
|
+
.remove-btn {
|
|
418
|
+
position: absolute;
|
|
419
|
+
top: 0.5rem;
|
|
420
|
+
right: 0.5rem;
|
|
421
|
+
padding: 0.25rem 0.5rem;
|
|
422
|
+
font-size: 0.875rem;
|
|
534
423
|
}
|
|
535
|
-
}
|
|
536
424
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
let typeHtml = '';
|
|
542
|
-
let typeBadgeClass = '';
|
|
543
|
-
|
|
544
|
-
switch(type) {
|
|
545
|
-
case 'calculation':
|
|
546
|
-
typeBadgeClass = 'bg-primary';
|
|
547
|
-
typeHtml = `
|
|
548
|
-
<div class="mb-2">
|
|
549
|
-
<label class="form-label small">{{ _('Fórmula') }}</label>
|
|
550
|
-
<input type="text" class="form-control form-control-sm step-formula"
|
|
551
|
-
value="${data?.formula || ''}"
|
|
552
|
-
placeholder="{{ _('Ej: salario_mensual * 12') }}"
|
|
553
|
-
onchange="updateJsonPreview()">
|
|
554
|
-
</div>
|
|
555
|
-
`;
|
|
556
|
-
break;
|
|
557
|
-
case 'conditional':
|
|
558
|
-
typeBadgeClass = 'bg-warning text-dark';
|
|
559
|
-
typeHtml = `
|
|
560
|
-
<div class="mb-2">
|
|
561
|
-
<label class="form-label small">{{ _('Condición') }}</label>
|
|
562
|
-
<div class="row g-2">
|
|
563
|
-
<div class="col-4">
|
|
564
|
-
<input type="text" class="form-control form-control-sm step-cond-left"
|
|
565
|
-
value="${data?.condition?.left || ''}" placeholder="{{ _('Variable') }}"
|
|
566
|
-
onchange="updateJsonPreview()">
|
|
567
|
-
</div>
|
|
568
|
-
<div class="col-2">
|
|
569
|
-
<select class="form-select form-select-sm step-cond-op" onchange="updateJsonPreview()">
|
|
570
|
-
<option value=">" ${ data?.condition?.operator === '>' ? 'selected' : '' }>></option>
|
|
571
|
-
<option value=">=" ${ data?.condition?.operator === '>=' ? 'selected' : '' }>>=</option>
|
|
572
|
-
<option value="<" ${ data?.condition?.operator === '<' ? 'selected' : '' }><</option>
|
|
573
|
-
<option value="<=" ${ data?.condition?.operator === '<=' ? 'selected' : '' }><=</option>
|
|
574
|
-
<option value="==" ${ data?.condition?.operator === '==' ? 'selected' : '' }>==</option>
|
|
575
|
-
<option value="!=" ${ data?.condition?.operator === '!=' ? 'selected' : '' }>!=</option>
|
|
576
|
-
</select>
|
|
577
|
-
</div>
|
|
578
|
-
<div class="col-4">
|
|
579
|
-
<input type="text" class="form-control form-control-sm step-cond-right"
|
|
580
|
-
value="${data?.condition?.right || ''}" placeholder="{{ _('Valor') }}"
|
|
581
|
-
onchange="updateJsonPreview()">
|
|
582
|
-
</div>
|
|
583
|
-
</div>
|
|
584
|
-
</div>
|
|
585
|
-
<div class="row g-2">
|
|
586
|
-
<div class="col-6">
|
|
587
|
-
<label class="form-label small">{{ _('Si verdadero') }}</label>
|
|
588
|
-
<input type="text" class="form-control form-control-sm step-if-true"
|
|
589
|
-
value="${data?.if_true || ''}" placeholder="{{ _('Fórmula si cumple') }}"
|
|
590
|
-
onchange="updateJsonPreview()">
|
|
591
|
-
</div>
|
|
592
|
-
<div class="col-6">
|
|
593
|
-
<label class="form-label small">{{ _('Si falso') }}</label>
|
|
594
|
-
<input type="text" class="form-control form-control-sm step-if-false"
|
|
595
|
-
value="${data?.if_false || ''}" placeholder="{{ _('Fórmula si no cumple') }}"
|
|
596
|
-
onchange="updateJsonPreview()">
|
|
597
|
-
</div>
|
|
598
|
-
</div>
|
|
599
|
-
`;
|
|
600
|
-
break;
|
|
601
|
-
case 'tax_lookup':
|
|
602
|
-
typeBadgeClass = 'bg-danger';
|
|
603
|
-
typeHtml = `
|
|
604
|
-
<div class="row g-2">
|
|
605
|
-
<div class="col-6">
|
|
606
|
-
<label class="form-label small">{{ _('Tabla de impuestos') }}</label>
|
|
607
|
-
<input type="text" class="form-control form-control-sm step-table"
|
|
608
|
-
value="${data?.table || ''}" placeholder="{{ _('Nombre de la tabla') }}"
|
|
609
|
-
onchange="updateJsonPreview()">
|
|
610
|
-
</div>
|
|
611
|
-
<div class="col-6">
|
|
612
|
-
<label class="form-label small">{{ _('Variable de entrada') }}</label>
|
|
613
|
-
<input type="text" class="form-control form-control-sm step-input"
|
|
614
|
-
value="${data?.input || ''}" placeholder="{{ _('Variable a buscar') }}"
|
|
615
|
-
onchange="updateJsonPreview()">
|
|
616
|
-
</div>
|
|
617
|
-
</div>
|
|
618
|
-
`;
|
|
619
|
-
break;
|
|
620
|
-
case 'assignment':
|
|
621
|
-
typeBadgeClass = 'bg-info';
|
|
622
|
-
typeHtml = `
|
|
623
|
-
<div class="mb-2">
|
|
624
|
-
<label class="form-label small">{{ _('Valor') }}</label>
|
|
625
|
-
<input type="text" class="form-control form-control-sm step-value"
|
|
626
|
-
value="${data?.value || ''}" placeholder="{{ _('Variable o valor a asignar') }}"
|
|
627
|
-
onchange="updateJsonPreview()">
|
|
628
|
-
</div>
|
|
629
|
-
`;
|
|
630
|
-
break;
|
|
425
|
+
.step-type-badge {
|
|
426
|
+
font-size: 0.75rem;
|
|
427
|
+
font-weight: 600;
|
|
428
|
+
text-transform: uppercase;
|
|
631
429
|
}
|
|
632
|
-
|
|
633
|
-
const html = `
|
|
634
|
-
<div class="step-item" id="step-${id}" data-type="${type}">
|
|
635
|
-
<div class="drag-handle">
|
|
636
|
-
<i class="bi bi-grip-vertical"></i>
|
|
637
|
-
</div>
|
|
638
|
-
<div class="reorder-buttons">
|
|
639
|
-
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="moveStepUp(${id})" title="{{ _('Subir') }}">
|
|
640
|
-
<i class="bi bi-arrow-up"></i>
|
|
641
|
-
</button>
|
|
642
|
-
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="moveStepDown(${id})" title="{{ _('Bajar') }}">
|
|
643
|
-
<i class="bi bi-arrow-down"></i>
|
|
644
|
-
</button>
|
|
645
|
-
</div>
|
|
646
|
-
<button type="button" class="btn btn-danger btn-sm remove-btn" onclick="removeStep(${id})">
|
|
647
|
-
<i class="bi bi-x"></i>
|
|
648
|
-
</button>
|
|
649
|
-
<div class="d-flex align-items-center mb-2">
|
|
650
|
-
<span class="badge ${typeBadgeClass} step-type-badge me-2">${type}</span>
|
|
651
|
-
<input type="text" class="form-control form-control-sm step-name"
|
|
652
|
-
value="${data?.name || ''}" placeholder="{{ _('Nombre del paso') }}"
|
|
653
|
-
style="max-width: 200px;" onchange="updateJsonPreview()">
|
|
654
|
-
</div>
|
|
655
|
-
${typeHtml}
|
|
656
|
-
<div class="mt-2">
|
|
657
|
-
<input type="text" class="form-control form-control-sm step-description"
|
|
658
|
-
value="${data?.description || ''}" placeholder="{{ _('Descripción (opcional)') }}"
|
|
659
|
-
onchange="updateJsonPreview()">
|
|
660
|
-
</div>
|
|
661
|
-
</div>
|
|
662
|
-
`;
|
|
663
|
-
container.insertAdjacentHTML('beforeend', html);
|
|
664
|
-
updateJsonPreview();
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
function removeStep(id) {
|
|
668
|
-
document.getElementById(`step-${id}`).remove();
|
|
669
|
-
updateJsonPreview();
|
|
670
|
-
}
|
|
671
430
|
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
updateJsonPreview();
|
|
431
|
+
.bracket-row {
|
|
432
|
+
background: #f8f9fa;
|
|
433
|
+
border-radius: 4px;
|
|
434
|
+
padding: 0.75rem;
|
|
435
|
+
margin-bottom: 0.5rem;
|
|
678
436
|
}
|
|
679
|
-
}
|
|
680
437
|
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
element.parentNode.insertBefore(next, element);
|
|
686
|
-
updateJsonPreview();
|
|
438
|
+
#json-preview {
|
|
439
|
+
font-size: 0.85rem;
|
|
440
|
+
background: #1e1e1e;
|
|
441
|
+
color: #d4d4d4;
|
|
687
442
|
}
|
|
688
|
-
}
|
|
689
443
|
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
const id = tableCounter++;
|
|
693
|
-
|
|
694
|
-
const html = `
|
|
695
|
-
<div class="tax-table-item" id="table-${id}">
|
|
696
|
-
<button type="button" class="btn btn-danger btn-sm remove-btn" onclick="removeTaxTable(${id})">
|
|
697
|
-
<i class="bi bi-x"></i>
|
|
698
|
-
</button>
|
|
699
|
-
<div class="mb-3">
|
|
700
|
-
<label class="form-label small">{{ _('Nombre de la tabla') }}</label>
|
|
701
|
-
<input type="text" class="form-control form-control-sm table-name"
|
|
702
|
-
value="${name || ''}" placeholder="{{ _('Ej: income_tax_brackets') }}"
|
|
703
|
-
onchange="updateJsonPreview()">
|
|
704
|
-
</div>
|
|
705
|
-
<div class="brackets-container" id="brackets-${id}">
|
|
706
|
-
<!-- Brackets will be added here -->
|
|
707
|
-
</div>
|
|
708
|
-
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addBracket(${id})">
|
|
709
|
-
<i class="bi bi-plus-lg me-1"></i>{{ _('Agregar Tramo') }}
|
|
710
|
-
</button>
|
|
711
|
-
</div>
|
|
712
|
-
`;
|
|
713
|
-
container.insertAdjacentHTML('beforeend', html);
|
|
714
|
-
|
|
715
|
-
// Add existing brackets
|
|
716
|
-
if (brackets && brackets.length > 0) {
|
|
717
|
-
brackets.forEach(bracket => addBracket(id, bracket));
|
|
444
|
+
.sortable-ghost {
|
|
445
|
+
opacity: 0.4;
|
|
718
446
|
}
|
|
719
|
-
|
|
720
|
-
updateJsonPreview();
|
|
721
|
-
}
|
|
722
447
|
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
function addBracket(tableId, data = null) {
|
|
729
|
-
const container = document.getElementById(`brackets-${tableId}`);
|
|
730
|
-
const html = `
|
|
731
|
-
<div class="bracket-row">
|
|
732
|
-
<div class="row g-2 align-items-center">
|
|
733
|
-
<div class="col">
|
|
734
|
-
<input type="number" class="form-control form-control-sm bracket-min"
|
|
735
|
-
value="${data?.min ?? ''}" placeholder="{{ _('Mín') }}"
|
|
736
|
-
onchange="updateJsonPreview()">
|
|
737
|
-
</div>
|
|
738
|
-
<div class="col">
|
|
739
|
-
<input type="number" class="form-control form-control-sm bracket-max"
|
|
740
|
-
value="${data?.max ?? ''}" placeholder="{{ _('Máx') }}"
|
|
741
|
-
onchange="updateJsonPreview()">
|
|
742
|
-
</div>
|
|
743
|
-
<div class="col">
|
|
744
|
-
<input type="number" class="form-control form-control-sm bracket-rate"
|
|
745
|
-
value="${data?.rate ?? ''}" placeholder="{{ _('Tasa') }}" step="0.01"
|
|
746
|
-
onchange="updateJsonPreview()">
|
|
747
|
-
</div>
|
|
748
|
-
<div class="col">
|
|
749
|
-
<input type="number" class="form-control form-control-sm bracket-fixed"
|
|
750
|
-
value="${data?.fixed ?? ''}" placeholder="{{ _('Fijo') }}"
|
|
751
|
-
onchange="updateJsonPreview()">
|
|
752
|
-
</div>
|
|
753
|
-
<div class="col">
|
|
754
|
-
<input type="number" class="form-control form-control-sm bracket-over"
|
|
755
|
-
value="${data?.over ?? ''}" placeholder="{{ _('Sobre') }}"
|
|
756
|
-
onchange="updateJsonPreview()">
|
|
757
|
-
</div>
|
|
758
|
-
<div class="col-auto">
|
|
759
|
-
<button type="button" class="btn btn-sm btn-outline-danger" onclick="this.closest('.bracket-row').remove(); updateJsonPreview();">
|
|
760
|
-
<i class="bi bi-x"></i>
|
|
761
|
-
</button>
|
|
762
|
-
</div>
|
|
763
|
-
</div>
|
|
764
|
-
</div>
|
|
765
|
-
`;
|
|
766
|
-
container.insertAdjacentHTML('beforeend', html);
|
|
767
|
-
updateJsonPreview();
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
function updateOutputSelect() {
|
|
771
|
-
const select = document.getElementById('output-variable');
|
|
772
|
-
const currentValue = select.value;
|
|
773
|
-
select.innerHTML = '<option value="">{{ _("Seleccionar variable de resultado...") }}</option>';
|
|
774
|
-
|
|
775
|
-
// Add all input names
|
|
776
|
-
document.querySelectorAll('.input-name').forEach(input => {
|
|
777
|
-
if (input.value) {
|
|
778
|
-
select.innerHTML += `<option value="${input.value}">${input.value} (input)</option>`;
|
|
779
|
-
}
|
|
780
|
-
});
|
|
781
|
-
|
|
782
|
-
// Add all step names
|
|
783
|
-
document.querySelectorAll('.step-name').forEach(input => {
|
|
784
|
-
if (input.value) {
|
|
785
|
-
select.innerHTML += `<option value="${input.value}">${input.value} (step)</option>`;
|
|
786
|
-
}
|
|
787
|
-
});
|
|
788
|
-
|
|
789
|
-
select.value = currentValue;
|
|
790
|
-
}
|
|
448
|
+
.sortable-drag {
|
|
449
|
+
opacity: 0.8;
|
|
450
|
+
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
|
|
451
|
+
}
|
|
452
|
+
</style>
|
|
791
453
|
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
reference_currency: document.getElementById('meta-reference-currency').value,
|
|
797
|
-
description: document.getElementById('meta-description').value
|
|
798
|
-
},
|
|
799
|
-
inputs: [],
|
|
800
|
-
steps: [],
|
|
801
|
-
tax_tables: {},
|
|
802
|
-
output: document.getElementById('output-variable').value
|
|
803
|
-
};
|
|
804
|
-
|
|
805
|
-
// Collect inputs
|
|
806
|
-
document.querySelectorAll('.input-item').forEach(item => {
|
|
807
|
-
const input = {
|
|
808
|
-
name: item.querySelector('.input-name').value,
|
|
809
|
-
type: item.querySelector('.input-type').value
|
|
810
|
-
};
|
|
811
|
-
// Get source from either the select dropdown or the custom input field
|
|
812
|
-
const selectEl = item.querySelector('.input-source-select');
|
|
813
|
-
const customEl = item.querySelector('.input-source-custom');
|
|
814
|
-
let source = '';
|
|
815
|
-
if (selectEl && selectEl.value && selectEl.value !== '__custom__' && selectEl.value !== '') {
|
|
816
|
-
source = selectEl.value;
|
|
817
|
-
} else if (customEl) {
|
|
818
|
-
source = customEl.value;
|
|
819
|
-
}
|
|
820
|
-
if (source) {
|
|
821
|
-
if (source.includes('.')) {
|
|
822
|
-
input.source = source;
|
|
823
|
-
} else if (!isNaN(source)) {
|
|
824
|
-
input.default = parseFloat(source);
|
|
825
|
-
} else {
|
|
826
|
-
input.default = source;
|
|
827
|
-
}
|
|
828
|
-
}
|
|
829
|
-
const desc = item.querySelector('.input-description').value;
|
|
830
|
-
if (desc) input.description = desc;
|
|
831
|
-
|
|
832
|
-
if (input.name) newSchema.inputs.push(input);
|
|
833
|
-
});
|
|
834
|
-
|
|
835
|
-
// Collect steps
|
|
836
|
-
document.querySelectorAll('.step-item').forEach(item => {
|
|
837
|
-
const type = item.dataset.type;
|
|
838
|
-
const step = {
|
|
839
|
-
name: item.querySelector('.step-name').value,
|
|
840
|
-
type: type
|
|
841
|
-
};
|
|
842
|
-
|
|
843
|
-
switch(type) {
|
|
844
|
-
case 'calculation':
|
|
845
|
-
step.formula = item.querySelector('.step-formula').value;
|
|
846
|
-
break;
|
|
847
|
-
case 'conditional':
|
|
848
|
-
step.condition = {
|
|
849
|
-
left: item.querySelector('.step-cond-left').value,
|
|
850
|
-
operator: item.querySelector('.step-cond-op').value,
|
|
851
|
-
right: item.querySelector('.step-cond-right').value
|
|
852
|
-
};
|
|
853
|
-
// Try to parse right value as number
|
|
854
|
-
if (!isNaN(step.condition.right)) {
|
|
855
|
-
step.condition.right = parseFloat(step.condition.right);
|
|
856
|
-
}
|
|
857
|
-
step.if_true = item.querySelector('.step-if-true').value;
|
|
858
|
-
step.if_false = item.querySelector('.step-if-false').value;
|
|
859
|
-
break;
|
|
860
|
-
case 'tax_lookup':
|
|
861
|
-
step.table = item.querySelector('.step-table').value;
|
|
862
|
-
step.input = item.querySelector('.step-input').value;
|
|
863
|
-
break;
|
|
864
|
-
case 'assignment':
|
|
865
|
-
step.value = item.querySelector('.step-value').value;
|
|
866
|
-
break;
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
const desc = item.querySelector('.step-description').value;
|
|
870
|
-
if (desc) step.description = desc;
|
|
871
|
-
|
|
872
|
-
if (step.name) newSchema.steps.push(step);
|
|
873
|
-
});
|
|
874
|
-
|
|
875
|
-
// Collect tax tables
|
|
876
|
-
document.querySelectorAll('.tax-table-item').forEach(item => {
|
|
877
|
-
const tableName = item.querySelector('.table-name').value;
|
|
878
|
-
if (!tableName) return;
|
|
879
|
-
|
|
880
|
-
const brackets = [];
|
|
881
|
-
item.querySelectorAll('.bracket-row').forEach(row => {
|
|
882
|
-
const bracket = {};
|
|
883
|
-
const min = row.querySelector('.bracket-min').value;
|
|
884
|
-
const max = row.querySelector('.bracket-max').value;
|
|
885
|
-
const rate = row.querySelector('.bracket-rate').value;
|
|
886
|
-
const fixed = row.querySelector('.bracket-fixed').value;
|
|
887
|
-
const over = row.querySelector('.bracket-over').value;
|
|
888
|
-
|
|
889
|
-
if (min !== '') bracket.min = parseFloat(min);
|
|
890
|
-
if (max !== '') bracket.max = parseFloat(max);
|
|
891
|
-
else bracket.max = null;
|
|
892
|
-
if (rate !== '') bracket.rate = parseFloat(rate);
|
|
893
|
-
if (fixed !== '') bracket.fixed = parseFloat(fixed);
|
|
894
|
-
if (over !== '') bracket.over = parseFloat(over);
|
|
895
|
-
|
|
896
|
-
if (Object.keys(bracket).length > 0) brackets.push(bracket);
|
|
897
|
-
});
|
|
898
|
-
|
|
899
|
-
if (brackets.length > 0) {
|
|
900
|
-
newSchema.tax_tables[tableName] = brackets;
|
|
901
|
-
}
|
|
902
|
-
});
|
|
903
|
-
|
|
904
|
-
return newSchema;
|
|
905
|
-
}
|
|
454
|
+
<script>
|
|
455
|
+
// Schema data from backend - using tojson filter for proper escaping
|
|
456
|
+
let schema = {{ schema_data|tojson }};
|
|
457
|
+
console.log("Initial schema:", schema);
|
|
906
458
|
|
|
907
|
-
|
|
908
|
-
schema = collectSchemaFromEditor();
|
|
909
|
-
document.getElementById('json-preview').value = JSON.stringify(schema, null, 2);
|
|
910
|
-
updateOutputSelect();
|
|
911
|
-
generateTestInputs();
|
|
912
|
-
}
|
|
459
|
+
const exampleSchema = {{ example_schema_data|tojson }};
|
|
913
460
|
|
|
914
|
-
|
|
915
|
-
const
|
|
916
|
-
container.innerHTML = '';
|
|
461
|
+
// Available data sources from the database
|
|
462
|
+
const availableSources = {{ available_sources_data|tojson }};
|
|
917
463
|
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
<div class="mb-2">
|
|
921
|
-
<label class="form-label small">${input.name}</label>
|
|
922
|
-
<input type="number" class="form-control form-control-sm test-input"
|
|
923
|
-
data-name="${input.name}"
|
|
924
|
-
value="${input.default || 0}" step="0.01">
|
|
925
|
-
</div>
|
|
926
|
-
`;
|
|
927
|
-
container.insertAdjacentHTML('beforeend', html);
|
|
928
|
-
});
|
|
929
|
-
}
|
|
464
|
+
// Store rule ID in body dataset for later use
|
|
465
|
+
document.body.dataset.ruleId = '{{ rule.id }}';
|
|
930
466
|
|
|
931
|
-
function
|
|
932
|
-
const textarea = document.getElementById('json-preview');
|
|
933
|
-
textarea.select();
|
|
934
|
-
document.execCommand('copy');
|
|
935
|
-
alert('{{ _("JSON copiado al portapapeles") }}');
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
function formatJson() {
|
|
939
|
-
const textarea = document.getElementById('json-preview');
|
|
940
|
-
try {
|
|
941
|
-
const json = JSON.parse(textarea.value);
|
|
942
|
-
textarea.value = JSON.stringify(json, null, 2);
|
|
943
|
-
} catch (e) {
|
|
944
|
-
alert('{{ _("JSON inválido") }}');
|
|
945
|
-
}
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
function loadExample() {
|
|
949
|
-
if (confirm('{{ _("¿Cargar el ejemplo de impuesto progresivo? Esto reemplazará el esquema actual.") }}')) {
|
|
950
|
-
schema = JSON.parse(JSON.stringify(exampleSchema));
|
|
467
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
951
468
|
loadSchemaToEditor();
|
|
952
469
|
updateJsonPreview();
|
|
953
|
-
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
async function saveSchema() {
|
|
958
|
-
try {
|
|
959
|
-
const schemaToSave = collectSchemaFromEditor();
|
|
960
|
-
|
|
961
|
-
const response = await fetch('{{ url_for("calculation_rule.save_schema", id=rule.id) }}', {
|
|
962
|
-
method: 'POST',
|
|
963
|
-
headers: {
|
|
964
|
-
'Content-Type': 'application/json',
|
|
965
|
-
},
|
|
966
|
-
body: JSON.stringify({ schema: schemaToSave })
|
|
967
|
-
});
|
|
968
|
-
|
|
969
|
-
const result = await response.json();
|
|
970
|
-
|
|
971
|
-
if (result.success) {
|
|
972
|
-
alert('{{ _("Esquema guardado exitosamente") }}');
|
|
973
|
-
} else {
|
|
974
|
-
alert('{{ _("Error: ") }}' + result.error);
|
|
975
|
-
}
|
|
976
|
-
} catch (e) {
|
|
977
|
-
alert('{{ _("Error al guardar: ") }}' + e.message);
|
|
978
|
-
}
|
|
979
|
-
}
|
|
980
|
-
|
|
981
|
-
async function testCalculation() {
|
|
982
|
-
try {
|
|
983
|
-
const testInputs = {};
|
|
984
|
-
document.querySelectorAll('.test-input').forEach(input => {
|
|
985
|
-
testInputs[input.dataset.name] = parseFloat(input.value) || 0;
|
|
986
|
-
});
|
|
987
|
-
|
|
988
|
-
const response = await fetch('{{ url_for("calculation_rule.test_schema", id=rule.id) }}', {
|
|
989
|
-
method: 'POST',
|
|
990
|
-
headers: {
|
|
991
|
-
'Content-Type': 'application/json',
|
|
992
|
-
},
|
|
993
|
-
body: JSON.stringify({
|
|
994
|
-
schema: collectSchemaFromEditor(),
|
|
995
|
-
inputs: testInputs
|
|
996
|
-
})
|
|
997
|
-
});
|
|
998
|
-
|
|
999
|
-
const result = await response.json();
|
|
1000
|
-
|
|
1001
|
-
const resultDiv = document.getElementById('test-result');
|
|
1002
|
-
const resultContent = document.getElementById('test-result-content');
|
|
1003
|
-
|
|
1004
|
-
resultDiv.style.display = 'block';
|
|
1005
|
-
|
|
1006
|
-
if (result.success) {
|
|
1007
|
-
// Format result with 2 decimal places
|
|
1008
|
-
const formattedResult = formatResultWithDecimals(result.result);
|
|
1009
|
-
resultContent.innerHTML = JSON.stringify(formattedResult, null, 2);
|
|
1010
|
-
resultContent.classList.remove('text-danger');
|
|
1011
|
-
resultContent.classList.add('text-success');
|
|
1012
|
-
} else {
|
|
1013
|
-
resultContent.innerHTML = '{{ _("Error: ") }}' + result.error;
|
|
1014
|
-
resultContent.classList.remove('text-success');
|
|
1015
|
-
resultContent.classList.add('text-danger');
|
|
1016
|
-
}
|
|
1017
|
-
} catch (e) {
|
|
1018
|
-
alert('{{ _("Error al probar: ") }}' + e.message);
|
|
1019
|
-
}
|
|
1020
|
-
}
|
|
1021
|
-
|
|
1022
|
-
function formatResultWithDecimals(obj) {
|
|
1023
|
-
if (typeof obj === 'number') {
|
|
1024
|
-
return parseFloat(obj.toFixed(2));
|
|
1025
|
-
} else if (typeof obj === 'object' && obj !== null) {
|
|
1026
|
-
if (Array.isArray(obj)) {
|
|
1027
|
-
return obj.map(item => formatResultWithDecimals(item));
|
|
1028
|
-
} else {
|
|
1029
|
-
const formatted = {};
|
|
1030
|
-
for (const key in obj) {
|
|
1031
|
-
formatted[key] = formatResultWithDecimals(obj[key]);
|
|
1032
|
-
}
|
|
1033
|
-
return formatted;
|
|
1034
|
-
}
|
|
1035
|
-
}
|
|
1036
|
-
return obj;
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
|
-
async function loadJsonFile(event) {
|
|
1040
|
-
const file = event.target.files[0];
|
|
1041
|
-
if (!file) return;
|
|
1042
|
-
|
|
1043
|
-
// Check file type
|
|
1044
|
-
if (!file.name.endsWith('.json')) {
|
|
1045
|
-
alert('{{ _("Por favor seleccione un archivo JSON válido") }}');
|
|
1046
|
-
event.target.value = '';
|
|
1047
|
-
return;
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
try {
|
|
1051
|
-
const text = await file.text();
|
|
1052
|
-
let jsonData;
|
|
1053
|
-
|
|
1054
|
-
// Parse JSON
|
|
1055
|
-
try {
|
|
1056
|
-
jsonData = JSON.parse(text);
|
|
1057
|
-
} catch (e) {
|
|
1058
|
-
alert('{{ _("Error: El archivo no contiene JSON válido") }}\n' + e.message);
|
|
1059
|
-
event.target.value = '';
|
|
1060
|
-
return;
|
|
1061
|
-
}
|
|
1062
|
-
|
|
1063
|
-
// Validate schema structure before loading
|
|
1064
|
-
const validationResult = await validateJsonSchema(jsonData);
|
|
1065
|
-
|
|
1066
|
-
if (!validationResult.valid) {
|
|
1067
|
-
alert('{{ _("Error de validación") }}:\n\n' + validationResult.error);
|
|
1068
|
-
event.target.value = '';
|
|
1069
|
-
return;
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
// Show confirmation dialog
|
|
1073
|
-
if (confirm('{{ _("¿Cargar este esquema? Esto reemplazará el esquema actual.") }}')) {
|
|
1074
|
-
schema = jsonData;
|
|
1075
|
-
loadSchemaToEditor();
|
|
1076
|
-
updateJsonPreview();
|
|
1077
|
-
alert('{{ _("Esquema cargado exitosamente") }}');
|
|
1078
|
-
}
|
|
1079
|
-
|
|
1080
|
-
} catch (e) {
|
|
1081
|
-
alert('{{ _("Error al leer el archivo") }}: ' + e.message);
|
|
1082
|
-
} finally {
|
|
1083
|
-
event.target.value = '';
|
|
1084
|
-
}
|
|
1085
|
-
}
|
|
1086
|
-
|
|
1087
|
-
async function validateJsonSchema(jsonData) {
|
|
1088
|
-
// Basic structure validation
|
|
1089
|
-
if (!jsonData || typeof jsonData !== 'object') {
|
|
1090
|
-
return { valid: false, error: '{{ _("El esquema debe ser un objeto JSON") }}' };
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
// Check for required sections
|
|
1094
|
-
if (!jsonData.steps || !Array.isArray(jsonData.steps)) {
|
|
1095
|
-
return { valid: false, error: '{{ _("El esquema debe contener una sección \'steps\' con un array de pasos") }}' };
|
|
1096
|
-
}
|
|
1097
|
-
|
|
1098
|
-
// Validate steps structure
|
|
1099
|
-
for (let i = 0; i < jsonData.steps.length; i++) {
|
|
1100
|
-
const step = jsonData.steps[i];
|
|
1101
|
-
if (!step.name) {
|
|
1102
|
-
return { valid: false, error: `{{ _("El paso") }} ${i + 1} {{ _("debe tener un campo \'name\'") }}` };
|
|
1103
|
-
}
|
|
1104
|
-
if (!step.type) {
|
|
1105
|
-
return { valid: false, error: `{{ _("El paso") }} ${i + 1} {{ _("debe tener un campo \'type\'") }}` };
|
|
1106
|
-
}
|
|
1107
|
-
|
|
1108
|
-
// Validate step type
|
|
1109
|
-
const validTypes = ['calculation', 'conditional', 'tax_lookup', 'assignment'];
|
|
1110
|
-
if (!validTypes.includes(step.type)) {
|
|
1111
|
-
return {
|
|
1112
|
-
valid: false,
|
|
1113
|
-
error: `{{ _("El paso") }} ${i + 1} {{ _("tiene un tipo inválido") }}: '${step.type}'. {{ _("Tipos permitidos") }}: ${validTypes.join(', ')}`
|
|
1114
|
-
};
|
|
1115
|
-
}
|
|
1116
|
-
|
|
1117
|
-
// Validate step-specific fields
|
|
1118
|
-
if (step.type === 'calculation' && !step.formula) {
|
|
1119
|
-
return { valid: false, error: `{{ _("El paso de cálculo") }} '${step.name}' {{ _("debe tener un campo \'formula\'") }}` };
|
|
1120
|
-
}
|
|
1121
|
-
if (step.type === 'conditional' && !step.condition) {
|
|
1122
|
-
return { valid: false, error: `{{ _("El paso condicional") }} '${step.name}' {{ _("debe tener un campo \'condition\'") }}` };
|
|
1123
|
-
}
|
|
1124
|
-
if (step.type === 'tax_lookup' && (!step.table || !step.input)) {
|
|
1125
|
-
return { valid: false, error: `{{ _("El paso de búsqueda en tabla") }} '${step.name}' {{ _("debe tener campos \'table\' e \'input\'") }}` };
|
|
1126
|
-
}
|
|
1127
|
-
if (step.type === 'assignment' && step.value === undefined) {
|
|
1128
|
-
return { valid: false, error: `{{ _("El paso de asignación") }} '${step.name}' {{ _("debe tener un campo \'value\'") }}` };
|
|
1129
|
-
}
|
|
1130
|
-
}
|
|
1131
|
-
|
|
1132
|
-
// Validate with backend FormulaEngine
|
|
1133
|
-
try {
|
|
1134
|
-
const response = await fetch('{{ url_for("calculation_rule.validate_schema_api", id=rule.id) }}', {
|
|
1135
|
-
method: 'POST',
|
|
1136
|
-
headers: {
|
|
1137
|
-
'Content-Type': 'application/json',
|
|
1138
|
-
},
|
|
1139
|
-
body: JSON.stringify({ schema: jsonData })
|
|
1140
|
-
});
|
|
1141
|
-
|
|
1142
|
-
const result = await response.json();
|
|
1143
|
-
|
|
1144
|
-
if (!result.success) {
|
|
1145
|
-
return { valid: false, error: result.error };
|
|
1146
|
-
}
|
|
1147
|
-
|
|
1148
|
-
return { valid: true };
|
|
1149
|
-
} catch (e) {
|
|
1150
|
-
// If backend validation fails, still allow if basic validation passed
|
|
1151
|
-
console.warn('Backend validation failed:', e);
|
|
1152
|
-
return { valid: true };
|
|
1153
|
-
}
|
|
1154
|
-
}
|
|
470
|
+
initializeSortable();
|
|
471
|
+
});
|
|
1155
472
|
</script>
|
|
473
|
+
<script src="{{ url_for('static', filename='schema_editor.js') }}"></script>
|
|
1156
474
|
|
|
1157
475
|
<!-- Include SortableJS library -->
|
|
1158
476
|
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.0/Sortable.min.js"></script>
|