diff --git a/backend/api/v1/modules/a24/fa/fa_classes/models.py b/backend/api/v1/modules/a24/fa/fa_classes/models.py index 3daf9efe..1f8f5fc4 100644 --- a/backend/api/v1/modules/a24/fa/fa_classes/models.py +++ b/backend/api/v1/modules/a24/fa/fa_classes/models.py @@ -17,7 +17,7 @@ class QClasses(Base, TenantScopedMixin, TimestampMixin): __tablename__ = "fa_classes" # QClases __table_args__ = ( PrimaryKeyConstraint("id", name="qclases_pk"), - ForeignKeyConstraint(["class_id"], ["classes.id"], name="fk_qclasses_classes"), + ForeignKeyConstraint(["class_id"], ["a76.classes.id"], name="fk_qclasses_classes"), {"schema": "a24"}, ) diff --git a/backend/api/v1/modules/a76/classes/models.py b/backend/api/v1/modules/a76/classes/models.py index 53138b16..240238e9 100644 --- a/backend/api/v1/modules/a76/classes/models.py +++ b/backend/api/v1/modules/a76/classes/models.py @@ -42,6 +42,7 @@ class Class(Base, TenantScopedMixin, TimestampMixin): UniqueConstraint( "tenant_id", "company_id", + "client_id", "class_code", name="ufa_classes_client_id_class_code", ), diff --git a/backend/api/v1/modules/a76/classes/service.py b/backend/api/v1/modules/a76/classes/service.py index 57d77c3f..ff7e2a93 100644 --- a/backend/api/v1/modules/a76/classes/service.py +++ b/backend/api/v1/modules/a76/classes/service.py @@ -102,6 +102,7 @@ class ClassService: existing = db.query(Class).filter( Class.tenant_id == tenant_id, Class.company_id == company_id, + Class.client_id == data_dict["client_id"], Class.class_code == data_dict["class_code"] ).first() diff --git a/backend/api/v1/modules/a76/general_catalogs/exchange_rate/dto.py b/backend/api/v1/modules/a76/general_catalogs/exchange_rate/dto.py index 3dbe23d3..ce0ddea8 100644 --- a/backend/api/v1/modules/a76/general_catalogs/exchange_rate/dto.py +++ b/backend/api/v1/modules/a76/general_catalogs/exchange_rate/dto.py @@ -1,10 +1,11 @@ +from datetime import datetime from typing import Optional from pydantic import BaseModel, Field class ExchangeRateBaseDTO(BaseModel): - date: int = Field(..., description="Exchange rate date") + date: datetime = Field(..., description="Exchange rate date") value: Optional[float] = Field(None, description="Exchange rate value") local_currency: Optional[str] = Field(None, max_length=7, description="Local currency code") foreign_currency: Optional[str] = Field(None, max_length=7, description="Foreign currency code") @@ -17,7 +18,7 @@ class ExchangeRateCreateDTO(ExchangeRateBaseDTO): class ExchangeRateUpdateDTO(ExchangeRateBaseDTO): """Schema for updating an exchange rate""" - date: Optional[int] = Field(None, description="Exchange rate date") + date: Optional[datetime] = Field(None, description="Exchange rate date") class ExchangeRateResponseDTO(ExchangeRateBaseDTO): diff --git a/backend/api/v1/modules/a76/general_catalogs/exchange_rate/models.py b/backend/api/v1/modules/a76/general_catalogs/exchange_rate/models.py index 271000b5..5e9d95b4 100644 --- a/backend/api/v1/modules/a76/general_catalogs/exchange_rate/models.py +++ b/backend/api/v1/modules/a76/general_catalogs/exchange_rate/models.py @@ -1,3 +1,4 @@ +from datetime import datetime from decimal import Decimal from typing import Optional @@ -28,7 +29,7 @@ class ExchangeRate(Base, TenantScopedMixin, TimestampMixin): id: Mapped[int] = mapped_column(Integer, primary_key=True) - date: Mapped[int] = mapped_column(DateTime) + date: Mapped[datetime] = mapped_column(DateTime) value: Mapped[Optional[Decimal]] = mapped_column(DECIMAL(13, 6)) local_currency: Mapped[Optional[str]] = mapped_column(String(7)) foreign_currency: Mapped[Optional[str]] = mapped_column(String(7)) diff --git a/backend/api/v1/modules/a76/general_catalogs/packages/dto.py b/backend/api/v1/modules/a76/general_catalogs/packages/dto.py index 3d1e153b..09e421df 100644 --- a/backend/api/v1/modules/a76/general_catalogs/packages/dto.py +++ b/backend/api/v1/modules/a76/general_catalogs/packages/dto.py @@ -2,6 +2,7 @@ DTOs for Packages (GBultos). """ +from datetime import datetime from typing import Optional from pydantic import BaseModel, Field @@ -33,8 +34,8 @@ class PackageResponseDTO(PackageBaseDTO): id: int company_id: int tenant_id: int - created_at: Optional[str] = None - updated_at: Optional[str] = None + created_at: Optional[datetime] = None + updated_at: Optional[datetime] = None class Config: from_attributes = True diff --git a/backend/api/v1/modules/a76/general_catalogs/packages/routes.py b/backend/api/v1/modules/a76/general_catalogs/packages/routes.py index 0622a247..88a6eba4 100644 --- a/backend/api/v1/modules/a76/general_catalogs/packages/routes.py +++ b/backend/api/v1/modules/a76/general_catalogs/packages/routes.py @@ -13,11 +13,11 @@ router = TenantCRUDRoutes( create_schema=PackageCreateDTO, update_schema=PackageUpdateDTO, response_schema=PackageResponseDTO, - prefix="/package", - tags=[], + prefix="/packages", + tags=["a76 / packages"], resource_name="Package", - id_name="id", # Using numeric ID - enable_list=True, # Enable GET /package with pagination + id_name="package_id", + enable_list=True, # Enable GET /packages with pagination enable_filters=True, # Enable filtering by key and description_es default_page_size=50, max_page_size=100, diff --git a/backend/api/v1/modules/a76/general_catalogs/seal/services.py b/backend/api/v1/modules/a76/general_catalogs/seal/services.py index a6843b9e..f29fcb2f 100644 --- a/backend/api/v1/modules/a76/general_catalogs/seal/services.py +++ b/backend/api/v1/modules/a76/general_catalogs/seal/services.py @@ -75,8 +75,8 @@ class SealService: db: Session, seal_id: int, tenant_id: int, - company_id: int, seal_data: dto.SealUpdateDTO, + company_id: int, ) -> Optional[models.Seal]: """Update a seal""" seal = SealService.get_by_id(db, seal_id, tenant_id, company_id) diff --git a/backend/api/v1/modules/a76/router.py b/backend/api/v1/modules/a76/router.py index 8f758c2a..be3a8b78 100644 --- a/backend/api/v1/modules/a76/router.py +++ b/backend/api/v1/modules/a76/router.py @@ -46,7 +46,7 @@ router.include_router(parts_router, prefix="/a76", tags=["a76 / parts"]) router.include_router( permission_rule_oct_router, prefix="/a76", tags=["a76 / permission_rule_oct"] ) -router.include_router(package_router, prefix="/a76", tags=["a76 / package"]) +router.include_router(package_router, prefix="/a76") router.include_router(seal_router, prefix="/a76", tags=["a76 / seal"]) router.include_router( fraction_rule_octave_router, prefix="/a76", tags=["a76 / fraction_rule_octave"] diff --git a/frontend/src/lib/api/dashboard/a76/exchange-rate.ts b/frontend/src/lib/api/dashboard/a76/exchange-rate.ts new file mode 100644 index 00000000..b8b94a40 --- /dev/null +++ b/frontend/src/lib/api/dashboard/a76/exchange-rate.ts @@ -0,0 +1,91 @@ +import type { PaginatedResponse } from '$lib/types'; +import { api } from '$lib/api'; + +export interface ExchangeRate { + id: number; + date: string; + value: number | null; + local_currency: string | null; + foreign_currency: string | null; + company_id: number; + tenant_id: number; +} + +export interface ExchangeRateCreate { + date: string; + value?: number | null; + local_currency?: string | null; + foreign_currency?: string | null; +} + +export interface ExchangeRateUpdate { + date?: string; + value?: number | null; + local_currency?: string | null; + foreign_currency?: string | null; +} + +export interface ExchangeRateListResponse extends PaginatedResponse { + items: ExchangeRate[]; +} + +export interface ExchangeRateFilters { + date?: string; + local_currency?: string; + foreign_currency?: string; + page?: number; + page_size?: number; +} + +export async function getExchangeRates( + companyId: number, + filters?: ExchangeRateFilters +): Promise { + const params = new URLSearchParams({ company_id: companyId.toString() }); + + if (filters) { + if (filters.date) params.append('date', filters.date); + if (filters.local_currency) params.append('local_currency', filters.local_currency); + if (filters.foreign_currency) params.append('foreign_currency', filters.foreign_currency); + if (filters.page) params.append('page', filters.page.toString()); + if (filters.page_size) params.append('page_size', filters.page_size.toString()); + } + + return api.get(`/v1/a76/exchange-rate/?${params.toString()}`); +} + +export async function getExchangeRate( + exchangeRateId: number, + companyId: number +): Promise { + const params = new URLSearchParams({ company_id: companyId.toString() }); + return api.get(`/v1/a76/exchange-rate/${exchangeRateId}?${params.toString()}`); +} + +export async function createExchangeRate( + data: ExchangeRateCreate, + companyId: number +): Promise { + const params = new URLSearchParams({ company_id: companyId.toString() }); + return api.post(`/v1/a76/exchange-rate/?${params.toString()}`, data); +} + +export async function updateExchangeRate( + exchangeRateId: number, + data: ExchangeRateUpdate, + companyId: number +): Promise { + const params = new URLSearchParams({ company_id: companyId.toString() }); + return api.put( + `/v1/a76/exchange-rate/${exchangeRateId}?${params.toString()}`, + data + ); +} + +export async function deleteExchangeRate( + exchangeRateId: number, + companyId: number +): Promise { + const params = new URLSearchParams({ company_id: companyId.toString() }); + return api.delete(`/v1/a76/exchange-rate/${exchangeRateId}?${params.toString()}`); +} diff --git a/frontend/src/lib/api/dashboard/a76/index.ts b/frontend/src/lib/api/dashboard/a76/index.ts index f070b9dd..84029759 100644 --- a/frontend/src/lib/api/dashboard/a76/index.ts +++ b/frontend/src/lib/api/dashboard/a76/index.ts @@ -2,3 +2,5 @@ * Exportaciones de APIs para módulo A76 */ export * from './classes'; +export * from './packages'; +export * from './exchange-rate'; diff --git a/frontend/src/lib/api/dashboard/a76/packages.ts b/frontend/src/lib/api/dashboard/a76/packages.ts new file mode 100644 index 00000000..8fb8d682 --- /dev/null +++ b/frontend/src/lib/api/dashboard/a76/packages.ts @@ -0,0 +1,122 @@ +/** + * API para gestión de Packages (Bultos/Embalajes A76) + */ +import { api } from '$lib/api'; +import type { ApiResponse } from '$lib/api'; + +export interface Package { + id: number; + tenant_id: number; + company_id: number; + key: string; + description_es: string | null; + description_en: string | null; + weight_unit: number | null; + plurals: string | null; + plural_in: string | null; + code_ace: string | null; + code_aamex: string | null; + created_at: string | null; + updated_at: string | null; +} + +export interface PackageCreate { + key: string; + description_es?: string | null; + description_en?: string | null; + weight_unit?: number | null; + plurals?: string | null; + plural_in?: string | null; + code_ace?: string | null; + code_aamex?: string | null; +} + +export interface PackageUpdate { + key?: string; + description_es?: string | null; + description_en?: string | null; + weight_unit?: number | null; + plurals?: string | null; + plural_in?: string | null; + code_ace?: string | null; + code_aamex?: string | null; +} + +export interface PackageListResponse { + items: Package[]; + total: number; + page: number; + page_size: number; + pages: number; +} + +export interface PackageFilters { + key?: string; + description_es?: string; +} + +/** + * Obtener lista de packages con paginación + */ +export async function getPackages( + companyId: number, + page: number = 1, + pageSize: number = 50, + filters?: PackageFilters +): Promise> { + const params = new URLSearchParams({ + company_id: companyId.toString(), + page: page.toString(), + page_size: pageSize.toString() + }); + + if (filters?.key) { + params.append('key', filters.key); + } + if (filters?.description_es) { + params.append('description_es', filters.description_es); + } + + return api.get(`/v1/a76/packages/?${params.toString()}`); +} + +/** + * Obtener un package por ID + */ +export async function getPackage( + packageId: number, + companyId: number +): Promise> { + return api.get(`/v1/a76/packages/${packageId}?company_id=${companyId}`); +} + +/** + * Crear un nuevo package + */ +export async function createPackage( + data: PackageCreate, + companyId: number +): Promise> { + return api.post(`/v1/a76/packages/?company_id=${companyId}`, data); +} + +/** + * Actualizar un package existente + */ +export async function updatePackage( + packageId: number, + data: PackageUpdate, + companyId: number +): Promise> { + return api.put(`/v1/a76/packages/${packageId}?company_id=${companyId}`, data); +} + +/** + * Eliminar un package + */ +export async function deletePackage( + packageId: number, + companyId: number +): Promise> { + return api.delete(`/v1/a76/packages/${packageId}?company_id=${companyId}`); +} diff --git a/frontend/src/lib/api/dashboard/a76/seal.ts b/frontend/src/lib/api/dashboard/a76/seal.ts new file mode 100644 index 00000000..e0d848d9 --- /dev/null +++ b/frontend/src/lib/api/dashboard/a76/seal.ts @@ -0,0 +1,95 @@ +/** + * API client for Seal operations + */ +import { api } from '$lib/api'; + +export interface Seal { + id: number; + seal: string; + company_id: number; + tenant_id: number; +} + +export interface SealListResponse { + items: Seal[]; + total: number; + page: number; + page_size: number; + pages: number; +} + +export interface SealCreateRequest { + seal: string; +} + +export interface SealUpdateRequest { + seal?: string; +} + +/** + * Get all seals with pagination and filters + */ +export async function getSeals( + companyId: number, + filters?: { + page?: number; + page_size?: number; + seal?: string; + } +): Promise<{ data: SealListResponse; status: number }> { + const params = new URLSearchParams(); + params.append('company_id', companyId.toString()); + + if (filters?.page) params.append('page', filters.page.toString()); + if (filters?.page_size) params.append('page_size', filters.page_size.toString()); + if (filters?.seal) params.append('seal', filters.seal); + + const response = await api.get(`/v1/a76/seals?${params.toString()}`); + + return response; +} + +/** + * Get a single seal by ID + */ +export async function getSeal( + id: number, + companyId: number +): Promise<{ data: Seal; status: number }> { + const response = await api.get(`/v1/a76/seals/${id}?company_id=${companyId}`); + return response; +} + +/** + * Create a new seal + */ +export async function createSeal( + data: SealCreateRequest, + companyId: number +): Promise<{ data: Seal; status: number }> { + const response = await api.post(`/v1/a76/seals?company_id=${companyId}`, data); + return response; +} + +/** + * Update an existing seal + */ +export async function updateSeal( + id: number, + data: SealUpdateRequest, + companyId: number +): Promise<{ data: Seal; status: number }> { + const response = await api.put(`/v1/a76/seals/${id}?company_id=${companyId}`, data); + return response; +} + +/** + * Delete a seal + */ +export async function deleteSeal( + id: number, + companyId: number +): Promise<{ data: any; status: number }> { + const response = await api.delete(`/v1/a76/seals/${id}?company_id=${companyId}`); + return response; +} diff --git a/frontend/src/lib/api/dashboard/public/countries.ts b/frontend/src/lib/api/dashboard/public/countries.ts new file mode 100644 index 00000000..df2c61ea --- /dev/null +++ b/frontend/src/lib/api/dashboard/public/countries.ts @@ -0,0 +1,95 @@ +/** + * API client for Countries operations + */ +import { api } from '$lib/api'; + +export interface Country { + m3_key: string; + mex_key: string; + ame_key: string; + description_es: string; + description_en: string; +} + +export interface CountryListResponse { + items: Country[]; + total: number; + page: number; + page_size: number; +} + +export interface CountryCreateRequest { + m3_key: string; + mex_key: string; + ame_key: string; + description_es: string; + description_en: string; +} + +export interface CountryUpdateRequest { + m3_key: string; + mex_key: string; + ame_key: string; + description_es: string; + description_en: string; +} + +/** + * Get all countries with pagination + */ +export async function getCountries( + filters?: { + page?: number; + page_size?: number; + } +): Promise<{ data: CountryListResponse; status: number }> { + const params = new URLSearchParams(); + + if (filters?.page) params.append('page', filters.page.toString()); + if (filters?.page_size) params.append('page_size', filters.page_size.toString()); + + const response = await api.get(`/v1/public/refrence_data/countries?${params.toString()}`); + + return response; +} + +/** + * Get a single country by m3_key + */ +export async function getCountry( + m3_key: string +): Promise<{ data: Country; status: number }> { + const response = await api.get(`/v1/public/refrence_data/countries/${m3_key}`); + return response; +} + +/** + * Create a new country + */ +export async function createCountry( + data: CountryCreateRequest +): Promise<{ data: Country; status: number }> { + const response = await api.post(`/v1/public/refrence_data/countries`, data); + return response; +} + +/** + * Update an existing country + */ +export async function updateCountry( + m3_key: string, + data: CountryUpdateRequest +): Promise<{ data: Country; status: number }> { + const response = await api.put(`/v1/public/refrence_data/countries/${m3_key}`, data); + return response; +} + +/** + * Delete a country + */ +export async function deleteCountry( + m3_key: string +): Promise<{ data: any; status: number }> { + const response = await api.delete(`/v1/public/refrence_data/countries/${m3_key}`); + return response; +} diff --git a/frontend/src/lib/components/dashboard/classes/create-edit-dialog.svelte b/frontend/src/lib/components/dashboard/classes/create-edit-dialog.svelte index a61219e4..aaaaafa4 100644 --- a/frontend/src/lib/components/dashboard/classes/create-edit-dialog.svelte +++ b/frontend/src/lib/components/dashboard/classes/create-edit-dialog.svelte @@ -7,9 +7,9 @@ import * as Select from "$lib/components/ui/select"; import { classesApi, type A76Class, type A76ClassCreate, type A76ClassUpdate } from "$lib/api/dashboard/a76/classes"; import { materialTypesApi, type MaterialType } from "$lib/api/dashboard/refrence_data/material_types"; + import { clientsProvidersApi, type ClientProviderBasic } from "$lib/api/dashboard/a76/clients-providers"; import { companyStore } from "$lib/stores/company.svelte"; import { onMount } from 'svelte'; - import { LoaderCircle, Home } from 'lucide-svelte'; let { open = $bindable(false), @@ -27,6 +27,7 @@ // Estado del formulario let formData = $state({ + client_id: item?.client_id || null, class_code: item?.class_code || '', description_es: item?.description_es || '', description_en: item?.description_en || '', @@ -43,17 +44,23 @@ let error = $state(null); let materialTypes = $state([]); let loadingMaterialTypes = $state(false); + let clients = $state([]); + let loadingClients = $state(false); // Variables para controlar los selects let selectedUnitValue = $state('KG'); let selectedMaterialValue = $state(''); let selectedPhysicalReviewValue = $state(0); - // Cargar tipos de materiales al montar + // Cargar tipos de materiales y clientes al montar onMount(async () => { + const companyId = companyStore.activeCompany?.id; + if (!companyId) return; + + // Cargar tipos de materiales loadingMaterialTypes = true; try { - const response = await materialTypesApi.list(1, 100); // Cargar los primeros 100 + const response = await materialTypesApi.list(1, 100); if (response.data) { materialTypes = response.data.items; } @@ -62,12 +69,26 @@ } finally { loadingMaterialTypes = false; } + + // Cargar clientes + loadingClients = true; + try { + const response = await clientsProvidersApi.listClients(companyId, 0, 500); + if (response.data) { + clients = response.data; + } + } catch (e) { + console.error('Error loading clients:', e); + } finally { + loadingClients = false; + } }); // Resetear formulario cuando cambia el item $effect(() => { if (item) { formData = { + client_id: item.client_id, class_code: item.class_code, description_es: item.description_es || '', description_en: item.description_en || '', @@ -86,6 +107,7 @@ } else { // Reset para modo crear formData = { + client_id: null, class_code: '', description_es: '', description_en: '', @@ -122,6 +144,10 @@ } // Validaciones básicas + if (!formData.client_id) { + error = 'Debes seleccionar un cliente'; + return; + } if (!formData.class_code.trim()) { error = 'El código de clase es requerido'; return; @@ -152,6 +178,7 @@ if (isEdit && item) { // Actualizar const updateData: A76ClassUpdate = { + client_id: formData.client_id!, class_code: formData.class_code, description_es: formData.description_es || null, description_en: formData.description_en || null, @@ -165,10 +192,10 @@ }; response = await classesApi.update(item.id, updateData, companyId); } else { - // Crear - usa el company_id como client_id + // Crear con el client_id seleccionado const createData: A76ClassCreate = { company_id: companyId, - client_id: companyId, // Usa el mismo company_id como client_id + client_id: formData.client_id!, class_code: formData.class_code, description_es: formData.description_es || null, description_en: formData.description_en || null, @@ -249,7 +276,21 @@ {#if companyStore.activeCompany}
- + + + +

{companyStore.activeCompany.name} @@ -262,6 +303,34 @@

{/if} + +
+ + {#if loadingClients} +
+
+ Cargando clientes... +
+ {:else if clients.length > 0} + + {:else} +
+ No hay clientes disponibles +
+ {/if} +
+
@@ -413,7 +482,26 @@ + +
+ + + diff --git a/frontend/src/lib/components/dashboard/exchange-rate/data-table-actions.svelte b/frontend/src/lib/components/dashboard/exchange-rate/data-table-actions.svelte new file mode 100644 index 00000000..ef912f89 --- /dev/null +++ b/frontend/src/lib/components/dashboard/exchange-rate/data-table-actions.svelte @@ -0,0 +1,99 @@ + + + + + {#snippet child({ props })} + + {/snippet} + + + Acciones + + + + Editar + + + + {#if loading} + + {:else} + + {/if} + Eliminar + + + + + diff --git a/frontend/src/lib/components/dashboard/exchange-rate/data-table.svelte b/frontend/src/lib/components/dashboard/exchange-rate/data-table.svelte new file mode 100644 index 00000000..ef98de23 --- /dev/null +++ b/frontend/src/lib/components/dashboard/exchange-rate/data-table.svelte @@ -0,0 +1,123 @@ + + +
+
+ + + {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} + + {#each headerGroup.headers as header (header.id)} + + {#if !header.isPlaceholder} + + {/if} + + {/each} + + {/each} + + + {#each table.getRowModel().rows as row (row.id)} + + {#each row.getVisibleCells() as cell (cell.id)} + + + + {/each} + + {:else} + + + No hay resultados. + + + {/each} + + + {#if hasMore} + + +
+ {#if loading} +
+
+ Cargando más... +
+ {:else} +
+ Desplázate para cargar más +
+ {/if} +
+
+
+ {/if} +
+
+
+
diff --git a/frontend/src/lib/components/dashboard/packages/columns.ts b/frontend/src/lib/components/dashboard/packages/columns.ts new file mode 100644 index 00000000..ae0838bd --- /dev/null +++ b/frontend/src/lib/components/dashboard/packages/columns.ts @@ -0,0 +1,71 @@ +/** + * Definición de columnas para la tabla de Packages + */ +import type { Package } from '$lib/api/dashboard/a76/packages'; +import type { ColumnDef } from '@tanstack/table-core'; +import { renderComponent } from '$lib/components/ui/data-table'; +import DataTableActions from './data-table-actions.svelte'; + +export function createColumns(onSuccess?: () => void): ColumnDef[] { + return [ + { + accessorKey: 'key', + header: 'Clave', + cell: ({ row }) => { + return row.original.key; + } + }, + { + accessorKey: 'description_es', + header: 'Descripción (ES)', + cell: ({ row }) => { + return row.original.description_es || '-'; + } + }, + { + accessorKey: 'description_en', + header: 'Descripción (EN)', + cell: ({ row }) => { + return row.original.description_en || '-'; + } + }, + { + accessorKey: 'weight_unit', + header: 'Peso Unitario', + cell: ({ row }) => { + return row.original.weight_unit ? row.original.weight_unit.toString() : '-'; + } + }, + { + accessorKey: 'plurals', + header: 'Plural', + cell: ({ row }) => { + return row.original.plurals || '-'; + } + }, + { + accessorKey: 'code_ace', + header: 'Código ACE', + cell: ({ row }) => { + return row.original.code_ace || '-'; + } + }, + { + accessorKey: 'code_aamex', + header: 'Código AAMEX', + cell: ({ row }) => { + return row.original.code_aamex || '-'; + } + }, + { + id: 'actions', + header: 'Acciones', + cell: ({ row }) => { + return renderComponent(DataTableActions, { + item: row.original, + onSuccess + }); + } + } + ]; +} diff --git a/frontend/src/lib/components/dashboard/packages/create-edit-dialog.svelte b/frontend/src/lib/components/dashboard/packages/create-edit-dialog.svelte new file mode 100644 index 00000000..ae48414f --- /dev/null +++ b/frontend/src/lib/components/dashboard/packages/create-edit-dialog.svelte @@ -0,0 +1,260 @@ + + + + + + {title} + + {isEdit ? 'Modifica los datos del bulto' : 'Completa los datos para crear un nuevo bulto'} + + + +
{ e.preventDefault(); handleSubmit(); }} class="space-y-4"> + {#if error} +
+ {error} +
+ {/if} + +
+ +
+ + +

+ Máximo 5 caracteres. {isEdit ? 'No se puede modificar en edición.' : ''} +

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

+ Peso unitario del bulto (hasta 8 decimales) +

+
+ + +
+
+ + +
+ +
+ + +
+
+ + +
+
+ + +
+ +
+ + +
+
+
+ + + + + +
+
+
diff --git a/frontend/src/lib/components/dashboard/packages/data-table-actions.svelte b/frontend/src/lib/components/dashboard/packages/data-table-actions.svelte new file mode 100644 index 00000000..bf4ce8be --- /dev/null +++ b/frontend/src/lib/components/dashboard/packages/data-table-actions.svelte @@ -0,0 +1,111 @@ + + + + + {#snippet child({ props })} + + {/snippet} + + + Acciones + + + + Editar + + + + {#if loading} + + {:else} + + {/if} + Eliminar + + + + + diff --git a/frontend/src/lib/components/dashboard/packages/data-table.svelte b/frontend/src/lib/components/dashboard/packages/data-table.svelte new file mode 100644 index 00000000..ef98de23 --- /dev/null +++ b/frontend/src/lib/components/dashboard/packages/data-table.svelte @@ -0,0 +1,123 @@ + + +
+
+ + + {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} + + {#each headerGroup.headers as header (header.id)} + + {#if !header.isPlaceholder} + + {/if} + + {/each} + + {/each} + + + {#each table.getRowModel().rows as row (row.id)} + + {#each row.getVisibleCells() as cell (cell.id)} + + + + {/each} + + {:else} + + + No hay resultados. + + + {/each} + + + {#if hasMore} + + +
+ {#if loading} +
+
+ Cargando más... +
+ {:else} +
+ Desplázate para cargar más +
+ {/if} +
+
+
+ {/if} +
+
+
+
diff --git a/frontend/src/lib/components/dashboard/seal/columns.ts b/frontend/src/lib/components/dashboard/seal/columns.ts new file mode 100644 index 00000000..cf354c75 --- /dev/null +++ b/frontend/src/lib/components/dashboard/seal/columns.ts @@ -0,0 +1,27 @@ +/** + * Column definitions for Seal table + */ +import type { ColumnDef } from '@tanstack/table-core'; +import type { Seal } from '$lib/api/dashboard/a76/seal'; +import { renderComponent } from '$lib/components/ui/data-table'; +import DataTableActions from './data-table-actions.svelte'; + +export function createColumns(onSuccess?: () => void): ColumnDef[] { + return [ + { + accessorKey: 'seal', + header: 'Sello', + cell: ({ row }) => row.original.seal + }, + { + id: 'actions', + header: 'Acciones', + cell: ({ row }) => { + return renderComponent(DataTableActions, { + item: row.original, + onSuccess + }); + } + } + ]; +} diff --git a/frontend/src/lib/components/dashboard/seal/create-edit-dialog.svelte b/frontend/src/lib/components/dashboard/seal/create-edit-dialog.svelte new file mode 100644 index 00000000..3b525222 --- /dev/null +++ b/frontend/src/lib/components/dashboard/seal/create-edit-dialog.svelte @@ -0,0 +1,132 @@ + + + + + + {title} + + {#if isEdit} + Modifica los datos del sello + {:else} + Ingresa los datos del nuevo sello + {/if} + + + +
+
+ + +

+ Máximo 15 caracteres +

+
+ + {#if error} +
+

{error}

+
+ {/if} + + + + + +
+
+
diff --git a/frontend/src/lib/components/dashboard/seal/data-table-actions.svelte b/frontend/src/lib/components/dashboard/seal/data-table-actions.svelte new file mode 100644 index 00000000..8a821ef3 --- /dev/null +++ b/frontend/src/lib/components/dashboard/seal/data-table-actions.svelte @@ -0,0 +1,98 @@ + + + + + {#snippet child({ props })} + + {/snippet} + + + Acciones + + + + Editar + + + + {#if loading} + + {:else} + + {/if} + Eliminar + + + + + diff --git a/frontend/src/lib/components/dashboard/seal/data-table.svelte b/frontend/src/lib/components/dashboard/seal/data-table.svelte new file mode 100644 index 00000000..7f02c1cc --- /dev/null +++ b/frontend/src/lib/components/dashboard/seal/data-table.svelte @@ -0,0 +1,112 @@ + + +
+
+ + + {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} + + {#each headerGroup.headers as header (header.id)} + + {#if !header.isPlaceholder} + + {/if} + + {/each} + + {/each} + + + {#each table.getRowModel().rows as row (row.id)} + + {#each row.getVisibleCells() as cell (cell.id)} + + + + {/each} + + {:else} + + + No hay resultados. + + + {/each} + + {#if loading} + + + Cargando... + + + {/if} + + +
+
diff --git a/frontend/src/lib/components/dashboard/seal/index.ts b/frontend/src/lib/components/dashboard/seal/index.ts new file mode 100644 index 00000000..5d54605b --- /dev/null +++ b/frontend/src/lib/components/dashboard/seal/index.ts @@ -0,0 +1,7 @@ +/** + * Seal components + */ +export { default as DataTable } from './data-table.svelte'; +export { default as DataTableActions } from './data-table-actions.svelte'; +export { default as CreateEditDialog } from './create-edit-dialog.svelte'; +export { createColumns } from './columns'; diff --git a/frontend/src/lib/components/sidebar/modules.ts b/frontend/src/lib/components/sidebar/modules.ts index 1992cae5..7beadf0b 100644 --- a/frontend/src/lib/components/sidebar/modules.ts +++ b/frontend/src/lib/components/sidebar/modules.ts @@ -159,7 +159,7 @@ export function getSidebarData(): SidebarData { }, { title: m["sidebar.general_catalogs.packages"](), - url: "#", + url: "/dashboard/general_catalogs/packages", }, { title: m["sidebar.general_catalogs.concepts"](), @@ -187,7 +187,7 @@ export function getSidebarData(): SidebarData { }, { title: m["sidebar.general_catalogs.seals"](), - url: "#", + url: "/dashboard/general_catalogs/seal", }, { title: m["sidebar.general_catalogs.valuation_methods"](), @@ -195,7 +195,7 @@ export function getSidebarData(): SidebarData { }, { title: m["sidebar.general_catalogs.countries"](), - url: "#", + url: "/dashboard/reference_data/countries", }, { title: m["sidebar.general_catalogs.ports"](), @@ -231,7 +231,7 @@ export function getSidebarData(): SidebarData { }, { title: m["sidebar.general_catalogs.exchange_rates"](), - url: "#", + url: "/dashboard/general_catalogs/exchange-rate", }, { title: m["sidebar.general_catalogs.currency_types"](), diff --git a/frontend/src/routes/dashboard/general_catalogs/exchange-rate/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/exchange-rate/+page.svelte new file mode 100644 index 00000000..e375caf3 --- /dev/null +++ b/frontend/src/routes/dashboard/general_catalogs/exchange-rate/+page.svelte @@ -0,0 +1,219 @@ + + + + Tipos de Cambio - Anexo 76 + + +
+
+
+

Tipos de Cambio

+

Gestiona los tipos de cambio del sistema

+
+ +
+ + + + Filtros + + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+
+
+ + {#if error} + + Error + {error} + + {/if} + + +
+ + (dialogOpen = open)} + onSuccess={handleSuccess} +/> diff --git a/frontend/src/routes/dashboard/general_catalogs/packages/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/packages/+page.svelte new file mode 100644 index 00000000..695c231b --- /dev/null +++ b/frontend/src/routes/dashboard/general_catalogs/packages/+page.svelte @@ -0,0 +1,326 @@ + + + + Bultos / Embalajes - Catálogos Generales + + +
+ +
+
+

Bultos / Embalajes

+

+ Gestiona los tipos de bultos y embalajes utilizados en tus operaciones +

+
+ +
+ + + + +
+ + + Filtros + + +
+
+ + {#if showFilters} + +
{ e.preventDefault(); handleApplyFilters(); }} class="space-y-4"> +
+
+ + +
+ +
+ + +
+
+ +
+ + + +
+
+
+ {/if} +
+ + + {#if error} + + +
+ + + +

{error}

+
+
+
+ {/if} + + + + +
+
+ Lista de Bultos + + Total: {totalItems} bulto{totalItems !== 1 ? 's' : ''} | + Mostrando: {allItems.length} + +
+
+
+ + + +
+
+ + + diff --git a/frontend/src/routes/dashboard/general_catalogs/seal/+page.svelte b/frontend/src/routes/dashboard/general_catalogs/seal/+page.svelte new file mode 100644 index 00000000..0bbc2509 --- /dev/null +++ b/frontend/src/routes/dashboard/general_catalogs/seal/+page.svelte @@ -0,0 +1,192 @@ + + + + Sellos - Anexo 76 + + +
+
+
+

Sellos

+

Gestiona los sellos de tu empresa

+
+ +
+ + {#if error} +
+

{error}

+
+ {/if} + + + +
+
+ Lista de Sellos + + Total: {allItems.length} sello{allItems.length !== 1 ? 's' : ''} + +
+
+ + +
+
+ + +
+ +
+
+ + + +
+
+ +