feature/tipo-operacion-invoice
This commit is contained in:
@@ -193,6 +193,18 @@
|
|||||||
"title": "Invoices",
|
"title": "Invoices",
|
||||||
"description": "Manage system invoices"
|
"description": "Manage system invoices"
|
||||||
},
|
},
|
||||||
|
"titles": {
|
||||||
|
"base": "INVOICE CATALOG",
|
||||||
|
"import": "IMPORT",
|
||||||
|
"export": "EXPORT",
|
||||||
|
"import_temporal": "TEMPORARY IMPORT",
|
||||||
|
"import_definitive": "DEFINITIVE IMPORT",
|
||||||
|
"import_mexican": "MEXICAN PURCHASES",
|
||||||
|
"import_regime_change": "REGIME CHANGE AND REGULARIZATION",
|
||||||
|
"import_repair": "IMPORT REPAIR",
|
||||||
|
"export_definitive": "DEFINITIVE EXIT",
|
||||||
|
"export_repair": "REPAIR"
|
||||||
|
},
|
||||||
"filters": {
|
"filters": {
|
||||||
"operation_label": "Operation Type",
|
"operation_label": "Operation Type",
|
||||||
"operation_all_option": "Operation: All",
|
"operation_all_option": "Operation: All",
|
||||||
|
|||||||
@@ -192,6 +192,18 @@
|
|||||||
"title": "Facturas",
|
"title": "Facturas",
|
||||||
"description": "Gestiona las facturas del sistema"
|
"description": "Gestiona las facturas del sistema"
|
||||||
},
|
},
|
||||||
|
"titles": {
|
||||||
|
"base": "CATALOGO DE FACTURAS",
|
||||||
|
"import": "DE IMPORTACION",
|
||||||
|
"export": "DE EXPORTACION",
|
||||||
|
"import_temporal": "DE IMPORTACION TEMPORAL",
|
||||||
|
"import_definitive": "DE IMPORTACION DEFINITIVA",
|
||||||
|
"import_mexican": "DE COMPRAS MEXICANAS",
|
||||||
|
"import_regime_change": "DE CAMBIO DE REGIMEN Y REGULARIZACION",
|
||||||
|
"import_repair": "DE IMPO. DE REPARACION",
|
||||||
|
"export_definitive": "DE SALIDA DEFINITIVA",
|
||||||
|
"export_repair": "DE REPARACION"
|
||||||
|
},
|
||||||
"filters": {
|
"filters": {
|
||||||
"operation_label": "Tipo de Operacion",
|
"operation_label": "Tipo de Operacion",
|
||||||
"operation_all_option": "Operacion: Todas",
|
"operation_all_option": "Operacion: Todas",
|
||||||
|
|||||||
@@ -82,7 +82,11 @@
|
|||||||
let loadingTrigger = $state<HTMLDivElement>();
|
let loadingTrigger = $state<HTMLDivElement>();
|
||||||
|
|
||||||
// Intersection Observer para detectar cuando el usuario llega al final
|
// Intersection Observer para detectar cuando el usuario llega al final
|
||||||
onMount(() => {
|
$effect(() => {
|
||||||
|
const target = loadingTrigger;
|
||||||
|
const root = scrollContainer;
|
||||||
|
if (!target) return;
|
||||||
|
|
||||||
const observer = new IntersectionObserver(
|
const observer = new IntersectionObserver(
|
||||||
(entries) => {
|
(entries) => {
|
||||||
const [entry] = entries;
|
const [entry] = entries;
|
||||||
@@ -91,14 +95,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
root: scrollContainer,
|
root: root,
|
||||||
threshold: 0.1
|
threshold: 0.1
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if (loadingTrigger) {
|
observer.observe(target);
|
||||||
observer.observe(loadingTrigger);
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
observer.disconnect();
|
observer.disconnect();
|
||||||
|
|||||||
@@ -152,56 +152,26 @@
|
|||||||
<!-- Datos Principales en una fila compacta (reusable across tabs) -->
|
<!-- Datos Principales en una fila compacta (reusable across tabs) -->
|
||||||
<div class="flex flex-wrap items-end gap-3 pb-3">
|
<div class="flex flex-wrap items-end gap-3 pb-3">
|
||||||
{#if !isSettings}
|
{#if !isSettings}
|
||||||
|
<!-- Campos no manipulables pero visibles -->
|
||||||
<div class="min-w-[100px] flex-1 space-y-1">
|
<div class="min-w-[100px] flex-1 space-y-1">
|
||||||
<Label for="operation_type" class="text-xs"
|
<Label class="text-xs text-muted-foreground"
|
||||||
>{m.invoice_edit_form_operation_type_label()}<span class="text-red-500">*</span></Label
|
>{m.invoice_edit_form_operation_type_label()}</Label
|
||||||
>
|
>
|
||||||
<Select.Root
|
<p class="h-8 text-sm font-medium flex items-center">
|
||||||
type="single"
|
{formData.operation_type
|
||||||
value={formData.operation_type || ''}
|
? (formData.operation_type === 'exp'
|
||||||
onValueChange={(v) => {
|
? m.invoice_edit_form_operation_type_export()
|
||||||
formData.operation_type = v;
|
: m.invoice_edit_form_operation_type_import())
|
||||||
}}
|
: '...'}
|
||||||
>
|
</p>
|
||||||
<Select.Trigger id="operation_type" class="h-8 text-sm">
|
|
||||||
<span class="truncate">
|
|
||||||
{formData.operation_type
|
|
||||||
? (formData.operation_type === 'exp'
|
|
||||||
? m.invoice_edit_form_operation_type_export()
|
|
||||||
: m.invoice_edit_form_operation_type_import())
|
|
||||||
: '...'}
|
|
||||||
</span>
|
|
||||||
</Select.Trigger>
|
|
||||||
<Select.Content>
|
|
||||||
<Select.Item value="exp">{m.invoice_edit_form_operation_type_export()}</Select.Item>
|
|
||||||
<Select.Item value="imp">{m.invoice_edit_form_operation_type_import()}</Select.Item>
|
|
||||||
</Select.Content>
|
|
||||||
</Select.Root>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="min-w-[100px] flex-1 space-y-1">
|
<div class="min-w-[100px] flex-1 space-y-1">
|
||||||
<Label for="operation_type" class="text-xs"
|
<Label class="text-xs text-muted-foreground"
|
||||||
>{m.invoice_edit_form_invoice_type_label()} <span class="text-red-500">*</span></Label
|
>{m.invoice_edit_form_invoice_type_label()}</Label
|
||||||
>
|
>
|
||||||
<Select.Root
|
<p class="h-8 text-sm font-medium flex items-center">
|
||||||
type="single"
|
{formData.invoice_type ? `${formData.invoice_type}` : '...'}
|
||||||
value={formData.invoice_type || ''}
|
</p>
|
||||||
onValueChange={(v) => {
|
|
||||||
formData.invoice_type = v ?? '';
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Select.Trigger id="invoice_type" class="h-7 text-xs">
|
|
||||||
<span class="truncate">
|
|
||||||
{formData.invoice_type ? `${formData.invoice_type}` : '...'}
|
|
||||||
</span>
|
|
||||||
</Select.Trigger>
|
|
||||||
<Select.Content class="max-h-[300px]">
|
|
||||||
{#each filteredInvoiceTypes as type}
|
|
||||||
<Select.Item value={type.key}>
|
|
||||||
{type.key} - {type.description}
|
|
||||||
</Select.Item>
|
|
||||||
{/each}
|
|
||||||
</Select.Content>
|
|
||||||
</Select.Root>
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|||||||
@@ -443,7 +443,7 @@ export function getSidebarData(): SidebarData {
|
|||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
title: m["sidebar.export_invoices.exportation"](),
|
title: m["sidebar.export_invoices.exportation"](),
|
||||||
url: "/dashboard/invoices?operation_type=exp",
|
url: "/dashboard/invoices?operation_type=exp&invoice_type=EXDEF",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: m["sidebar.export_invoices.repair"](),
|
title: m["sidebar.export_invoices.repair"](),
|
||||||
|
|||||||
@@ -6,14 +6,22 @@ import {
|
|||||||
} from '$lib/server/api';
|
} from '$lib/server/api';
|
||||||
|
|
||||||
export const load: PageServerLoad = async ({ fetch, cookies, url, parent }) => {
|
export const load: PageServerLoad = async ({ fetch, cookies, url, parent }) => {
|
||||||
|
const invoiceType = url.searchParams.get('invoice_type');
|
||||||
|
const operationType = url.searchParams.get('operation_type');
|
||||||
|
|
||||||
|
if (!operationType) {
|
||||||
|
throw redirect(302, '/dashboard');
|
||||||
|
}
|
||||||
|
|
||||||
// Esperar a que el layout padre valide/refresque el token
|
// Esperar a que el layout padre valide/refresque el token
|
||||||
const parentData = await parent();
|
const parentData = await parent();
|
||||||
|
|
||||||
// Verificar autenticación
|
// Verificar autenticación
|
||||||
const { accessToken } = getAuthTokens(cookies);
|
const tokens = getAuthTokens(cookies);
|
||||||
|
const accessToken = tokens.accessToken;
|
||||||
|
|
||||||
if (!accessToken) {
|
if (!accessToken) {
|
||||||
throw redirect(302, '/login');
|
throw redirect(302, '/auth/login');
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -43,7 +51,6 @@ export const load: PageServerLoad = async ({ fetch, cookies, url, parent }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Construir parámetros de consulta
|
// Construir parámetros de consulta
|
||||||
// Los query parameters invoice_type y operation_type se pasan al endpoint
|
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
company_id: companyId.toString(),
|
company_id: companyId.toString(),
|
||||||
page: '1',
|
page: '1',
|
||||||
@@ -51,8 +58,6 @@ export const load: PageServerLoad = async ({ fetch, cookies, url, parent }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Agregar filtros opcionales desde query parameters
|
// Agregar filtros opcionales desde query parameters
|
||||||
const invoiceType = url.searchParams.get('invoice_type');
|
|
||||||
const operationType = url.searchParams.get('operation_type');
|
|
||||||
const invoiceNumber = url.searchParams.get('invoice_number');
|
const invoiceNumber = url.searchParams.get('invoice_number');
|
||||||
const projectNumber = url.searchParams.get('project_number');
|
const projectNumber = url.searchParams.get('project_number');
|
||||||
const year = url.searchParams.get('year');
|
const year = url.searchParams.get('year');
|
||||||
@@ -70,14 +75,14 @@ export const load: PageServerLoad = async ({ fetch, cookies, url, parent }) => {
|
|||||||
{},
|
{},
|
||||||
cookies,
|
cookies,
|
||||||
fetch,
|
fetch,
|
||||||
'/login'
|
'/auth/login'
|
||||||
),
|
),
|
||||||
authenticatedFetch(
|
authenticatedFetch(
|
||||||
'v1/public/reference_data/invoice-types?page=1&page_size=100',
|
'v1/public/reference_data/invoice-types?page=1&page_size=100',
|
||||||
{},
|
{},
|
||||||
cookies,
|
cookies,
|
||||||
fetch,
|
fetch,
|
||||||
'/login'
|
'/auth/login'
|
||||||
)
|
)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -90,9 +95,9 @@ export const load: PageServerLoad = async ({ fetch, cookies, url, parent }) => {
|
|||||||
error: 'Error al cargar facturas',
|
error: 'Error al cargar facturas',
|
||||||
companies: parentData.companies || [],
|
companies: parentData.companies || [],
|
||||||
currentCompanyId: companyId,
|
currentCompanyId: companyId,
|
||||||
// Preservar los filtros aplicados
|
|
||||||
filters: {
|
filters: {
|
||||||
invoice_type: invoiceType,
|
invoice_type: invoiceType,
|
||||||
|
operation_type: operationType
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -108,7 +113,6 @@ export const load: PageServerLoad = async ({ fetch, cookies, url, parent }) => {
|
|||||||
companies: parentData.companies || [],
|
companies: parentData.companies || [],
|
||||||
currentCompanyId: companyId,
|
currentCompanyId: companyId,
|
||||||
invoiceTypes: invoiceTypesData.items || [],
|
invoiceTypes: invoiceTypesData.items || [],
|
||||||
// Preservar los filtros aplicados
|
|
||||||
filters: {
|
filters: {
|
||||||
invoice_type: invoiceType,
|
invoice_type: invoiceType,
|
||||||
operation_type: operationType,
|
operation_type: operationType,
|
||||||
@@ -119,6 +123,9 @@ export const load: PageServerLoad = async ({ fetch, cookies, url, parent }) => {
|
|||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading invoices:', error);
|
console.error('Error loading invoices:', error);
|
||||||
|
if (error instanceof Response && error.status >= 300 && error.status < 400) {
|
||||||
|
throw error; // Propagate redirects
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
items: [],
|
items: [],
|
||||||
total: 0,
|
total: 0,
|
||||||
|
|||||||
@@ -274,6 +274,29 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Título dinámico según el tipo de operación y factura
|
||||||
|
const viewTitle = $derived.by(() => {
|
||||||
|
const op = filters.operation_type;
|
||||||
|
const type = filters.invoice_type;
|
||||||
|
|
||||||
|
const base = m.invoice_list_titles_base();
|
||||||
|
|
||||||
|
if (op === 'imp') {
|
||||||
|
if (type === 'TEM') return `${base} ${m.invoice_list_titles_import_temporal()}`;
|
||||||
|
if (type === 'DEF') return `${base} ${m.invoice_list_titles_import_definitive()}`;
|
||||||
|
if (type === 'MEX') return `${base} ${m.invoice_list_titles_import_mexican()}`;
|
||||||
|
if (type === 'CR') return `${base} ${m.invoice_list_titles_import_regime_change()}`;
|
||||||
|
if (type === 'REP') return `${base} ${m.invoice_list_titles_import_repair()}`;
|
||||||
|
return `${base} ${m.invoice_list_titles_import()}`;
|
||||||
|
} else if (op === 'exp') {
|
||||||
|
if (type === 'EXDEF') return `${base} ${m.invoice_list_titles_export_definitive()}`;
|
||||||
|
if (type === 'REPAR') return `${base} ${m.invoice_list_titles_export_repair()}`;
|
||||||
|
return `${base} ${m.invoice_list_titles_export()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.invoice_list_header_title();
|
||||||
|
});
|
||||||
|
|
||||||
async function loadMore() {
|
async function loadMore() {
|
||||||
if (loading || !hasMore) return;
|
if (loading || !hasMore) return;
|
||||||
|
|
||||||
@@ -1360,35 +1383,38 @@
|
|||||||
</a>
|
</a>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-2xl font-bold tracking-tight">{m.invoice_list_header_title()}</h1>
|
<h1 class="text-2xl font-bold tracking-tight">{viewTitle}</h1>
|
||||||
<p class="text-muted-foreground">{m.invoice_list_header_description()}</p>
|
<p class="text-muted-foreground">{m.invoice_list_header_description()}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-wrap items-center gap-2">
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
<select
|
<!-- Filtros ocultos por requerimiento -->
|
||||||
id="filter-operation-type"
|
<div class="hidden">
|
||||||
bind:value={filters.operation_type}
|
<select
|
||||||
class="flex h-9 w-[180px] rounded-md border border-input bg-card px-3 py-1 text-sm shadow-sm ring-offset-background focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none"
|
id="filter-operation-type"
|
||||||
title={m.invoice_list_filters_operation_label()}
|
bind:value={filters.operation_type}
|
||||||
>
|
class="flex h-9 w-[180px] rounded-md border border-input bg-card px-3 py-1 text-sm shadow-sm ring-offset-background focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none"
|
||||||
{#each operationTypeOptions as option}
|
title={m.invoice_list_filters_operation_label()}
|
||||||
<option value={option.value}>
|
>
|
||||||
{option.value === '' ? m.invoice_list_filters_operation_all_option() : option.label}
|
{#each operationTypeOptions as option}
|
||||||
</option>
|
<option value={option.value}>
|
||||||
{/each}
|
{option.value === '' ? m.invoice_list_filters_operation_all_option() : option.label}
|
||||||
</select>
|
</option>
|
||||||
|
{/each}
|
||||||
<select
|
</select>
|
||||||
id="filter-invoice-type"
|
|
||||||
bind:value={filters.invoice_type}
|
<select
|
||||||
class="flex h-9 w-[220px] rounded-md border border-input bg-card px-3 py-1 text-sm shadow-sm ring-offset-background focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none"
|
id="filter-invoice-type"
|
||||||
title={m.invoice_list_filters_invoice_type_label()}
|
bind:value={filters.invoice_type}
|
||||||
>
|
class="flex h-9 w-[220px] rounded-md border border-input bg-card px-3 py-1 text-sm shadow-sm ring-offset-background focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none"
|
||||||
{#each invoiceTypeOptions() as option}
|
title={m.invoice_list_filters_invoice_type_label()}
|
||||||
<option value={option.value}>
|
>
|
||||||
{option.value === '' ? m.invoice_list_filters_invoice_type_all_option() : option.label}
|
{#each invoiceTypeOptions() as option}
|
||||||
</option>
|
<option value={option.value}>
|
||||||
{/each}
|
{option.value === '' ? m.invoice_list_filters_invoice_type_all_option() : option.label}
|
||||||
</select>
|
</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Button variant="outline" class="h-9" onclick={() => goto('/dashboard/invoices/settings')}>
|
<Button variant="outline" class="h-9" onclick={() => goto('/dashboard/invoices/settings')}>
|
||||||
<Settings class="mr-2" size={16} />
|
<Settings class="mr-2" size={16} />
|
||||||
|
|||||||
@@ -26,6 +26,11 @@ export const load: PageServerLoad = async ({ params, cookies, fetch, url }) => {
|
|||||||
parsedOperationType = operationTypeParam;
|
parsedOperationType = operationTypeParam;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Para creación ('new'), es obligatorio tener operation_type e invoice_type
|
||||||
|
if (params.id === 'new' && (!parsedOperationType || !invoiceTypeParam)) {
|
||||||
|
throw redirect(302, '/dashboard');
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let invoiceData: any = null;
|
let invoiceData: any = null;
|
||||||
let catalogsData: any = {};
|
let catalogsData: any = {};
|
||||||
|
|||||||
Reference in New Issue
Block a user