Se agrego la forma de seleccion de clase, cliente y unidades de medida
This commit is contained in:
@@ -183,7 +183,7 @@ export interface UnitOfMeasureGeneralUpdate {
|
||||
}
|
||||
|
||||
export interface UnitOfMeasureGeneralListResponse {
|
||||
items: UnitOfMeasureGeneral[];
|
||||
items: UnitOfMeasureGeneral[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
@@ -273,3 +273,38 @@ export async function updateUnitOfMeasureCustoms(id: number, data: UnitOfMeasure
|
||||
export async function deleteUnitOfMeasureCustoms(id: number, companyId: number): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/v1/a76/units-of-measure/customs/${id}/?company_id=${companyId}`);
|
||||
}
|
||||
|
||||
export interface UnitOfMeasure {
|
||||
id: number;
|
||||
code: string; // Ej: KG, PZ
|
||||
description: string | null;
|
||||
description_en: string | null;
|
||||
customs_code: string | null;
|
||||
american_code: string | null;
|
||||
created_at: string | null;
|
||||
updated_at: string | null;
|
||||
}
|
||||
|
||||
export interface UnitOfMeasureListResponse {
|
||||
items: UnitOfMeasure[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
export async function getUnitsOfMeasure(
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
companyId: number,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<UnitOfMeasureListResponse>> {
|
||||
const queryParams = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
company_id: companyId.toString(),
|
||||
...filters
|
||||
});
|
||||
// Apunta a /v1/a76/units-of-measure/ (La ruta base del router)
|
||||
return await api.get(`/v1/a76/units-of-measure/?${queryParams.toString()}`);
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
<script lang="ts">
|
||||
import * as Dialog from "$lib/components/ui/dialog";
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import { Search, Loader2, Scale, Check } from "lucide-svelte";
|
||||
import * as Table from "$lib/components/ui/table";
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
import { getUnitsOfMeasure, type UnitOfMeasure } from '$lib/api/dashboard/a76/general_catalogs/units-of-measure';
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
onSelect
|
||||
}: {
|
||||
open: boolean;
|
||||
onSelect: (item: UnitOfMeasure) => void;
|
||||
} = $props();
|
||||
|
||||
let items = $state<UnitOfMeasure[]>([]);
|
||||
let loading = $state(false);
|
||||
let searchTerm = $state("");
|
||||
let page = $state(1);
|
||||
let totalPages = $state(1);
|
||||
let searchTimeout: NodeJS.Timeout;
|
||||
|
||||
// Cargar datos al abrir
|
||||
$effect(() => {
|
||||
if (open && companyStore.activeCompany) {
|
||||
loadData();
|
||||
}
|
||||
});
|
||||
|
||||
async function loadData() {
|
||||
if (!companyStore.activeCompany) return;
|
||||
|
||||
loading = true;
|
||||
try {
|
||||
const filters = searchTerm ? { code: searchTerm } : {};
|
||||
// Nota: Si tu backend soporta búsqueda por descripción, úsalo aquí.
|
||||
// Por ahora asumo búsqueda por 'code' o 'description' según tu filtro backend.
|
||||
|
||||
const response = await getUnitsOfMeasure(page, 10, companyStore.activeCompany.id, {
|
||||
q: searchTerm // Asumiendo que tu backend tiene un filtro genérico 'q' o usa 'code'/'description'
|
||||
});
|
||||
|
||||
if (response.data) {
|
||||
items = response.data.items;
|
||||
totalPages = response.data.pages;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error cargando unidades:", error);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleSearch(e: Event) {
|
||||
const value = (e.target as HTMLInputElement).value;
|
||||
searchTerm = value;
|
||||
page = 1;
|
||||
|
||||
clearTimeout(searchTimeout);
|
||||
searchTimeout = setTimeout(() => {
|
||||
loadData();
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function handleSelect(item: UnitOfMeasure) {
|
||||
onSelect(item);
|
||||
open = false;
|
||||
}
|
||||
|
||||
function nextPage() {
|
||||
if (page < totalPages) {
|
||||
page++;
|
||||
loadData();
|
||||
}
|
||||
}
|
||||
|
||||
function prevPage() {
|
||||
if (page > 1) {
|
||||
page--;
|
||||
loadData();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open>
|
||||
<Dialog.Content class="sm:max-w-[600px]">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>Seleccionar Unidad de Medida</Dialog.Title>
|
||||
<Dialog.Description>
|
||||
Busca y selecciona una unidad del catálogo maestro.
|
||||
</Dialog.Description>
|
||||
</Dialog.Header>
|
||||
|
||||
<div class="space-y-4 py-4">
|
||||
<div class="relative">
|
||||
<Search class="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="Buscar por código (KG, PZ...) o descripción..."
|
||||
class="pl-8"
|
||||
value={searchTerm}
|
||||
oninput={handleSearch}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="rounded-md border h-[300px] overflow-auto relative">
|
||||
{#if loading}
|
||||
<div class="absolute inset-0 bg-background/50 flex items-center justify-center z-10">
|
||||
<Loader2 class="h-6 w-6 animate-spin text-primary" />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.Head class="w-[80px]">Código</Table.Head>
|
||||
<Table.Head>Descripción</Table.Head>
|
||||
<Table.Head class="text-right w-[50px]"></Table.Head>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{#if items.length === 0 && !loading}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={3} class="text-center h-24 text-muted-foreground">
|
||||
No se encontraron resultados
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{:else}
|
||||
{#each items as item}
|
||||
<Table.Row
|
||||
class="cursor-pointer hover:bg-muted/50"
|
||||
onclick={() => handleSelect(item)}
|
||||
>
|
||||
<Table.Cell class="font-mono font-bold">{item.code}</Table.Cell>
|
||||
<Table.Cell>
|
||||
<div class="flex flex-col">
|
||||
<span>{item.description || '-'}</span>
|
||||
{#if item.description_en}
|
||||
<span class="text-xs text-muted-foreground">{item.description_en}</span>
|
||||
{/if}
|
||||
</div>
|
||||
</Table.Cell>
|
||||
<Table.Cell class="text-right">
|
||||
<Button variant="ghost" size="icon" class="h-6 w-6">
|
||||
<Check class="h-4 w-4" />
|
||||
</Button>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/each}
|
||||
{/if}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-xs text-muted-foreground">Página {page} de {totalPages}</span>
|
||||
<div class="flex gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
disabled={page === 1 || loading}
|
||||
onclick={prevPage}
|
||||
>
|
||||
Anterior
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
disabled={page >= totalPages || loading}
|
||||
onclick={nextPage}
|
||||
>
|
||||
Siguiente
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
@@ -31,6 +31,7 @@
|
||||
import ClientSelectorDialog from '$lib/components/dashboard/parts/client-selector-dialog.svelte';
|
||||
import ClassSelectorDialog from '$lib/components/dashboard/parts/class-selector-dialog.svelte';
|
||||
import MaterialTypeSelectorDialog from '$lib/components/dashboard/parts/material-type-selector-dialog.svelte';
|
||||
import UnitMeasureSelectorDialog from '$lib/components/dashboard/parts/unit-measure-dialog.svelte';
|
||||
|
||||
// --- 1. IDENTIFICACIÓN ---
|
||||
let id = $derived($page.params.id === 'new' ? null : Number($page.params.id));
|
||||
@@ -45,6 +46,8 @@
|
||||
let showClientModal = $state(false);
|
||||
let showClassModal = $state(false);
|
||||
let showMaterialModal = $state(false);
|
||||
let showUOMModal = $state(false);
|
||||
let showAltUOMModal = $state(false);
|
||||
|
||||
// Descripciones Visuales
|
||||
let selectedClientName = $state("");
|
||||
@@ -61,7 +64,7 @@
|
||||
description_spanish: '',
|
||||
description_english: '',
|
||||
part_class: '',
|
||||
material_type: '', // Corregido: coincide con backend
|
||||
material_type: '',
|
||||
country_of_origin: 'MEX',
|
||||
unit_of_measure: 'PZ', // U.M. TIGIE
|
||||
|
||||
@@ -215,6 +218,14 @@
|
||||
selectedMaterialDesc = item.description;
|
||||
}
|
||||
|
||||
function handleUOMSelect(item: any) {
|
||||
formData.unit_of_measure = item.code;
|
||||
}
|
||||
|
||||
function handleAltUOMSelect(item: any) {
|
||||
formData.alternate_unit_measure = item.code;
|
||||
}
|
||||
|
||||
// --- SUBMIT ---
|
||||
async function handleSubmit() {
|
||||
error = null;
|
||||
@@ -393,24 +404,25 @@
|
||||
</div> -->
|
||||
|
||||
<div class="grid gap-2">
|
||||
<Label for="uom" class="required">Comercial</Label>
|
||||
<Select.Root type="single" bind:value={formData.unit_of_measure}>
|
||||
<Select.Trigger id="uom" class="font-mono">
|
||||
{formData.unit_of_measure || 'Seleccione...'}
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-h-[300px]">
|
||||
<Select.Group>
|
||||
<Select.Label>Comunes</Select.Label>
|
||||
<Select.Item value="PZ">Pieza (PZ)</Select.Item>
|
||||
<Select.Item value="KG">Kilogramo (KG)</Select.Item>
|
||||
<Select.Item value="L">Litro (L)</Select.Item>
|
||||
<Select.Item value="M">Metro Lineal (M)</Select.Item>
|
||||
<Select.Item value="M2">Metro Cuadrado (M2)</Select.Item>
|
||||
<Select.Item value="JGO">Juego (JGO)</Select.Item>
|
||||
<Select.Item value="PAR">Par (PAR)</Select.Item>
|
||||
</Select.Group>
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
<Label for="uom" class="required">Unidad de Medida (TIGIE)</Label>
|
||||
<div class="flex gap-2">
|
||||
<div class="relative flex-1">
|
||||
<div class="absolute left-3 top-2.5 text-muted-foreground">
|
||||
<Scale class="h-4 w-4" />
|
||||
</div>
|
||||
<Input
|
||||
id="uom"
|
||||
bind:value={formData.unit_of_measure}
|
||||
placeholder="Seleccione..."
|
||||
class="pl-9 font-mono cursor-pointer"
|
||||
readonly
|
||||
onclick={() => showUOMModal = true}
|
||||
/>
|
||||
</div>
|
||||
<Button variant="outline" size="icon" type="button" onclick={() => showUOMModal = true}>
|
||||
<Search class="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -563,27 +575,25 @@
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<Label for="alt_um">Unidad de Medida Comercial</Label>
|
||||
<Select.Root type="single" bind:value={formData.alternate_unit_measure}>
|
||||
<Select.Trigger id="alt_um" class="font-mono">
|
||||
{formData.alternate_unit_measure || 'Seleccione...'}
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-h-[300px]">
|
||||
<Select.Group>
|
||||
<Select.Label>Comunes</Select.Label>
|
||||
<Select.Item value="PZ">Pieza (PZ)</Select.Item>
|
||||
<Select.Item value="KG">Kilogramo (KG)</Select.Item>
|
||||
<Select.Item value="L">Litro (L)</Select.Item>
|
||||
<Select.Item value="M">Metro Lineal (M)</Select.Item>
|
||||
<Select.Item value="M2">Metro Cuadrado (M2)</Select.Item>
|
||||
<Select.Item value="JGO">Juego (JGO)</Select.Item>
|
||||
<Select.Item value="PAR">Par (PAR)</Select.Item>
|
||||
<Select.Item value="SET">Set (SET)</Select.Item>
|
||||
<Select.Item value="CAJA">Caja (CAJA)</Select.Item>
|
||||
<Select.Item value="PK">Paquete (PK)</Select.Item>
|
||||
</Select.Group>
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
<Label for="uom" class="required">Unidad de medida alternativa</Label>
|
||||
<div class="flex gap-2">
|
||||
<div class="relative flex-1">
|
||||
<div class="absolute left-3 top-2.5 text-muted-foreground">
|
||||
<Scale class="h-4 w-4" />
|
||||
</div>
|
||||
<Input
|
||||
id="uom"
|
||||
bind:value={formData.alternate_unit_measure}
|
||||
placeholder="Seleccione..."
|
||||
class="pl-9 font-mono cursor-pointer"
|
||||
readonly
|
||||
onclick={() => showAltUOMModal = true}
|
||||
/>
|
||||
</div>
|
||||
<Button variant="outline" size="icon" type="button" onclick={() => showAltUOMModal = true}>
|
||||
<Search class="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -658,6 +668,16 @@
|
||||
onSelect={handleMaterialSelect}
|
||||
/>
|
||||
|
||||
<UnitMeasureSelectorDialog
|
||||
bind:open={showUOMModal}
|
||||
onSelect={handleUOMSelect}
|
||||
/>
|
||||
|
||||
<UnitMeasureSelectorDialog
|
||||
bind:open={showAltUOMModal}
|
||||
onSelect={handleAltUOMSelect}
|
||||
/>
|
||||
|
||||
<style>
|
||||
:global(.required::after) {
|
||||
content: " *";
|
||||
|
||||
Reference in New Issue
Block a user