This commit is contained in:
KevinMrkz3221
2025-12-02 19:36:22 -06:00
parent 6eae26acf3
commit baf76cf414
6 changed files with 1312 additions and 9 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -23,7 +23,7 @@ class QClasses(Base, TenantScopedMixin):
ForeignKeyConstraint(
["company_id"], ["a76.company.id"], name="fk_qclasses_company"
),
ForeignKeyConstraint(["class_id"], ["classes.id"], name="fk_qclasses_classes"),
ForeignKeyConstraint(["class_id"], ["a76.classes.id"], name="fk_qclasses_classes"),
{"schema": "a24"},
)

View File

@@ -14,7 +14,7 @@ class SClasses(Base, TenantScopedMixin):
["company_id"], ["a76.company.id"], name="fk_sclasses_company"
),
ForeignKeyConstraint(
["class_id"], ["a76.clases.class_id"], name="fk_sclasses_classes"
["class_id"], ["a76.classes.id"], name="fk_sclasses_classes"
),
PrimaryKeyConstraint("id", name="sclases_pk"),
{"schema": "a24"},

View File

@@ -47,6 +47,7 @@ class Class(Base, TenantScopedMixin, TimestampMixin):
UniqueConstraint(
"tenant_id",
"company_id",
"client_id",
"class_code",
name="ufa_classes_client_id_class_code",
),

View File

@@ -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()

View File

@@ -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<string | null>(null);
let materialTypes = $state<MaterialType[]>([]);
let loadingMaterialTypes = $state(false);
let clients = $state<ClientProviderBasic[]>([]);
let loadingClients = $state(false);
// Variables para controlar los selects
let selectedUnitValue = $state<string>('KG');
let selectedMaterialValue = $state<string>('');
let selectedPhysicalReviewValue = $state<number>(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}
<div class="rounded-md bg-blue-50 border border-blue-200 p-3">
<div class="flex items-center gap-2">
<Home class="h-4 w-4 text-blue-600" />
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="text-blue-600"
>
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
<polyline points="9 22 9 12 15 12 15 22" />
</svg>
<div>
<p class="text-sm font-medium text-blue-900">
{companyStore.activeCompany.name}
@@ -262,6 +303,34 @@
</div>
{/if}
<!-- Cliente -->
<div class="space-y-2">
<Label for="client_id" class="required">Cliente</Label>
{#if loadingClients}
<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 clientes...
</div>
{:else if clients.length > 0}
<select
bind:value={formData.client_id}
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"
>
<option value="">Selecciona un cliente</option>
{#each clients as client}
<option value={client.id}>
{client.name} ({client.rfc})
</option>
{/each}
</select>
{:else}
<div class="text-sm text-muted-foreground">
No hay clientes disponibles
</div>
{/if}
</div>
<!-- Código de Clase -->
<div class="space-y-2">
<Label for="class_code" class="required">Código de Clase</Label>
@@ -413,7 +482,26 @@
</Button>
<Button type="submit" disabled={loading}>
{#if loading}
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
<svg
class="mr-2 h-4 w-4 animate-spin"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
></circle>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
Guardando...
{:else}
{isEdit ? 'Actualizar' : 'Crear'}