Errores corregidos y despliegie normal decatalgos en trailers
This commit is contained in:
@@ -22,12 +22,16 @@ async def list_states(
|
||||
page: int = Query(1, ge=1, description="Número de página"),
|
||||
page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"),
|
||||
search: str = Query(None, description="Término de búsqueda"),
|
||||
country_id: str = Query(None, description="Filtrar por país (m3_key)"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
skip = (page - 1) * page_size
|
||||
query = db.query(State)
|
||||
|
||||
if country_id:
|
||||
query = query.filter(State.m3_key == country_id)
|
||||
|
||||
if search:
|
||||
search_filter = f"%{search}%"
|
||||
query = query.filter(
|
||||
|
||||
@@ -40,11 +40,14 @@ export const statesApi = {
|
||||
* Lista todos los estados con paginación y búsqueda
|
||||
* 🛡️ CORREGIDO: Ahora requiere companyId
|
||||
*/
|
||||
list: (companyId: number, page = 1, pageSize = 50, search?: string) => {
|
||||
list: (companyId: number, page = 1, pageSize = 50, search?: string, countryId?: string) => {
|
||||
let url = `/v1/public/reference_data/states/?company_id=${companyId}&page=${page}&page_size=${pageSize}`;
|
||||
if (search) {
|
||||
url += `&search=${encodeURIComponent(search)}`;
|
||||
}
|
||||
if (countryId) {
|
||||
url += `&country_id=${encodeURIComponent(countryId)}`;
|
||||
}
|
||||
return api.get<StateListResponse>(url);
|
||||
},
|
||||
|
||||
|
||||
@@ -16,8 +16,14 @@ export interface TrailerTypeListResponse {
|
||||
}
|
||||
|
||||
export const trailerTypesApi = {
|
||||
list: (page = 1, pageSize = 100) =>
|
||||
/**
|
||||
* Lista los tipos de trailer con paginación
|
||||
* @param companyId - ID de la empresa (requerido por RBAC)
|
||||
* @param page - Número de página
|
||||
* @param pageSize - Tamaño de página
|
||||
*/
|
||||
list: (companyId: number, page = 1, pageSize = 100) =>
|
||||
api.get<TrailerTypeListResponse>(
|
||||
`/v1/public/reference_data/trailer-types/?page=${page}&page_size=${pageSize}`
|
||||
`/v1/public/reference_data/trailer-types/?company_id=${companyId}&page=${page}&page_size=${pageSize}`
|
||||
)
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Loader2, Search } from 'lucide-svelte';
|
||||
import { statesApi, type State } from '$lib/api/dashboard/reference_data/states';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
import { m } from '$lib/i18n/messages';
|
||||
|
||||
let {
|
||||
@@ -28,7 +29,8 @@
|
||||
loading = true;
|
||||
error = '';
|
||||
try {
|
||||
const response = await statesApi.list(1, 100);
|
||||
const companyId = companyStore.activeCompany?.id || 1;
|
||||
const response = await statesApi.list(companyId, 1, 100);
|
||||
if (response.data?.items) {
|
||||
items = response.data.items;
|
||||
filteredItems = items;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import * as Table from '$lib/components/ui/table';
|
||||
import { Search, Loader2, MapPin } from 'lucide-svelte';
|
||||
import { statesApi, type State } from '$lib/api/dashboard/reference_data/states';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
// --- PROPS ---
|
||||
@@ -102,7 +103,8 @@
|
||||
// Note: statesApi.list takes page, pageSize, and searchTerm?
|
||||
// Wait, let me check statesApi.list signature again.
|
||||
// It only takes page and pageSize! I need to check if it supports search.
|
||||
const response = await statesApi.list(page, pageSize);
|
||||
const companyId = companyStore.activeCompany?.id || 1;
|
||||
const response = await statesApi.list(companyId, page, pageSize);
|
||||
|
||||
if (response.error) {
|
||||
toast.error(`Error: ${response.error}`);
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
import { browser } from '$app/environment';
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
import { getTrailerTypeDescription } from '$lib/i18n/trailer-types';
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
@@ -53,7 +54,7 @@
|
||||
];
|
||||
|
||||
const countryM3 = $derived(
|
||||
countries.find((c) => c.ame_key === formData.country)?.m3_key ?? null
|
||||
countries.find((c) => c.ame_key === formData.country || c.m3_key === formData.country)?.m3_key ?? null
|
||||
);
|
||||
|
||||
const statesFiltered = $derived(
|
||||
@@ -74,19 +75,31 @@
|
||||
};
|
||||
}
|
||||
|
||||
async function loadReferenceData() {
|
||||
async function loadReferenceData(companyId: number) {
|
||||
if (!browser) return;
|
||||
refsLoading = true;
|
||||
try {
|
||||
const [tt, cc, ss] = await Promise.all([
|
||||
trailerTypesApi.list(1, 100),
|
||||
countriesApi.list(1, 100),
|
||||
statesApi.list(1, 100)
|
||||
const [tt, cc] = await Promise.all([
|
||||
trailerTypesApi.list(companyId, 1, 100),
|
||||
countriesApi.list(companyId, 1, 100)
|
||||
]);
|
||||
if (tt.data?.items) trailerTypes = tt.data.items;
|
||||
if (cc.data?.items) countries = cc.data.items;
|
||||
if (ss.data?.items) states = ss.data.items;
|
||||
} catch {
|
||||
if (tt.data?.items) trailerTypes = [...tt.data.items];
|
||||
if (cc.data?.items) countries = [...cc.data.items];
|
||||
|
||||
// Si ya tenemos país, cargar sus estados
|
||||
if (formData.country) {
|
||||
const cM3 = countries.find(c => c.ame_key === formData.country || c.m3_key === formData.country)?.m3_key;
|
||||
if (cM3) {
|
||||
const ss = await statesApi.list(companyId, 1, 100, undefined, cM3);
|
||||
if (ss.data?.items) states = [...ss.data.items];
|
||||
}
|
||||
} else {
|
||||
// Cargar algunos estados por defecto (opcional)
|
||||
const ss = await statesApi.list(companyId, 1, 100);
|
||||
if (ss.data?.items) states = [...ss.data.items];
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error loading reference data:', e);
|
||||
trailerTypes = [];
|
||||
countries = [];
|
||||
states = [];
|
||||
@@ -95,18 +108,43 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Efecto para recargar estados cuando cambia el país
|
||||
$effect(() => {
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
if (!browser || !companyId || !formData.country) return;
|
||||
|
||||
const cM3 = countries.find(c => c.ame_key === formData.country || c.m3_key === formData.country)?.m3_key;
|
||||
if (cM3) {
|
||||
void statesApi.list(companyId, 1, 100, undefined, cM3).then(res => {
|
||||
if (res.data?.items) {
|
||||
states = [...res.data.items];
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (!open) {
|
||||
error = null;
|
||||
loading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
if (!companyId) return;
|
||||
|
||||
if (item) {
|
||||
formData = { ...item };
|
||||
formData = {
|
||||
...item,
|
||||
state: item.state ?? '',
|
||||
country: item.country ?? '',
|
||||
trailer_type_key: item.trailer_type_key ?? ''
|
||||
};
|
||||
} else {
|
||||
formData = emptyTrailerForm();
|
||||
}
|
||||
void loadReferenceData();
|
||||
|
||||
void loadReferenceData(companyId);
|
||||
});
|
||||
|
||||
async function handleSubmit() {
|
||||
@@ -222,7 +260,7 @@
|
||||
{refsLoading
|
||||
? 'Cargando tipos...'
|
||||
: formData.trailer_type_key
|
||||
? `${formData.trailer_type_key} — ${trailerTypes.find((t) => t.trailer_type_key === formData.trailer_type_key)?.description ?? ''}`
|
||||
? `${formData.trailer_type_key} — ${getTrailerTypeDescription(formData.trailer_type_key, trailerTypes.find((t) => t.trailer_type_key === formData.trailer_type_key)?.description ?? '')}`
|
||||
: '— Sin tipo (opcional) —'}
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
@@ -231,7 +269,9 @@
|
||||
<Select.Item value={t.trailer_type_key} label={t.trailer_type_key}>
|
||||
{t.trailer_type_key}
|
||||
{#if t.description}
|
||||
<span class="text-muted-foreground"> — {t.description}</span>
|
||||
<span class="text-muted-foreground">
|
||||
— {getTrailerTypeDescription(t.trailer_type_key, t.description)}</span
|
||||
>
|
||||
{/if}
|
||||
</Select.Item>
|
||||
{/each}
|
||||
|
||||
@@ -65,11 +65,12 @@
|
||||
|
||||
async function loadReferenceData() {
|
||||
if (!browser) return;
|
||||
const companyId = companyStore.activeCompany?.id || 1;
|
||||
refsLoading = true;
|
||||
try {
|
||||
const [cc, ss] = await Promise.all([
|
||||
countriesApi.list(1, 100),
|
||||
statesApi.list(1, 100)
|
||||
countriesApi.list(companyId, 1, 100),
|
||||
statesApi.list(companyId, 1, 100)
|
||||
]);
|
||||
if (cc.data?.items) countries = cc.data.items;
|
||||
if (ss.data?.items) states = ss.data.items;
|
||||
|
||||
@@ -90,16 +90,26 @@
|
||||
refsLoading = true;
|
||||
const cid = companyStore.activeCompany.id;
|
||||
try {
|
||||
const [tr, tt, cc, ss] = await Promise.all([
|
||||
const [tr, tt, cc] = await Promise.all([
|
||||
transportersApi.list(cid, { page: 1, page_size: 100 }),
|
||||
transportTypesApi.list(cid, 1, 100),
|
||||
countriesApi.list(1, 100),
|
||||
statesApi.list(1, 100)
|
||||
countriesApi.list(cid, 1, 100)
|
||||
]);
|
||||
if (tr.data?.items) transporters = tr.data.items;
|
||||
if (tt.data?.items) transportTypes = tt.data.items;
|
||||
if (cc.data?.items) countries = cc.data.items;
|
||||
if (ss.data?.items) states = ss.data.items;
|
||||
|
||||
// Si ya tenemos país, cargar sus estados
|
||||
if (formData.country) {
|
||||
const cM3 = countries.find(c => c.ame_key === formData.country)?.m3_key;
|
||||
if (cM3) {
|
||||
const ss = await statesApi.list(cid, 1, 100, undefined, cM3);
|
||||
if (ss.data?.items) states = ss.data.items;
|
||||
}
|
||||
} else {
|
||||
const ss = await statesApi.list(cid, 1, 100);
|
||||
if (ss.data?.items) states = ss.data.items;
|
||||
}
|
||||
} catch {
|
||||
transporters = [];
|
||||
transportTypes = [];
|
||||
@@ -110,6 +120,21 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Efecto para recargar estados cuando cambia el país
|
||||
$effect(() => {
|
||||
const cid = companyStore.activeCompany?.id;
|
||||
if (!browser || !cid || !formData.country) return;
|
||||
|
||||
const cM3 = countries.find(c => c.ame_key === formData.country)?.m3_key;
|
||||
if (cM3) {
|
||||
void statesApi.list(cid, 1, 100, undefined, cM3).then(res => {
|
||||
if (res.data?.items) {
|
||||
states = res.data.items;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function emptyVehicleForm(): Vehicle {
|
||||
return {
|
||||
vehicle_key: '',
|
||||
|
||||
65
frontend/src/lib/i18n/trailer-types.ts
Normal file
65
frontend/src/lib/i18n/trailer-types.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { getLocale } from '$lib/paraglide/runtime';
|
||||
|
||||
/**
|
||||
* Mapeo de traducciones para el catálogo de Tipos de Trailer (GTipoTrailer).
|
||||
* Se usa la clave (trailer_type_key) para obtener la descripción en español.
|
||||
*/
|
||||
const TRAILER_TYPE_ES: Record<string, string> = {
|
||||
'20': 'Contenedor marítimo de 20 pies - Techo abierto',
|
||||
'2B': 'Contenedor marítimo de 20 pies - Techo cerrado',
|
||||
'40': 'Contenedor marítimo de 40 pies - Techo abierto',
|
||||
'4B': 'Contenedor marítimo de 40 pies - Techo cerrado',
|
||||
'BI': 'Remolque para bebidas',
|
||||
'CB': 'Remolque de cuello de ganso',
|
||||
'CH': 'Chasis',
|
||||
'CL': 'Contenedor marítimo de otra longitud - Techo cerrado',
|
||||
'CU': 'Contenedor marítimo de otra longitud - Techo abierto',
|
||||
'CZ': 'Contenedor refrigerado',
|
||||
'DD': 'Remolque de doble caída',
|
||||
'DT': 'Remolque de caída trasera',
|
||||
'FR': 'Remolque flat rack',
|
||||
'FT': 'Plataforma / Cama plana',
|
||||
'HC': 'Remolque tolva (cubierto)',
|
||||
'HE': 'Remolque para caballos',
|
||||
'HO': 'Remolque tolva (abierto)',
|
||||
'HP': 'Remolque tolva (descarga neumática cubierto)',
|
||||
'L1': 'Pipa / Tanque (líquidos) no caldeado / no aislado',
|
||||
'L2': 'Pipa / Tanque (líquidos) caldeado / no aislado',
|
||||
'L3': 'Pipa / Tanque (líquidos) no caldeado / aislado',
|
||||
'L4': 'Pipa / Tanque (líquidos) caldeado / aislado',
|
||||
'LP': 'Remolque para troncos / tubería / postes',
|
||||
'LT': 'Remolque para ganado',
|
||||
'NC': 'Sin equipo',
|
||||
'OE': 'Otro',
|
||||
'RD': 'Remolque de rack fijo / doble caída',
|
||||
'RG': 'Góndola cerrada',
|
||||
'RO': 'Góndola abierta',
|
||||
'RS': 'Remolque de rack fijo / caída simple',
|
||||
'SD': 'Remolque de caída simple',
|
||||
'T1': 'Pipa / Tanque (gas) no caldeado / no aislado',
|
||||
'T2': 'Pipa / Tanque (gas) caldeado / no aislado',
|
||||
'T3': 'Pipa / Tanque (gas) no caldeado / aislado',
|
||||
'T4': 'Pipa / Tanque (gas) caldeado / aislado',
|
||||
'T5': 'Pipa / Tanque (químicos) no caldeado / no aislado',
|
||||
'T6': 'Pipa / Tanque (químicos) caldeado / no aislado',
|
||||
'T7': 'Pipa / Tanque (químicos) no caldeado / aislado',
|
||||
'T8': 'Pipa / Tanque (químicos) caldeado / aislado',
|
||||
'TC': 'Portavehículos / Nodriza',
|
||||
'TK': 'Pipa / Tanque (líquidos grado alimenticio)',
|
||||
'TL': 'Semirremolque',
|
||||
'TW': 'Remolque de temperatura controlada'
|
||||
};
|
||||
|
||||
/**
|
||||
* Obtiene la descripción traducida de un tipo de trailer.
|
||||
* @param key Clave del tipo de trailer (ej: '20', 'FT')
|
||||
* @param fallback Descripción original por si no hay traducción
|
||||
* @returns La descripción en el idioma activo
|
||||
*/
|
||||
export function getTrailerTypeDescription(key: string, fallback: string = ''): string {
|
||||
const locale = getLocale();
|
||||
if (locale.startsWith('es')) {
|
||||
return TRAILER_TYPE_ES[key] || fallback;
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
Reference in New Issue
Block a user