feat(api): add Units of Measure API client with CRUD operations

- Implemented interfaces for UnitOfMeasure, UnitOfMeasureListResponse, CreateUnitOfMeasureData, and UpdateUnitOfMeasureData.
- Added API methods for listing, retrieving, creating, updating, and deleting units of measure.
- Included pagination support for the list method.
This commit is contained in:
Galindo97
2026-01-12 10:36:43 -06:00
parent c2d1961994
commit 0aea981cf1
12 changed files with 1693 additions and 147 deletions

View File

@@ -0,0 +1,101 @@
/**
* API Client para Units of Measure
* Gestiona las operaciones CRUD para unidades de medida
*/
import { api } from '$lib/api';
export interface UnitOfMeasure {
id: number;
code: string;
description: string | null;
description_en: string | null;
customs_code: string | null;
american_code: string | null;
ace_code: string | null;
oma_code: string | null;
}
export interface UnitOfMeasureListResponse {
items: UnitOfMeasure[];
total: number;
page: number;
page_size: number;
}
export interface CreateUnitOfMeasureData {
code: string;
description?: string | null;
description_en?: string | null;
customs_code?: string | null;
american_code?: string | null;
ace_code?: string | null;
oma_code?: string | null;
}
export interface UpdateUnitOfMeasureData {
code?: string;
description?: string | null;
description_en?: string | null;
customs_code?: string | null;
american_code?: string | null;
ace_code?: string | null;
oma_code?: string | null;
}
/**
* API para Units of Measure
*/
export const unitsOfMeasureApi = {
/**
* Lista todas las unidades de medida con paginación
* @param companyId - ID de la compañía
* @param page - Número de página (por defecto 1)
* @param pageSize - Tamaño de página (por defecto 50)
*/
list: (companyId: number, page = 1, pageSize = 50) => {
const params = new URLSearchParams({
company_id: companyId.toString(),
page: page.toString(),
page_size: pageSize.toString()
});
return api.get<UnitOfMeasureListResponse>(
`/v1/a76/units-of-measure?${params.toString()}`
);
},
/**
* Obtiene una unidad de medida por ID
* @param companyId - ID de la compañía
* @param id - ID de la unidad de medida
*/
get: (companyId: number, id: number) =>
api.get<UnitOfMeasure>(`/v1/a76/units-of-measure/${id}?company_id=${companyId}`),
/**
* Crea una nueva unidad de medida
* @param companyId - ID de la compañía
* @param data - Datos de la unidad de medida a crear
*/
create: (companyId: number, data: CreateUnitOfMeasureData) =>
api.post<UnitOfMeasure>(`/v1/a76/units-of-measure?company_id=${companyId}`, data),
/**
* Actualiza una unidad de medida existente
* @param companyId - ID de la compañía
* @param id - ID de la unidad de medida a actualizar
* @param data - Datos a actualizar
*/
update: (companyId: number, id: number, data: UpdateUnitOfMeasureData) =>
api.put<UnitOfMeasure>(
`/v1/a76/units-of-measure/${id}?company_id=${companyId}`,
data
),
/**
* Elimina una unidad de medida
* @param companyId - ID de la compañía
* @param id - ID de la unidad de medida a eliminar
*/
delete: (companyId: number, id: number) =>
api.delete(`/v1/a76/units-of-measure/${id}?company_id=${companyId}`)
};

View File

@@ -48,7 +48,7 @@ export const materialTypesApi = {
params.append('type', type);
}
return api.get<MaterialTypeListResponse>(
`/v1/public/refrence_data/material-types?${params.toString()}`
`/v1/public/refrence_data/material-types/?${params.toString()}`
);
},
@@ -56,14 +56,14 @@ export const materialTypesApi = {
* Obtiene un tipo de material por key
* @param key - Clave del tipo de material
*/
get: (key: string) => api.get<MaterialType>(`/v1/public/refrence_data/material-types/${key}`),
get: (key: string) => api.get<MaterialType>(`/v1/public/refrence_data/material-types/${key}/`),
/**
* Crea un nuevo tipo de material
* @param data - Datos del tipo de material a crear
*/
create: (data: CreateMaterialTypeData) =>
api.post<MaterialType>('/v1/public/refrence_data/material-types', data),
api.post<MaterialType>('/v1/public/refrence_data/material-types/', data),
/**
* Actualiza un tipo de material existente
@@ -71,11 +71,11 @@ export const materialTypesApi = {
* @param data - Datos a actualizar
*/
update: (key: string, data: UpdateMaterialTypeData) =>
api.put<MaterialType>(`/v1/public/refrence_data/material-types/${key}`, data),
api.put<MaterialType>(`/v1/public/refrence_data/material-types/${key}/`, data),
/**
* Elimina un tipo de material
* @param key - Clave del tipo de material a eliminar
*/
delete: (key: string) => api.delete(`/v1/public/refrence_data/material-types/${key}`)
delete: (key: string) => api.delete(`/v1/public/refrence_data/material-types/${key}/`)
};

View File

@@ -7,6 +7,7 @@
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 { unitsOfMeasureApi, type UnitOfMeasure } from "$lib/api/dashboard/a76/units_of_measure";
import { clientsProvidersApi, type ClientProvider } from "$lib/api/dashboard/a76/clients-providers";
import { companyStore } from "$lib/stores/company.svelte";
import { onMount } from 'svelte';
@@ -43,6 +44,8 @@
let error = $state<string | null>(null);
let materialTypes = $state<MaterialType[]>([]);
let loadingMaterialTypes = $state(false);
let unitsOfMeasure = $state<UnitOfMeasure[]>([]);
let loadingUnitsOfMeasure = $state(false);
let clients = $state<ClientProvider[]>([]);
let loadingClients = $state(false);
@@ -51,15 +54,15 @@
let selectedMaterialValue = $state<string>('');
let selectedPhysicalReviewValue = $state<number>(0);
// Cargar tipos de materiales y clientes al montar
// Cargar tipos de materiales, unidades de medida y clientes al montar
onMount(async () => {
const companyId = companyStore.activeCompany?.id;
if (!companyId) return;
// Cargar tipos de materiales
// Cargar tipos de materiales (solo ACTIVO FIJO)
loadingMaterialTypes = true;
try {
const response = await materialTypesApi.list(1, 100);
const response = await materialTypesApi.list(1, 100, 'ACTIVO FIJO');
if (response.data) {
materialTypes = response.data.items;
}
@@ -69,6 +72,19 @@
loadingMaterialTypes = false;
}
// Cargar unidades de medida
loadingUnitsOfMeasure = true;
try {
const response = await unitsOfMeasureApi.list(companyId, 1, 100);
if (response.data) {
unitsOfMeasure = response.data.items;
}
} catch (e) {
console.error('Error loading units of measure:', e);
} finally {
loadingUnitsOfMeasure = false;
}
// Cargar clientes
loadingClients = true;
try {
@@ -216,31 +232,9 @@
}
}
// Opciones de unidades de medida (puedes expandir esto)
const unitOptions = [
{ value: 'KG', label: 'Kilogramos (KG)' },
{ value: 'LB', label: 'Libras (LB)' },
{ value: 'MT', label: 'Metros (MT)' },
{ value: 'PZ', label: 'Piezas (PZ)' },
{ value: 'LT', label: 'Litros (LT)' },
{ value: 'M3', label: 'Metros Cúbicos (M3)' },
{ value: 'TON', label: 'Toneladas (TON)' }
];
// Funciones para obtener valores seleccionados
function getSelectedMaterialType() {
if (!formData.material_key) return null;
const found = materialTypes.find(mt => mt.key === formData.material_key);
return found ? { value: found.key, label: `${found.key} - ${found.description}` } : null;
}
function getSelectedUnit() {
return unitOptions.find(opt => opt.value === formData.unit_of_measure) || unitOptions[0];
}
function getSelectedPhysicalReview() {
return { value: formData.physical_review, label: formData.physical_review === 1 ? 'Sí' : 'No' };
}
</script>
<Dialog.Root bind:open onOpenChange={handleOpenChange}>
@@ -409,18 +403,34 @@
</div>
</div>
<!-- Unidad de Medida -->
<!-- Unidad de Medida Comercial -->
<div class="space-y-2">
<Label for="unit_of_measure" class="required">Unidad de Medida</Label>
<select
bind:value={formData.unit_of_measure}
disabled={loading}
class="border-input bg-background selection:bg-primary dark:bg-input/30 selection:text-primary-foreground ring-offset-background placeholder:text-muted-foreground shadow-xs flex h-9 w-full min-w-0 rounded-md border px-3 py-1 text-base outline-none transition-[color,box-shadow] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive"
>
{#each unitOptions as unit}
<option value={unit.value}>{unit.label}</option>
{/each}
</select>
<Label for="unit_of_measure" class="required">U.M. Comercial</Label>
{#if loadingUnitsOfMeasure}
<div class="flex items-center gap-2 text-sm text-muted-foreground">
<div class="h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent"></div>
Cargando unidades de medida...
</div>
{:else if unitsOfMeasure.length > 0}
<select
bind:value={formData.unit_of_measure}
disabled={loading}
class="border-input bg-background selection:bg-primary dark:bg-input/30 selection:text-primary-foreground ring-offset-background placeholder:text-muted-foreground shadow-xs flex h-9 w-full min-w-0 rounded-md border px-3 py-1 text-base outline-none transition-[color,box-shadow] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive"
>
{#each unitsOfMeasure as unit}
<option value={unit.code}>
{unit.code}{unit.description ? ` - ${unit.description}` : ''}
</option>
{/each}
</select>
{:else}
<Input
id="unit_of_measure"
bind:value={formData.unit_of_measure}
placeholder="No hay unidades de medida disponibles"
disabled={true}
/>
{/if}
</div>
<!-- Revisión Física -->

View File

@@ -6,6 +6,7 @@
import { Folder } from 'lucide-svelte';
import { toast } from 'svelte-sonner';
import { materialTypesApi, type MaterialType } from '$lib/api/dashboard/refrence_data/material_types';
import { unitsOfMeasureApi } from '$lib/api/dashboard/a76/units_of_measure';
import { getTariffFractions, type TariffFraction } from '$lib/api/dashboard/a76/general_catalogs/tariff-fractions';
import { getUSTariffFractions, type USTariffFraction } from '$lib/api/dashboard/a76/general_catalogs/us-tariff-fractions';
import { getDepreciationCatalog, type DepreciationCatalog } from '$lib/api/dashboard/a76/general_catalogs/depreciation-catalog';
@@ -33,14 +34,8 @@
claveOMA: string;
}
// Datos de unidades de medida
const unitsOfMeasureData: UnitOfMeasure[] = [
{ code: 'BARR', description: 'BARRIL', descriptionEnglish: 'BARREL', claveMexicana: '8', claveAmericana: 'BBL', claveACE: '', claveOMA: 'BLL' },
{ code: 'BD FT', description: 'PIE TABLA', descriptionEnglish: 'BD FEET', claveMexicana: '5', claveAmericana: 'FT', claveACE: '', claveOMA: 'BFT' },
{ code: 'BOLS', description: 'BOLSA', descriptionEnglish: 'BAG', claveMexicana: '6', claveAmericana: 'PCS', claveACE: '', claveOMA: 'BG' },
{ code: 'KGS', description: 'KILOGRAMOS', descriptionEnglish: 'KGS', claveMexicana: '1', claveAmericana: 'KG2', claveACE: '', claveOMA: 'KGM' },
{ code: 'PZA', description: 'PIEZA', descriptionEnglish: 'PCS', claveMexicana: '6', claveAmericana: 'PCS', claveACE: '', claveOMA: 'C62_1' },
];
// Datos de unidades de medida (se cargan desde el API)
let unitsOfMeasureData: UnitOfMeasure[] = $state([]);
// Estado del formulario
let formData = $state({
@@ -195,6 +190,28 @@
);
// Funciones
async function loadUnitsOfMeasure() {
if (unitsOfMeasureData.length > 0) return;
try {
const companyId = companyStore.activeCompany?.id;
if (!companyId) return;
const response = await unitsOfMeasureApi.list(companyId, 1, 100);
if (response.data) {
unitsOfMeasureData = response.data.items.map(item => ({
code: item.code,
description: item.description || '',
descriptionEnglish: item.description_en || '',
claveMexicana: item.customs_code || '',
claveAmericana: item.american_code || '',
claveACE: item.ace_code || '',
claveOMA: item.oma_code || ''
}));
}
} catch (error) {
console.error('Error cargando units of measure:', error);
}
}
async function openMaterialSearch() {
showMaterialDialog = true;
searchMaterial = '';
@@ -202,7 +219,7 @@
try {
const companyId = companyStore.activeCompany?.id;
if (!companyId) return;
const response = await materialTypesApi.list(1, 1000);
const response = await materialTypesApi.list(1, 100, 'ACTIVO FIJO');
if (response.data) {
materialTypes = response.data.items;
}
@@ -222,6 +239,7 @@
async function openUnitOfMeasureSearch() {
showUnitDialog = true;
searchUnit = '';
await loadUnitsOfMeasure();
}
function selectUnit(unit: UnitOfMeasure) {