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/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/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/part-number-dialog.svelte b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/part-number-dialog.svelte index c60d96a6..f15141ff 100644 --- a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/part-number-dialog.svelte +++ b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/part-number-dialog.svelte @@ -3,10 +3,11 @@ 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, Package } from 'lucide-svelte'; - import { toast } from 'svelte-sonner'; + import { Search, Loader2, Info, Package, AlertCircle } from 'lucide-svelte'; import { companyStore } from '$lib/stores/company.svelte'; + let partsLoadError = $state(''); + let { open = $bindable(false), onSelect @@ -62,7 +63,7 @@ currentPage = page; } catch (error) { console.error('Error fetching parts:', error); - toast.error('Error al cargar números de parte'); + partsLoadError = 'No se pudieron cargar los números de parte. Intenta de nuevo.'; } finally { isSearching = false; isLoadingMore = false; @@ -131,6 +132,22 @@ + {#if partsLoadError} + + {/if} +
diff --git a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/tab-series.svelte b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/tab-series.svelte index 5ede683c..1cf364f5 100644 --- a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/tab-series.svelte +++ b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/tab-series.svelte @@ -4,12 +4,14 @@ import { Checkbox } from '$lib/components/ui/checkbox'; import { Label } from '$lib/components/ui/label'; import { Button } from '$lib/components/ui/button'; - import { Plus, Pencil, Trash2, CheckCircle2, AlertTriangle } from 'lucide-svelte'; + import { Plus, Pencil, Trash2, CheckCircle2, AlertTriangle, AlertCircle } from 'lucide-svelte'; import { itemsApi, type Item, type LineDescriptions, type Serie } from '$lib/api/dashboard/a76/items'; import type { Invoice } from '$lib/api/dashboard/a76/invoices'; import { companyStore } from '$lib/stores/company.svelte'; import { toast } from 'svelte-sonner'; + let seriesErrorMessage = $state(''); + let { descriptions = $bindable(), series = $bindable(), @@ -93,7 +95,7 @@ selectedSeriesIndex = null; } catch (err) { console.error('Error clearing series:', err); - toast.error('Error al intentar borrar las series.'); + seriesErrorMessage = 'No se pudieron borrar las series. Intenta de nuevo.'; } } @@ -171,6 +173,22 @@
Series + {#if seriesErrorMessage} + + {/if} +
+ import { AlertCircle, AlertTriangle, ChevronDown, ChevronUp, X } from 'lucide-svelte'; + import { cn } from '$lib/utils'; + + export type ErrorPanelNoticeRow = { + field: string; + message: string; + }; + + export type ErrorPanelNoticeVariant = 'error' | 'warning'; + + export type ErrorPanelNoticeLabels = { + clear: string; + columnType: string; + columnField: string; + columnMessage: string; + emptyField: string; + dismissRowAria: string; + toggleDetailsAria: string; + }; + + let { + open, + variant = 'error', + title, + rows = [], + durationMs = 12000, + labels, + dismissibleRows = true, + class: className = '', + onClose, + onDismissRow + }: { + open: boolean; + variant?: ErrorPanelNoticeVariant; + title: string; + rows: ErrorPanelNoticeRow[]; + durationMs?: number; + labels: ErrorPanelNoticeLabels; + dismissibleRows?: boolean; + class?: string; + onClose?: () => void; + onDismissRow?: (index: number) => void; + } = $props(); + + let expanded = $state(true); + + const isWarning = $derived(variant === 'warning'); + + /** Re-expand the table when the panel opens or when errors/warnings change (new batch). */ + $effect(() => { + if (!open) return; + void title; + void variant; + void JSON.stringify(rows); + expanded = true; + }); + + $effect(() => { + if (!open || durationMs <= 0) return; + void rows.length; + const t = setTimeout(() => onClose?.(), durationMs); + return () => clearTimeout(t); + }); + + function toggleExpanded() { + expanded = !expanded; + } + + +{#if open} +
+ +
+ +

{title}

+ + + + +
+ + {#if expanded && rows.length > 0} +
+ + + + + + + {#if dismissibleRows && onDismissRow} + + {/if} + + + + {#each rows as row, i (i)} + + + + + {#if dismissibleRows && onDismissRow} + + {/if} + + {/each} + +
+ {labels.columnType} + + {labels.columnField} + + {labels.columnMessage} +
+ + + {row.field || labels.emptyField} + + {row.message} + + +
+
+ {/if} +
+{/if} diff --git a/frontend/src/routes/dashboard/+page.svelte b/frontend/src/routes/dashboard/+page.svelte index 7922ab1b..c49e42ca 100644 --- a/frontend/src/routes/dashboard/+page.svelte +++ b/frontend/src/routes/dashboard/+page.svelte @@ -144,7 +144,7 @@ ├───────────────┬────────┴─┬───────────────┤ row 3 │ Clientes (4) │Proveed(4) │ Accesos (4) │ ├────────────────────────┬─────────────────┤ row 4 - │ ActivityFeed (8, ×3) │ Documentos (4) │ + │ ActivityFeed (8, x3) │ Documentos (4) │ │ ├─────────────────┤ row 5 │ │ Contactos (4) │ │ ├─────────────────┤ row 6 diff --git a/frontend/src/routes/dashboard/goods/fda-codes/+page.svelte b/frontend/src/routes/dashboard/goods/fda-codes/+page.svelte index d386ed83..972209a8 100644 --- a/frontend/src/routes/dashboard/goods/fda-codes/+page.svelte +++ b/frontend/src/routes/dashboard/goods/fda-codes/+page.svelte @@ -19,16 +19,13 @@ Globe, Box, CheckSquare, + Square, AlertCircle } from 'lucide-svelte'; import { toast } from 'svelte-sonner'; import { Checkbox } from '$lib/components/ui/checkbox'; import { cn } from '$lib/utils'; - // Importaciones de Seguridad y UI de Errores - import { currentUser, userHasPermission } from '$lib/auth'; - import ErrorState from '$lib/components/dashboard/common/error-state.svelte'; - let fdaList = $state([]); let loading = $state(false); let selectedRows = $state([]); @@ -36,18 +33,6 @@ let searchQuery = $state(''); let hoveredRow = $state(null); - // Estados de error - let error = $state(null); - let status = $state(200); - - // 🛡️ Permisos (Usando good_fda) - const canView = $derived(userHasPermission($currentUser, 'goods_fda.view')); - const canCreate = $derived(userHasPermission($currentUser, 'goods_fda.create')); - const canEdit = $derived(userHasPermission($currentUser, 'goods_fda.edit')); - const canDelete = $derived(userHasPermission($currentUser, 'goods_fda.delete')); - - const isError = $derived(!canView || status >= 400 || error); - onMount(() => { mounted = true; }); @@ -74,8 +59,6 @@ ); async function loadAllFdaData() { - if (!canView) return; // Bloqueo si no hay permiso - const companyId = companyStore.activeCompany?.id; if (!companyId) return; @@ -86,35 +69,24 @@ const data = await res.json(); fdaList = data.items || []; selectedRows = []; - status = 200; - error = null; - } else { - const errData = await res.json(); - error = errData.error || 'Error al cargar los datos'; - status = res.status || 500; } - } catch (err: any) { - console.error('Error cargando lista de FDA:', err); - error = 'Error de conexión con el servidor'; - status = 500; + } catch (error) { + console.error('Error cargando lista de FDA:', error); } finally { loading = false; } } function handleCreateClick() { - if (!canCreate) return; goto('/dashboard/goods/fda-codes/edit/new'); } function handleEditClick(fdaId: number) { - if (!canEdit) return; goto(`/dashboard/goods/fda-codes/edit/${fdaId}`); } async function deleteFdaCode(id: number, event?: MouseEvent) { event?.stopPropagation(); - if (!canDelete) return; // Bloqueo si no hay permiso if (!confirm('¿Está seguro de eliminar este código FDA?')) return; const companyId = companyStore.activeCompany?.id; @@ -129,8 +101,8 @@ selectedRows = selectedRows.filter((r) => r !== id); fdaList = fdaList.filter((f) => f.id !== id); } else { - const errData = await res.json(); - toast.error(errData.error || 'Error al eliminar'); + const error = await res.json(); + toast.error(error.error || 'Error al eliminar'); } } catch (err) { console.error('Error:', err); @@ -155,7 +127,8 @@ } async function handleDeleteSelected() { - if (!canDelete || selectedRows.length === 0) return; + if (selectedRows.length === 0) return; + if (!confirm(`¿Está seguro de eliminar ${selectedRows.length} código(s) FDA?`)) return; for (const id of selectedRows) { @@ -164,7 +137,7 @@ } function handleEditSelected() { - if (!canEdit || selectedRows.length !== 1) return; + if (selectedRows.length !== 1) return; const id = selectedRows[0]; handleEditClick(id); } @@ -173,6 +146,7 @@ selectedRows = []; } + // Obtener badge de estado de almacenaje function getStorageBadge(status: string) { const styles: Record = { ACTIVO: 'bg-green-100 text-green-700 border-green-200', @@ -184,6 +158,7 @@
+
@@ -209,305 +184,286 @@ - - {#if !isError && canCreate} - - {/if} +
+
- {#if isError} - - {:else} - -
-
-
-
- -
-
-

Registros FDA

-

- {filteredFdaList.length} de {fdaList.length} registros -

-
+ + +
+
+
+
+
- -
- - +
+

Registros FDA

+

+ {filteredFdaList.length} de {fdaList.length} registros +

- {#if selectedRows.length > 0} -
-
- - - {selectedRows.length} - {selectedRows.length === 1 ? 'registro seleccionado' : 'registros seleccionados'} - -
- -
- {/if} +
+ + +
-
- - - - - 0 && - selectedRows.length === filteredFdaList.length} - indeterminate={selectedRows.length > 0 && - selectedRows.length < filteredFdaList.length} - onCheckedChange={toggleAllSelection} - aria-label="Seleccionar todos" - /> - - Clave FDA - Descripción - Código FDA - Fabricante - País - Estado + + {#if selectedRows.length > 0} +
+
+ + + {selectedRows.length} + {selectedRows.length === 1 ? 'registro seleccionado' : 'registros seleccionados'} + +
+ +
+ {/if} +
+ + +
+ + + + + 0 && + selectedRows.length === filteredFdaList.length} + indeterminate={selectedRows.length > 0 && + selectedRows.length < filteredFdaList.length} + onCheckedChange={toggleAllSelection} + aria-label="Seleccionar todos" + /> + + Clave FDA + Descripción + Código FDA + Fabricante + País + Estado + + + + {#if loading} + + +
+
+ +
+
+

Cargando registros...

+

Por favor espere

+
+
+
- - - {#if loading} - - + {:else if filteredFdaList.length === 0} + + + {#if searchQuery}
-
- +
+
-

Cargando registros...

-

Por favor espere

+

No se encontraron resultados

+

Intenta con otra búsqueda

+
+ +
+ {:else} +
+
+ +
+
+

No hay registros FDA

+

+ No se encontraron registros para esta compañía +

+ {/if} + + + {:else} + {#each filteredFdaList as fda, i (fda.id)} + (hoveredRow = fda.id)} + onmouseleave={() => (hoveredRow = null)} + onclick={() => toggleRowSelection(fda.id)} + > + e.stopPropagation()}> + toggleRowSelection(fda.id)} + aria-label="Seleccionar fila" + /> - - {:else if filteredFdaList.length === 0} - - - {#if searchQuery} -
-
- -
-
-

No se encontraron resultados

-

Intenta con otra búsqueda

-
- -
+ +
+ + {fda.fda_key} + +
+
+ +
+

+ {fda.description || '-'} +

+ {#if fda.requirements} +

+ {fda.requirements} +

+ {/if} +
+
+ + + {fda.fda_code || '-'} + + + +
+ + {fda.manufacturer_number || '-'} +
+
+ +
+ + {fda.country_of_production || '-'} +
+
+ + {#if fda.storage_status} + + {fda.storage_status} + {:else} -
-
- -
-
-

No hay registros FDA

-

- No se encontraron registros para esta compañía -

-
-
+ - {/if}
- {:else} - {#each filteredFdaList as fda, i (fda.id)} - (hoveredRow = fda.id)} - onmouseleave={() => (hoveredRow = null)} - onclick={() => toggleRowSelection(fda.id)} - > - e.stopPropagation()}> - toggleRowSelection(fda.id)} - aria-label="Seleccionar fila" - /> - - -
- - {fda.fda_key} - -
-
- -
-

- {fda.description || '-'} -

- {#if fda.requirements} -

- {fda.requirements} -

- {/if} -
-
- - - {fda.fda_code || '-'} - - - -
- - {fda.manufacturer_number || '-'} -
-
- -
- - {fda.country_of_production || '-'} -
-
- - {#if fda.storage_status} - - {fda.storage_status} - - {:else} - - - {/if} - -
- {/each} - {/if} - - -
- - {#if filteredFdaList.length > 0} -
- - Mostrando {filteredFdaList.length} registros - - {#if searchQuery} - - Filtro: "{searchQuery}" - - + {/each} {/if} -
- {/if} - - {/if} +
+
+
+ + + {#if filteredFdaList.length > 0} +
+ + Mostrando {filteredFdaList.length} registros + + {#if searchQuery} + + Filtro: "{searchQuery}" + + + {/if} +
+ {/if} +
- {#if !isError} -
-
-
-
- {#if selectedRows.length > 0} -
- - - {selectedRows.length} seleccionado(s) - -
- {:else} -
- - Selecciona registros para editar o eliminar -
- {/if} -
+ +
+
+
+ +
+ {#if selectedRows.length > 0} +
+ + + {selectedRows.length} seleccionado(s) + +
+ {:else} +
+ + Selecciona registros para editar o eliminar +
+ {/if} +
-
- {#if canCreate} - - {/if} - - {#if canEdit} - - {/if} - - {#if canDelete} - - {/if} -
+ +
+ + +
- {/if} +