From 6a9f69e39ebc1f1e371f45b4bdafd19e4c549f55 Mon Sep 17 00:00:00 2001 From: Kevin_Ramirez Date: Mon, 22 Dec 2025 11:13:35 -0600 Subject: [PATCH] Se termino de agregar los formularios modales a los catalogos que faltaba --- .../a76/general_catalogs/doda/models.py | 8 +- .../general_catalogs/prevalidators/models.py | 2 + .../dashboard/a76/general_catalogs/doda.ts | 182 ++++++--- .../general_catalogs/electronic-notices.ts | 85 +++-- .../a76/general_catalogs/prevalidators.ts | 70 ++-- .../exchange-rate/create-edit-dialog.svelte | 154 +++----- .../doda/create-edit-dialog.svelte | 265 +++++++++++++ .../create-edit-dialog.svelte | 209 +++++++++++ .../create-edite-dialoge.svelte | 276 +++++++------- .../inpc/create-edite-dialoge.svelte | 1 - .../locations/create-edite-dialog.svelte | 244 ++++++------ .../prevalidators/create-edit-dialog.svelte | 200 ++++++++++ .../general_catalogs/doda/+page.svelte | 6 + .../electronic_notices/+page.svelte | 7 + .../error_catalogs/+page.svelte | 3 +- .../exchange-rate/+page.svelte | 353 ++++++++---------- .../exchange-rate/new/+page.svelte | 140 +++++++ .../prevalidators/+page.svelte | 7 + 18 files changed, 1570 insertions(+), 642 deletions(-) create mode 100644 frontend/src/lib/components/dashboard/general_catalogs/doda/create-edit-dialog.svelte create mode 100644 frontend/src/lib/components/dashboard/general_catalogs/electronic-notices/create-edit-dialog.svelte create mode 100644 frontend/src/lib/components/dashboard/general_catalogs/prevalidators/create-edit-dialog.svelte create mode 100644 frontend/src/routes/dashboard/general_catalogs/exchange-rate/new/+page.svelte diff --git a/backend/api/v1/modules/a76/general_catalogs/doda/models.py b/backend/api/v1/modules/a76/general_catalogs/doda/models.py index e61b2918..810d4977 100644 --- a/backend/api/v1/modules/a76/general_catalogs/doda/models.py +++ b/backend/api/v1/modules/a76/general_catalogs/doda/models.py @@ -151,19 +151,19 @@ class DodaContainerSeal(Base, TenantScopedMixin, TimestampMixin): {"schema": "a76"}, ) - # Primary key + id: Mapped[int] = mapped_column( Integer, primary_key=True, autoincrement=True) - # Foreign key and line info + container_id: Mapped[int] = mapped_column(Integer, nullable=False) doda_id: Mapped[int] = mapped_column(Integer, nullable=False) seal_line: Mapped[int] = mapped_column(Integer, nullable=False) - # Seal information + seal_value: Mapped[Optional[str]] = mapped_column(String(21)) - # Relationships + container: Mapped["DodaContainer"] = relationship( "DodaContainer", back_populates="seals_detail" ) diff --git a/backend/api/v1/modules/a76/general_catalogs/prevalidators/models.py b/backend/api/v1/modules/a76/general_catalogs/prevalidators/models.py index aab8dc00..a9d0f2b8 100644 --- a/backend/api/v1/modules/a76/general_catalogs/prevalidators/models.py +++ b/backend/api/v1/modules/a76/general_catalogs/prevalidators/models.py @@ -31,7 +31,9 @@ class Prevalidator(Base, TenantScopedMixin, TimestampMixin): # Prevalidator information customs_prevalidator: Mapped[Optional[str]] = mapped_column(String(20)) + patent_prevalidator: Mapped[Optional[str]] = mapped_column(String(20)) + description: Mapped[Optional[str]] = mapped_column(String(50)) def __repr__(self): diff --git a/frontend/src/lib/api/dashboard/a76/general_catalogs/doda.ts b/frontend/src/lib/api/dashboard/a76/general_catalogs/doda.ts index 74ce4983..a6bf6a9c 100644 --- a/frontend/src/lib/api/dashboard/a76/general_catalogs/doda.ts +++ b/frontend/src/lib/api/dashboard/a76/general_catalogs/doda.ts @@ -1,61 +1,157 @@ import { api } from '$lib/api'; import type { ApiResponse } from '$lib/api'; -export interface DODA { - id: number; - code: string; - description?: string; - tenant_id: string; - company_id?: string; - created_at: string; - updated_at?: string; + +export interface DodaContainerSeal { + id: number; + doda_sys_id: number; + seal_line: number; + seal_value?: string; } -export interface DODACreate { - code: string; - description?: string; +export interface DodaContainerSealCreate { + seal_value?: string; } -export interface DODAUpdate extends Partial {} - -export interface DODAListResponse { - items: DODA[]; - total: number; - page: number; - page_size: number; - pages: number; +export interface DodaContainer { + id: number; + doda_sys_id: number; + container_line: number; + container_value?: string; + seals?: string; + seals_detail?: DodaContainerSeal[]; } -export async function getDODAs( - page: number = 1, - pageSize: number = 50, - filters: Record = {} -): Promise> { - const params = new URLSearchParams({ - page: page.toString(), - page_size: pageSize.toString(), - ...filters - }); - - const response = await api.get(`/a76/doda?${params.toString()}`); - return response.data; +export interface DodaContainerCreate { + container_value?: string; + seals?: string; + seals_detail?: DodaContainerSealCreate[]; } -export async function getDODA(id: number): Promise { - const response = await api.get(`/a76/doda/${id}`); - return response.data; + +export interface DodaAmericanPedimento { + id: number; + doda_sys_id: number; + american_pedimento_line: number; + american_pedimento_type?: string; + american_pedimento_value?: string; } -export async function createDODA(data: DODACreate): Promise { - const response = await api.post('/a76/doda', data); - return response.data; +export interface DodaAmericanPedimentoCreate { + american_pedimento_type?: string; + american_pedimento_value?: string; } -export async function updateDODA(id: number, data: DODAUpdate): Promise { - const response = await api.patch(`/a76/doda/${id}`, data); - return response.data; +export interface DodaPedimento { + id: number; + doda_sys_id: number; + pedimento_line: number; + authorization_patent?: string; + document?: string; + shipment?: string; + cove?: string; + umc?: string; + effective_amount_usd?: number; + difference_amount_usd?: number; + dta_niu?: string; + article_7?: boolean; + pedimento_sys_id?: number; + invoice_line?: number; + part_ii_line?: number; + pedimento_type?: string; + zero_packaging_validation?: boolean; } -export async function deleteDODA(id: number): Promise { - await api.delete(`/a76/doda/${id}`); +export interface DodaPedimentoCreate { + authorization_patent?: string; + document?: string; + shipment?: string; + + effective_amount_usd?: number; } + + +export interface Doda { + + sys_id: number; + + integration_number?: string; + doda_date?: number; + doda_time?: number; + dispatch_customs?: string; + customs_sections?: string; + patent?: string; + pedimentos?: string; + caat?: string; + transport_identification?: string; + fast_id?: string; + operation_type?: string; + status?: string; + + + containers?: DodaContainer[]; + american_pedimentos?: DodaAmericanPedimento[]; + pedimentos_detail?: DodaPedimento[]; + + + tenant_id?: string; + created_at?: string; + updated_at?: string; +} + + +export interface DodaCreate { + integration_number?: string; + doda_date?: number; + doda_time?: number; + dispatch_customs?: string; + customs_sections?: string; + patent?: string; + caat?: string; + transport_identification?: string; + fast_id?: string; +} + +export interface DodaUpdate extends Partial {} + +export interface DodaListResponse { + items: Doda[]; + total: number; + page: number; + page_size: number; + pages: number; +} + + +export async function getDodas( + page: number = 1, + pageSize: number = 50, + filters: Record = {} +): Promise> { + const params = new URLSearchParams({ + page: page.toString(), + page_size: pageSize.toString(), + ...filters + }); + const response = await api.get(`/v1/a76/doda?${params.toString()}`); + return response.data; +} + +export async function getDoda(id: number): Promise { + const response = await api.get(`/v1/a76/doda/${id}`); + return response.data; +} + +export async function createDoda(data: DodaCreate): Promise { + const response = await api.post('/v1/a76/doda', data); + return response.data; +} + +export async function updateDoda(id: number, data: DodaUpdate): Promise { + const response = await api.patch(`/v1/a76/doda/${id}`, data); + return response.data; +} + +export async function deleteDoda(id: number): Promise { + await api.delete(`/v1/a76/doda/${id}`); +} \ No newline at end of file diff --git a/frontend/src/lib/api/dashboard/a76/general_catalogs/electronic-notices.ts b/frontend/src/lib/api/dashboard/a76/general_catalogs/electronic-notices.ts index a1bb341f..921f1955 100644 --- a/frontend/src/lib/api/dashboard/a76/general_catalogs/electronic-notices.ts +++ b/frontend/src/lib/api/dashboard/a76/general_catalogs/electronic-notices.ts @@ -2,60 +2,81 @@ import { api } from '$lib/api'; import type { ApiResponse } from '$lib/api'; export interface ElectronicNotice { - id: number; - code: string; - description?: string; - tenant_id: string; - company_id?: string; - created_at: string; - updated_at?: string; + id: number; + // Campos del Modelo Python + notice_number?: string; + year?: string; + patent?: string; + pedimento?: string; + file_sent?: string; + file_response?: string; + status?: string; + invoice?: string; + validation_acknowledgment?: string; + fea?: string; + certificate_number?: string; + + // Mixins + tenant_id?: string; + created_at?: string; + updated_at?: string; } export interface ElectronicNoticeCreate { - code: string; - description?: string; + notice_number?: string; + year?: string; + patent?: string; + pedimento?: string; + file_sent?: string; + file_response?: string; + status?: string; + invoice?: string; + validation_acknowledgment?: string; + fea?: string; + certificate_number?: string; } export interface ElectronicNoticeUpdate extends Partial {} export interface ElectronicNoticeListResponse { - items: ElectronicNotice[]; - total: number; - page: number; - page_size: number; - pages: number; + items: ElectronicNotice[]; + total: number; + page: number; + page_size: number; + pages: number; } export async function getElectronicNotices( - page: number = 1, - pageSize: number = 50, - filters: Record = {} + page: number = 1, + pageSize: number = 50, + filters: Record = {} ): Promise> { - const params = new URLSearchParams({ - page: page.toString(), - page_size: pageSize.toString(), - ...filters - }); + const params = new URLSearchParams({ + page: page.toString(), + page_size: pageSize.toString(), + ...filters + }); - const response = await api.get(`/a76/electronic_notices?${params.toString()}`); - return response.data; + // Agregamos '/' al final + const response = await api.get(`/a76/electronic_notices/?${params.toString()}`); + return response.data; } export async function getElectronicNotice(id: number): Promise { - const response = await api.get(`/a76/electronic_notices/${id}`); - return response.data; + const response = await api.get(`/a76/electronic_notices/${id}`); + return response.data; } export async function createElectronicNotice(data: ElectronicNoticeCreate): Promise { - const response = await api.post('/a76/electronic_notices', data); - return response.data; + const response = await api.post('/a76/electronic_notices', data); + return response.data; } export async function updateElectronicNotice(id: number, data: ElectronicNoticeUpdate): Promise { - const response = await api.patch(`/a76/electronic_notices/${id}`, data); - return response.data; + const response = await api.patch(`/a76/electronic_notices/${id}`, data); + return response.data; } export async function deleteElectronicNotice(id: number): Promise { - await api.delete(`/a76/electronic_notices/${id}`); -} + await api.delete(`/a76/electronic_notices/${id}`); +} \ No newline at end of file diff --git a/frontend/src/lib/api/dashboard/a76/general_catalogs/prevalidators.ts b/frontend/src/lib/api/dashboard/a76/general_catalogs/prevalidators.ts index fde1a562..a7842b43 100644 --- a/frontend/src/lib/api/dashboard/a76/general_catalogs/prevalidators.ts +++ b/frontend/src/lib/api/dashboard/a76/general_catalogs/prevalidators.ts @@ -2,60 +2,66 @@ import { api } from '$lib/api'; import type { ApiResponse } from '$lib/api'; export interface Prevalidator { - id: number; - code: string; - description?: string; - tenant_id: string; - company_id?: string; - created_at: string; - updated_at?: string; + id: number; + code: string; + description?: string; + // Campos que faltaban según tu modelo Python: + customs_prevalidator?: string; + patent_prevalidator?: string; + + tenant_id: string; + company_id?: string; + created_at: string; + updated_at?: string; } export interface PrevalidatorCreate { - code: string; - description?: string; + code: string; + description?: string; + customs_prevalidator?: string; + patent_prevalidator?: string; } export interface PrevalidatorUpdate extends Partial {} export interface PrevalidatorListResponse { - items: Prevalidator[]; - total: number; - page: number; - page_size: number; - pages: number; + items: Prevalidator[]; + total: number; + page: number; + page_size: number; + pages: number; } export async function getPrevalidators( - page: number = 1, - pageSize: number = 50, - filters: Record = {} + page: number = 1, + pageSize: number = 50, + filters: Record = {} ): Promise> { - const params = new URLSearchParams({ - page: page.toString(), - page_size: pageSize.toString(), - ...filters - }); + const params = new URLSearchParams({ + page: page.toString(), + page_size: pageSize.toString(), + ...filters + }); - const response = await api.get(`/a76/prevalidators?${params.toString()}`); - return response.data; + const response = await api.get(`/a76/prevalidators/?${params.toString()}`); + return response.data; } export async function getPrevalidator(id: number): Promise { - const response = await api.get(`/a76/prevalidators/${id}`); - return response.data; + const response = await api.get(`/a76/prevalidators/${id}`); + return response.data; } export async function createPrevalidator(data: PrevalidatorCreate): Promise { - const response = await api.post('/a76/prevalidators', data); - return response.data; + const response = await api.post('/a76/prevalidators', data); + return response.data; } export async function updatePrevalidator(id: number, data: PrevalidatorUpdate): Promise { - const response = await api.patch(`/a76/prevalidators/${id}`, data); - return response.data; + const response = await api.patch(`/a76/prevalidators/${id}`, data); + return response.data; } export async function deletePrevalidator(id: number): Promise { - await api.delete(`/a76/prevalidators/${id}`); -} + await api.delete(`/a76/prevalidators/${id}`); +} \ No newline at end of file diff --git a/frontend/src/lib/components/dashboard/exchange-rate/create-edit-dialog.svelte b/frontend/src/lib/components/dashboard/exchange-rate/create-edit-dialog.svelte index 5577e18a..d2a2f7fe 100644 --- a/frontend/src/lib/components/dashboard/exchange-rate/create-edit-dialog.svelte +++ b/frontend/src/lib/components/dashboard/exchange-rate/create-edit-dialog.svelte @@ -23,9 +23,8 @@ const isEdit = $derived(!!item); const title = $derived(isEdit ? "Editar Tipo de Cambio" : "Nuevo Tipo de Cambio"); - // Estado del formulario let formData = $state({ - date: '', // Se usará con input type="date" (YYYY-MM-DD) + date: '', value: null as number | null, local_currency: '', foreign_currency: '' @@ -38,10 +37,7 @@ $effect(() => { if (open) { if (item && item.date) { - // Truco: Si la fecha viene con hora (ej: 2023-01-01T12:00:00), - // solo tomamos la parte de la fecha para el input. const formattedDate = item.date.includes('T') ? item.date.split('T')[0] : item.date; - formData = { date: formattedDate, value: item.value, @@ -49,12 +45,11 @@ foreign_currency: item.foreign_currency || '' }; } else { - // Reset para nuevo registro. Ponemos la fecha de hoy por default. formData = { date: new Date().toISOString().split('T')[0], value: null, - local_currency: 'MXN', // Default común - foreign_currency: 'USD' // Default común + local_currency: 'MXN', + foreign_currency: 'USD' }; } error = null; @@ -64,40 +59,30 @@ async function handleSubmit() { loading = true; error = null; - try { const companyId = companyStore.activeCompany?.id; if (!companyId) throw new Error('No hay una compañía seleccionada'); - // Validaciones básicas if (!formData.date) throw new Error('La fecha es requerida'); - if (formData.value === null || formData.value === undefined) throw new Error('El valor es requerido'); + if (formData.value === null) throw new Error('El valor es requerido'); - // Preparar datos - // Pydantic suele aceptar YYYY-MM-DD para campos datetime sin problema. const dataToSend = { date: formData.date, value: Number(formData.value), - // Estandarizamos a mayúsculas las monedas local_currency: formData.local_currency?.trim().toUpperCase() || null, foreign_currency: formData.foreign_currency?.trim().toUpperCase() || null }; - let response; - - // 👇 companyId va como tercer argumento, ¡bien ahí! if (isEdit && item) { - response = await updateExchangeRate(item.id, dataToSend, companyId); + await updateExchangeRate(item.id, dataToSend, companyId); } else { - response = await createExchangeRate(dataToSend, companyId); + await createExchangeRate(dataToSend, companyId); } open = false; if (onSuccess) onSuccess(); - } catch (e) { - // Si el API wrapper no lanza error, revisa cómo manejar la respuesta de error aquí - error = e instanceof Error ? e.message : 'Error al guardar el tipo de cambio'; + error = e instanceof Error ? e.message : 'Error al guardar'; } finally { loading = false; } @@ -105,83 +90,60 @@ - - - {title} - + + + + + + {title} + -
{ e.preventDefault(); handleSubmit(); }} class="space-y-4 py-4"> - {#if error} -
- {error} -
- {/if} + { e.preventDefault(); handleSubmit(); }} class="space-y-4 py-4"> + {#if error} +
+ {error} +
+ {/if} -
-
- -
- +
+
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
-
- -
- -

Hasta 6 decimales.

-
-
- -
- -
- -
-
- -
- -
- -
-
-
- - - - - - - + + + + + + + \ No newline at end of file diff --git a/frontend/src/lib/components/dashboard/general_catalogs/doda/create-edit-dialog.svelte b/frontend/src/lib/components/dashboard/general_catalogs/doda/create-edit-dialog.svelte new file mode 100644 index 00000000..d03e173c --- /dev/null +++ b/frontend/src/lib/components/dashboard/general_catalogs/doda/create-edit-dialog.svelte @@ -0,0 +1,265 @@ + + + + + + {title} + + +
{ e.preventDefault(); handleSubmit(); }} class="py-2"> + {#if error} +
+ {error} +
+ {/if} + + + + General + Transporte + + Contenedores ({formData.containers.length}) + + + + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+ + +
+ +
+ {#if formData.containers.length === 0} +
+ No hay contenedores registrados. +
+ {:else} + {#each formData.containers as container, i} +
+
+ + +
+
+ + +
+ +
+ {/each} + {/if} +
+
+
+ + {#if isEdit && doda} +
+
+ Creado: + {formatDate(doda.created_at)} +
+ {#if doda.updated_at} +
+ Actualizado: + {formatDate(doda.updated_at)} +
+ {/if} +
+ {/if} + + + + + +
+
+
\ No newline at end of file diff --git a/frontend/src/lib/components/dashboard/general_catalogs/electronic-notices/create-edit-dialog.svelte b/frontend/src/lib/components/dashboard/general_catalogs/electronic-notices/create-edit-dialog.svelte new file mode 100644 index 00000000..f3ca2d15 --- /dev/null +++ b/frontend/src/lib/components/dashboard/general_catalogs/electronic-notices/create-edit-dialog.svelte @@ -0,0 +1,209 @@ + + + + + + {title} + + +
{ e.preventDefault(); handleSubmit(); }} class="space-y-6 py-4"> + {#if error} +
+ {error} +
+ {/if} + +
+ +
+

Identificación

+
+ +
+ + +
+ +
+ + +
+ +
+

Datos Operativos

+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+

Validación

+
+ +
+ + +
+ +
+ + +
+
+ + {#if isEdit && notice} +
+
+ Creado: + {formatDate(notice.created_at)} +
+ {#if notice.updated_at} +
+ Actualizado: + {formatDate(notice.updated_at)} +
+ {/if} +
+ {/if} + + + + + +
+
+
\ No newline at end of file diff --git a/frontend/src/lib/components/dashboard/general_catalogs/error_catalogs/create-edite-dialoge.svelte b/frontend/src/lib/components/dashboard/general_catalogs/error_catalogs/create-edite-dialoge.svelte index 1a8b1db0..7c6a668d 100644 --- a/frontend/src/lib/components/dashboard/general_catalogs/error_catalogs/create-edite-dialoge.svelte +++ b/frontend/src/lib/components/dashboard/general_catalogs/error_catalogs/create-edite-dialoge.svelte @@ -1,141 +1,165 @@ -{#if open} -
- -
- -
-

- {classification ? 'Editar Clasificación' : 'Nueva Clasificación'} -

- -
+ + + + {title} + -
- -
- - - {#if classification} -

El código no se puede cambiar.

- {/if} -
+ { e.preventDefault(); handleSubmit(); }} class="space-y-4 py-4"> + {#if error} +
+ {error} +
+ {/if} -
- - -
+
+
+ +
+ + {#if isEdit} +

El código no se puede modificar.

+ {/if} +
+
- {#if classification} -
-
- Creado: - {formatDate(classification.created_at)} -
- {#if classification.updated_at} -
- Actualizado: - {formatDate(classification.updated_at)} -
- {/if} -
- {/if} +
+ +
+ +

Máximo 3 caracteres

+
+
+
-
- - -
+ {#if isEdit && classification} +
+
+ Creado: + {formatDate(classification.created_at)} +
+ {#if classification.updated_at} +
+ Actualizado: + {formatDate(classification.updated_at)} +
+ {/if} +
+ {/if} -
-
-
-{/if} \ No newline at end of file + + + + + + + \ No newline at end of file diff --git a/frontend/src/lib/components/dashboard/general_catalogs/inpc/create-edite-dialoge.svelte b/frontend/src/lib/components/dashboard/general_catalogs/inpc/create-edite-dialoge.svelte index bb314713..3d2aa34b 100644 --- a/frontend/src/lib/components/dashboard/general_catalogs/inpc/create-edite-dialoge.svelte +++ b/frontend/src/lib/components/dashboard/general_catalogs/inpc/create-edite-dialoge.svelte @@ -23,7 +23,6 @@ const isEdit = $derived(!!item); const title = $derived(isEdit ? "Editar INPC" : "Nuevo INPC"); - // Estado del formulario let formData = $state({ year: '', month: '', diff --git a/frontend/src/lib/components/dashboard/general_catalogs/locations/create-edite-dialog.svelte b/frontend/src/lib/components/dashboard/general_catalogs/locations/create-edite-dialog.svelte index 1fc27954..921b2e6c 100644 --- a/frontend/src/lib/components/dashboard/general_catalogs/locations/create-edite-dialog.svelte +++ b/frontend/src/lib/components/dashboard/general_catalogs/locations/create-edite-dialog.svelte @@ -1,25 +1,40 @@ -{#if open} -
- -
- -
-

- {location ? 'Editar Ubicación' : 'Nueva Ubicación'} -

- + + + + {title} + + +
{ e.preventDefault(); handleSubmit(); }} class="space-y-4 py-4"> + {#if error} +
+ {error} +
+ {/if} + +
+ +
+ +
+ +

Máximo 5 caracteres

+
+
+ +
+ +
+ -
- - {#if location} -
-
- Creado: - {formatDate(location.created_at)} -
- {#if location.updated_at} -
- Actualizado: - {formatDate(location.updated_at)} -
- {/if} + {#if isEdit && location} +
+
+ Creado: + {formatDate(location.created_at)}
- {/if} - -
- - + {#if location.updated_at} +
+ Actualizado: + {formatDate(location.updated_at)} +
+ {/if}
+ {/if} - -
-
-{/if} \ No newline at end of file + + + + + + + \ No newline at end of file diff --git a/frontend/src/lib/components/dashboard/general_catalogs/prevalidators/create-edit-dialog.svelte b/frontend/src/lib/components/dashboard/general_catalogs/prevalidators/create-edit-dialog.svelte new file mode 100644 index 00000000..cc7094db --- /dev/null +++ b/frontend/src/lib/components/dashboard/general_catalogs/prevalidators/create-edit-dialog.svelte @@ -0,0 +1,200 @@ + + + + + + {title} + + +
{ e.preventDefault(); handleSubmit(); }} class="space-y-4 py-4"> + {#if error} +
+ {error} +
+ {/if} + +
+ +
+ +
+ +

Máximo 20 caracteres

+
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+