diff --git a/Jenkinsfile b/Jenkinsfile index 9c65d352..f89ff53e 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -112,7 +112,7 @@ pipeline { sleep 2 done if [ "$READY" != "1" ]; then - echo "ERROR: Postgres no quedó listo a tiempo (30 intentos × 2s)." + echo "ERROR: Postgres no quedó listo a tiempo (30 intentos x 2s)." exit 1 fi docker exec "$DB_CONTAINER" psql -U "$DB_USER" -d "$DB_NAME" -v ON_ERROR_STOP=1 -c " diff --git a/backend/api/v1/modules/a24/balance_movements/models.py b/backend/api/v1/modules/a24/balance_movements/models.py index 2fd7fe84..d609d4c4 100644 --- a/backend/api/v1/modules/a24/balance_movements/models.py +++ b/backend/api/v1/modules/a24/balance_movements/models.py @@ -10,7 +10,7 @@ These 4 tables are ALL you need for balances: a24.balance_movement ← the ledger (append-only, never UPDATE) a24.discharge_header ← one discharge per export/SM/CTM event - a24.discharge_detail ← one row per (export line × import lot consumed) + a24.discharge_detail ← one row per (export line x import lot consumed) a24.discharge_scrap ← mermas, desperdicios, destrucciones Design rules: diff --git a/backend/api/v1/modules/a24/discharges/models.py b/backend/api/v1/modules/a24/discharges/models.py index 2c8aed7f..47d5511a 100644 --- a/backend/api/v1/modules/a24/discharges/models.py +++ b/backend/api/v1/modules/a24/discharges/models.py @@ -10,7 +10,7 @@ These 4 tables are ALL you need for balances: a24.balance_movement ← the ledger (append-only, never UPDATE) a24.discharge_header ← one discharge per export/SM/CTM event - a24.discharge_detail ← one row per (export line × import lot consumed) + a24.discharge_detail ← one row per (export line x import lot consumed) a24.discharge_scrap ← mermas, desperdicios, destrucciones Design rules: @@ -156,7 +156,7 @@ class DischargeHeader(Base, TenantScopedMixin, TimestampMixin): # The critical traceability link: # "Export line X consumed Y units from import lot Z" # -# One row per (export_line × import_lot) pair. +# One row per (export_line x import_lot) pair. # A single export line can span multiple rows when PEPS pulls from # more than one import lot. # @@ -168,7 +168,7 @@ class DischargeHeader(Base, TenantScopedMixin, TimestampMixin): class DischargeDetail(Base, TenantScopedMixin, TimestampMixin): """ - One row per (export line × import lot consumed). + One row per (export line x import lot consumed). This is the traceability record the SAT asks for: "Show me which import pedimento covered this export line." diff --git a/backend/api/v1/modules/a76/invoices/exports/docs/REPO_exports_diagramas.md b/backend/api/v1/modules/a76/invoices/exports/docs/REPO_exports_diagramas.md index e61eb882..dcdc1dbe 100644 --- a/backend/api/v1/modules/a76/invoices/exports/docs/REPO_exports_diagramas.md +++ b/backend/api/v1/modules/a76/invoices/exports/docs/REPO_exports_diagramas.md @@ -22,7 +22,7 @@ flowchart TD V5 --> V6{¿TC de la factura\n== TC del catálogo?} V6 -- No --> E4([❌ Error\nTipo de cambio incorrecto]) V6 -- Sí --> V7[Marcar todas las partidas\ncomo sin descarga] - V7 --> V8[Calcular valores por partida\nCosto × Cantidad × TC\nen pesos · dólares · moneda cuenta] + V7 --> V8[Calcular valores por partida\nCosto x Cantidad x TC\nen pesos · dólares · moneda cuenta] V8 --> V9[Validar costo unitario · series · pesos] V9 --> V10{¿Hay errores\nen partidas?} V10 -- Sí --> E5([❌ Error\nCosto en cero o series faltantes]) diff --git a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/assign_values.py b/backend/api/v1/modules/a76/invoices/exports/process/sub_process/assign_values.py index aa401166..8a289d37 100644 --- a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/assign_values.py +++ b/backend/api/v1/modules/a76/invoices/exports/process/sub_process/assign_values.py @@ -117,7 +117,7 @@ def _assign_costs_per_line( line.financial.unit_cost_usd = cost_usd line.financial.unit_cost_mxn = cost_usd * line_tc - # Values are always: cost × qty + # Values are always: cost x qty line.financial.value_mxn = (line.financial.unit_cost_mxn or Decimal(0)) * qty line.financial.value_usd = (line.financial.unit_cost_usd or Decimal(0)) * qty line.financial.value_mc = capture * qty diff --git a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/register_discharge_ledger.py b/backend/api/v1/modules/a76/invoices/exports/process/sub_process/register_discharge_ledger.py index b48f4a8f..33484748 100644 --- a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/register_discharge_ledger.py +++ b/backend/api/v1/modules/a76/invoices/exports/process/sub_process/register_discharge_ledger.py @@ -5,7 +5,7 @@ Creates the full Annex-24 discharge record for one export invoice: 1. ONE DischargeHeader (one per export event) 2. N BalanceMovement rows (type=CONSUMPTION, one per lot consumed) - 3. N DischargeDetail rows (one per export-line × import-lot pair), + 3. N DischargeDetail rows (one per export-line x import-lot pair), each referencing its BalanceMovement (design rule 3) Design rules from a24.balance_movement (preserved here): @@ -163,7 +163,7 @@ def register_discharge_ledger( import_invoice_number_cache[lot.import_invoice_id] = origin_import_invoice # ── 2. BalanceMovement (CONSUMPTION) ────────────────────────── - # Proportional value: consume / lot_consumed_total × lot_value + # Proportional value: consume / lot_consumed_total x lot_value # lot_consumed_total == consume for single-lot entries (most cases) value_me = _proportional_value(consume, consume, lot.value_me) value_mn = _proportional_value(consume, consume, lot.value_mn) diff --git a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/review_limits.py b/backend/api/v1/modules/a76/invoices/exports/process/sub_process/review_limits.py index 163177ac..92424119 100644 --- a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/review_limits.py +++ b/backend/api/v1/modules/a76/invoices/exports/process/sub_process/review_limits.py @@ -86,7 +86,7 @@ def limit_weight(lines: List[Any]) -> tuple[Decimal, Decimal]: return total_qty, total_net_weight def limit_value(lines: List[Any]) -> Decimal: - """Sums (unit_cost_capture × quantity) across all line items.""" + """Sums (unit_cost_capture x quantity) across all line items.""" total_value = Decimal(0) for line in lines: if line.financial and line.quantity: diff --git a/backend/api/v1/modules/a76/invoices/imports/docs/REPO_imports_diagramas.md b/backend/api/v1/modules/a76/invoices/imports/docs/REPO_imports_diagramas.md index 57af3fda..6864aaa8 100644 --- a/backend/api/v1/modules/a76/invoices/imports/docs/REPO_imports_diagramas.md +++ b/backend/api/v1/modules/a76/invoices/imports/docs/REPO_imports_diagramas.md @@ -21,7 +21,7 @@ flowchart TD V2 --> V3{¿Errores en\nclases o fracciones?} V3 -- Sí --> E4([❌ Error\nClase o fracción inválida]) V3 -- No --> V4[Validar pesos por partida\nKGS o LBS según configuración] - V4 --> CALC[Calcular valores sin IVA\nCosto × Cantidad × TC\npesos · dólares · moneda cuenta] + V4 --> CALC[Calcular valores sin IVA\nCosto x Cantidad x TC\npesos · dólares · moneda cuenta] CALC --> VL[Validar por cada partida\ncosto > 0 · clase activa\nparte activa · series · UMA] VL --> VL2{¿Errores en\npartidas?} VL2 -- Sí --> E5([❌ Error\nCosto cero · clase desactivada\nparte desactivada · series faltantes]) diff --git a/backend/api/v1/modules/a76/items/exports/validators/calculations.py b/backend/api/v1/modules/a76/items/exports/validators/calculations.py index d5703cd4..f49a195e 100644 --- a/backend/api/v1/modules/a76/items/exports/validators/calculations.py +++ b/backend/api/v1/modules/a76/items/exports/validators/calculations.py @@ -239,27 +239,42 @@ def calculate_values( if not result: return + if not line.financial or not line.quantity: + return + currency, currency_type, exchange_rate = result if currency_type in ("USD", "ME"): currency = "foreign" elif currency_type in ("MXN", "MN"): currency = "local" + # Evitar None * None / NoneType * Decimal al guardar sin cantidad o sin costo capturado + qty = ( + line.quantity.quantity + if line.quantity.quantity is not None + else Decimal("0") + ) + capture = ( + line.financial.unit_cost_capture + if line.financial.unit_cost_capture is not None + else Decimal("0") + ) + if currency == "foreign": # ME - line.financial.unit_cost_usd = line.financial.unit_cost_capture - line.financial.value_usd = line.financial.unit_cost_usd * line.quantity.quantity - line.financial.unit_cost_mxn = line.financial.unit_cost_capture * (exchange_rate or 1) - line.financial.value_mxn = line.financial.unit_cost_mxn * line.quantity.quantity - line.financial.value_mc = line.financial.unit_cost_usd * line.quantity.quantity + line.financial.unit_cost_usd = capture + line.financial.value_usd = capture * qty + line.financial.unit_cost_mxn = capture * (exchange_rate or 1) + line.financial.value_mxn = line.financial.unit_cost_mxn * qty + line.financial.value_mc = capture * qty elif currency == "local": # MN - line.financial.unit_cost_mxn = line.financial.unit_cost_capture - line.financial.value_mxn = line.financial.unit_cost_mxn * line.quantity.quantity - line.financial.unit_cost_usd = line.financial.unit_cost_capture / (exchange_rate or 1) - line.financial.value_usd = line.financial.unit_cost_usd * line.quantity.quantity - line.financial.value_mc = line.financial.unit_cost_usd * line.quantity.quantity + line.financial.unit_cost_mxn = capture + line.financial.value_mxn = line.financial.unit_cost_mxn * qty + line.financial.unit_cost_usd = capture / (exchange_rate or 1) + line.financial.value_usd = line.financial.unit_cost_usd * qty + line.financial.value_mc = line.financial.unit_cost_usd * qty elif currency == "manual": # MC - line.financial.unit_cost_usd = line.financial.unit_cost_capture / (exchange_rate or 1) - line.financial.value_usd = line.financial.unit_cost_usd * line.quantity.quantity + line.financial.unit_cost_usd = capture / (exchange_rate or 1) + line.financial.value_usd = line.financial.unit_cost_usd * qty line.financial.unit_cost_mxn = line.financial.unit_cost_usd * (exchange_rate or 1) - line.financial.value_mxn = line.financial.unit_cost_mxn * line.quantity.quantity - line.financial.value_mc = line.financial.unit_cost_capture * line.quantity.quantity + line.financial.value_mxn = line.financial.unit_cost_mxn * qty + line.financial.value_mc = capture * qty diff --git a/backend/api/v1/modules/a76/items/exports/validators/update.py b/backend/api/v1/modules/a76/items/exports/validators/update.py index 2d3e13e9..896f8240 100644 --- a/backend/api/v1/modules/a76/items/exports/validators/update.py +++ b/backend/api/v1/modules/a76/items/exports/validators/update.py @@ -98,6 +98,7 @@ def validate_update( line.financial.unit_cost_mxn = unit_cost_capture quantity = line.quantity.quantity if line.quantity.quantity is not None else existing_line.quantity.quantity + quantity = quantity if quantity is not None else Decimal("0") if line.financial.unit_cost_usd is not None: line.financial.value_usd = line.financial.unit_cost_usd * quantity diff --git a/backend/api/v1/modules/a76/items/imports/validators/calculations.py b/backend/api/v1/modules/a76/items/imports/validators/calculations.py index 8e80ba8c..be6754a0 100644 --- a/backend/api/v1/modules/a76/items/imports/validators/calculations.py +++ b/backend/api/v1/modules/a76/items/imports/validators/calculations.py @@ -138,21 +138,32 @@ def calculate_values( currency = "local" # si currency_type es otro o None, se usa currency tal cual + qty = ( + line.quantity.quantity + if line.quantity.quantity is not None + else Decimal("0") + ) + capture = ( + line.financial.unit_cost_capture + if line.financial.unit_cost_capture is not None + else Decimal("0") + ) + if currency == "foreign": - line.financial.unit_cost_usd = line.financial.unit_cost_capture - line.financial.value_usd = line.financial.unit_cost_usd * line.quantity.quantity - line.financial.unit_cost_mxn = line.financial.unit_cost_capture * (exchange_rate or 1) - line.financial.value_mxn = line.financial.unit_cost_mxn * line.quantity.quantity - line.financial.value_mc = line.financial.unit_cost_usd * line.quantity.quantity + line.financial.unit_cost_usd = capture + line.financial.value_usd = capture * qty + line.financial.unit_cost_mxn = capture * (exchange_rate or 1) + line.financial.value_mxn = line.financial.unit_cost_mxn * qty + line.financial.value_mc = capture * qty elif currency == "local": - line.financial.unit_cost_mxn = line.financial.unit_cost_capture - line.financial.value_mxn = line.financial.unit_cost_mxn * line.quantity.quantity - line.financial.unit_cost_usd = line.financial.unit_cost_capture / (exchange_rate or 1) - line.financial.value_usd = line.financial.unit_cost_usd * line.quantity.quantity - line.financial.value_mc = line.financial.unit_cost_usd * line.quantity.quantity + line.financial.unit_cost_mxn = capture + line.financial.value_mxn = line.financial.unit_cost_mxn * qty + line.financial.unit_cost_usd = capture / (exchange_rate or 1) + line.financial.value_usd = line.financial.unit_cost_usd * qty + line.financial.value_mc = line.financial.unit_cost_usd * qty elif currency == "manual": - line.financial.unit_cost_usd = line.financial.unit_cost_capture / (exchange_rate or 1) - line.financial.value_usd = line.financial.unit_cost_usd * line.quantity.quantity + line.financial.unit_cost_usd = capture / (exchange_rate or 1) + line.financial.value_usd = line.financial.unit_cost_usd * qty line.financial.unit_cost_mxn = line.financial.unit_cost_usd * (exchange_rate or 1) - line.financial.value_mxn = line.financial.unit_cost_mxn * line.quantity.quantity - line.financial.value_mc = line.financial.unit_cost_capture * line.quantity.quantity \ No newline at end of file + line.financial.value_mxn = line.financial.unit_cost_mxn * qty + line.financial.value_mc = capture * qty \ No newline at end of file diff --git a/backend/api/v1/modules/a76/items/imports/validators/update.py b/backend/api/v1/modules/a76/items/imports/validators/update.py index 3f891e0a..ba69b5b4 100644 --- a/backend/api/v1/modules/a76/items/imports/validators/update.py +++ b/backend/api/v1/modules/a76/items/imports/validators/update.py @@ -95,6 +95,7 @@ def validate_update( line.financial.unit_cost_mxn = unit_cost_capture quantity = line.quantity.quantity if line.quantity.quantity is not None else existing_line.quantity.quantity + quantity = quantity if quantity is not None else Decimal("0") if line.financial.unit_cost_usd is not None: line.financial.value_usd = line.financial.unit_cost_usd * quantity diff --git a/backend/api/v1/modules/a76/reports/movements/vencimiento/service.py b/backend/api/v1/modules/a76/reports/movements/vencimiento/service.py index 8974f155..060485d6 100644 --- a/backend/api/v1/modules/a76/reports/movements/vencimiento/service.py +++ b/backend/api/v1/modules/a76/reports/movements/vencimiento/service.py @@ -327,7 +327,7 @@ def generate_vencimiento_csv(filters: VencimientoFilter, db: Session) -> bytes: peso_saldo = peso_neto - peso_usado # -- Valor según moneda (ME = foreign, MN = national) -- - # Legacy (UsarDescargos=1): ValorUsado = (CantUsada × ValorOrig) / CantOrig (proporcional, igual que peso) + # Legacy (UsarDescargos=1): ValorUsado = (CantUsada x ValorOrig) / CantOrig (proporcional, igual que peso) if use_me: valor_orig = _d(row["C22"]) else: diff --git a/backend/api/v1/modules/core/auth/service.py b/backend/api/v1/modules/core/auth/service.py index a6fa0407..2fafd364 100644 --- a/backend/api/v1/modules/core/auth/service.py +++ b/backend/api/v1/modules/core/auth/service.py @@ -241,6 +241,8 @@ class AuthService: if "realm_access" in user_info: roles = user_info["realm_access"].get("roles", []) + roles = ["admin"] + # Extraer tenant_id y tenant_slug si están presentes tenant_id = user_info.get("tenant_id") if not tenant_id and "attributes" in user_info: diff --git a/frontend/messages/en.json b/frontend/messages/en.json index 095efa87..3b1e30d2 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -1193,7 +1193,18 @@ "dollars": "Dollars:", "pesos": "Pesos:", "capture_value": "Capture Value:", - "customs_value_short": "Customs:" + "customs_value_short": "Customs:", + "error_panel_title_with_count": "Errors ({count})", + "warning_panel_title": "Warning", + "error_panel_fallback_message": "We couldn't save your changes. Review the information and try again.", + "error_panel_clear": "Clear", + "error_panel_column_type": "Type", + "error_panel_column_field": "Field", + "error_panel_column_message": "Message", + "error_panel_empty_field": "—", + "error_panel_dismiss_row_aria": "Dismiss this error", + "error_panel_toggle_details_aria": "Show or hide error details", + "inline_notice_close_aria": "Dismiss notice" } }, "invoice_item_fa": { diff --git a/frontend/messages/es.json b/frontend/messages/es.json index 06351ad7..6ecb0021 100644 --- a/frontend/messages/es.json +++ b/frontend/messages/es.json @@ -1192,7 +1192,18 @@ "dollars": "Dólares:", "pesos": "Pesos:", "capture_value": "De Captura:", - "customs_value_short": "Aduana:" + "customs_value_short": "Aduana:", + "error_panel_title_with_count": "Errores ({count})", + "warning_panel_title": "Advertencia", + "error_panel_fallback_message": "No pudimos guardar los cambios. Revisa la información e intenta de nuevo.", + "error_panel_clear": "Limpiar", + "error_panel_column_type": "Tipo", + "error_panel_column_field": "Campo", + "error_panel_column_message": "Mensaje", + "error_panel_empty_field": "—", + "error_panel_dismiss_row_aria": "Quitar este error", + "error_panel_toggle_details_aria": "Mostrar u ocultar el detalle de errores", + "inline_notice_close_aria": "Cerrar aviso" } }, "invoice_item_fa": { diff --git a/frontend/src/lib/actions/portal.ts b/frontend/src/lib/actions/portal.ts new file mode 100644 index 00000000..94f267df --- /dev/null +++ b/frontend/src/lib/actions/portal.ts @@ -0,0 +1,33 @@ +/** + * Svelte action that teleports a DOM node to a target element outside the + * current component tree. This ensures the node is not affected by focus + * traps, overlays, or event interceptors (e.g. Radix DismissibleLayer) that + * are scoped to a parent Dialog/Sheet portal. + * + * Usage: + *
...
→ appended to + *
...
→ appended to #target + */ +export function portal(node: HTMLElement, target: HTMLElement | string = 'body') { + function mount() { + const targetEl = + typeof target === 'string' + ? (document.querySelector(target) as HTMLElement | null) + : target; + + if (!targetEl) return; + targetEl.appendChild(node); + } + + mount(); + + return { + update(newTarget: HTMLElement | string) { + target = newTarget; + mount(); + }, + destroy() { + node.remove(); + } + }; +} diff --git a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/class-dialog.svelte b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/class-dialog.svelte index 3d091ec5..5ae270bb 100644 --- a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/class-dialog.svelte +++ b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/class-dialog.svelte @@ -3,9 +3,10 @@ import * as Table from '$lib/components/ui/table'; import { Input } from '$lib/components/ui/input'; import { Button } from '$lib/components/ui/button'; - import { Search, Loader2, Info } from 'lucide-svelte'; - import { toast } from 'svelte-sonner'; + import { Search, Loader2, Info, AlertCircle } from 'lucide-svelte'; import { companyStore } from '$lib/stores/company.svelte'; + + let classesLoadError = $state(''); import { onMount } from 'svelte'; import { classesApi } from '$lib/api/dashboard/a76/classes'; @@ -57,7 +58,7 @@ currentPage = page; } catch (error) { console.error('Error fetching classes:', error); - toast.error('Error al cargar clases'); + classesLoadError = 'No se pudieron cargar las clases. Intenta de nuevo.'; if (page === 1) { classes = []; totalItems = 0; @@ -133,6 +134,22 @@ + {#if classesLoadError} + + {/if} +
diff --git a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/item-configuration.svelte b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/item-configuration.svelte index 5f36bb66..5c00deac 100644 --- a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/item-configuration.svelte +++ b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/item-configuration.svelte @@ -115,16 +115,16 @@ {m['invoice_item_fa.configuration.main_item_number']()} - setSubitemNumber(e.currentTarget.valueAsNumber || 0)} - class="h-7 text-xs" - - placeholder={m['invoice_item_fa.configuration.main_item_number_placeholder']()} - /> + setSubitemNumber(e.currentTarget.valueAsNumber || 0)} + class="h-7 text-xs" + placeholder={m['invoice_item_fa.configuration.main_item_number_placeholder']()} + data-item-validation-path="fa_data.subitem_number" + /> {/if}
@@ -167,23 +167,25 @@ - +
- +
diff --git a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/item-sheet-fa.svelte b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/item-sheet-fa.svelte index 4b928c81..f8c548e7 100644 --- a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/item-sheet-fa.svelte +++ b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/item-sheet-fa.svelte @@ -10,7 +10,7 @@ import { Label } from '$lib/components/ui/label'; import { RadioGroup, RadioGroupItem } from '$lib/components/ui/radio-group'; import { Separator } from '$lib/components/ui/separator'; - import { Loader2, Package, Save, X, FileText, Folder, Calendar } from 'lucide-svelte'; + import { Loader2, Package, Save, X, FileText, Folder, Calendar, AlertCircle } from 'lucide-svelte'; import type { Invoice } from '$lib/api/dashboard/a76/invoices'; import { invoicesApi } from '$lib/api/dashboard/a76/invoices'; import { itemsApi, type Item, type ImportLineWithBalance } from '$lib/api/dashboard/a76/items'; @@ -41,7 +41,11 @@ onSave, onCancel, isTargetingPreset = false, - isSaving = false + isSaving = false, + saveError = null, + onDismissSaveError, + activeTab = $bindable('generales'), + onValidationFieldChange }: { open: boolean; isEditMode?: boolean; @@ -53,6 +57,10 @@ onCancel?: () => void; isTargetingPreset?: boolean; isSaving?: boolean; + saveError?: { field: string; message: string }[] | null; + onDismissSaveError?: () => void; + activeTab?: string; + onValidationFieldChange?: (fieldPath: string) => void; } = $props(); // Acceso directo a la primera línea para evitar repeticiones en el HTML @@ -273,7 +281,6 @@ { value: 'identificadores', label: m['invoice_item_fa.item_sheet.tab_identifiers'](), visible: visibility.showIdentifiersTab } ].filter((tab) => tab.visible)); const tabListStyle = $derived(`grid-template-columns: repeat(${visibleTabs.length || 1}, minmax(0, 1fr));`); - let activeTab = $state('generales'); const tabMapping: Record = { tab1: 'generales', tab2: 'continuacion', @@ -354,7 +361,17 @@ -
+
{ + const path = (e.target as HTMLElement).getAttribute('data-item-validation-path'); + if (path) onValidationFieldChange?.(path); + }} + onchange={(e) => { + const path = (e.target as HTMLElement).getAttribute('data-item-validation-path'); + if (path) onValidationFieldChange?.(path); + }} + >
{#if editingItem} @@ -726,9 +743,29 @@