Merge branch 'feature/packing_list' into development

This commit is contained in:
2026-02-03 17:36:41 -06:00
26 changed files with 2420 additions and 16 deletions

View File

@@ -0,0 +1,35 @@
const BASE_URL = import.meta.env.VITE_API_URL || '';
export const avisoConsolidadoReportsApi = {
triggerPdfGeneration: async (invoiceId: number, companyId: number) => {
const params = new URLSearchParams({ company_id: companyId.toString() });
const endpoint = `${BASE_URL}/v1/a76/reports/exportacion/aviso_consolidado/${invoiceId}/download-async?${params.toString()}`;
const token = localStorage.getItem('access_token');
const response = await fetch(endpoint, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
if (!response.ok) throw new Error('Error al iniciar la generación del Aviso Consolidado');
return await response.json();
},
getTaskStatus: async (taskId: string) => {
const endpoint = `${BASE_URL}/v1/a76/reports/exportacion/aviso_consolidado/tasks/${taskId}`;
const token = localStorage.getItem('access_token');
const response = await fetch(endpoint, {
method: 'GET',
headers: { 'Authorization': `Bearer ${token}` }
});
if (!response.ok) throw new Error('Error al consultar estado del Aviso Consolidado');
return await response.json();
}
};

View File

@@ -1,4 +1,5 @@
const BASE_URL = import.meta.env.VITE_API_URL || '';
const BASE_URL = import.meta.env.VITE_API_URL || '';
export const invoicesReportsApi = {
@@ -11,8 +12,10 @@ export const invoicesReportsApi = {
});
const endpoint = `${BASE_URL}/v1/a76/reports/importacion/facturas/${invoiceId}/download-async?${params.toString()}`;
const token = localStorage.getItem('access_token');
const response = await fetch(endpoint, {
method: 'POST',
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
@@ -22,11 +25,14 @@ export const invoicesReportsApi = {
if (!response.ok) throw new Error('Error al iniciar la generación');
return await response.json();
return await response.json();
},
getTaskStatus: async (taskId: string) => {
const endpoint = `${BASE_URL}/v1/a76/reports/importacion/facturas/tasks/${taskId}`;
const endpoint = `${BASE_URL}/v1/a76/reports/importacion/facturas/tasks/${taskId}`;
const token = localStorage.getItem('access_token');
const response = await fetch(endpoint, {
method: 'GET',
@@ -35,5 +41,35 @@ export const invoicesReportsApi = {
if (!response.ok) throw new Error('Error al consultar estado');
return await response.json();
},
triggerPackingListGeneration: async (invoiceId: number, companyId: number) => {
const params = new URLSearchParams({ company_id: companyId.toString() });
const endpoint = `${BASE_URL}/v1/a76/reports/importacion/packing-lists/${invoiceId}/download-async?${params.toString()}`;
const token = localStorage.getItem('access_token');
const response = await fetch(endpoint, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
if (!response.ok) throw new Error('Error al iniciar la generación de Packing List');
return await response.json();
},
getPackingListTaskStatus: async (taskId: string) => {
const endpoint = `${BASE_URL}/v1/a76/reports/importacion/packing-lists/tasks/${taskId}`;
const token = localStorage.getItem('access_token');
const response = await fetch(endpoint, {
method: 'GET',
headers: { 'Authorization': `Bearer ${token}` }
});
if (!response.ok) throw new Error('Error al consultar estado de Packing List');
return await response.json();
}
};

View File

@@ -63,9 +63,12 @@
}
else if (response.state === 'FAILURE') {
hasError = true;
statusMessage = "Error al generar el PDF";
// Intenta mostrar el mensaje de error real si viene en 'result'
const errMsg = response.result ? String(response.result) : "Error desconocido";
statusMessage = `Error: ${errMsg}`;
stopPolling();
toast.error("Falló la generación del PDF");
toast.error(`Falló la generación: ${errMsg}`);
console.error("Task failed with result:", response);
}
} catch (error) {
console.error("Error polling task status:", error);

View File

@@ -0,0 +1,7 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
let { ...restProps }: DropdownMenuPrimitive.RootProps = $props();
</script>
<DropdownMenuPrimitive.Root data-slot="dropdown-menu-root" {...restProps} />

View File

@@ -0,0 +1,7 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
let { ...restProps }: DropdownMenuPrimitive.SubProps = $props();
</script>
<DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...restProps} />

View File

@@ -1,4 +1,4 @@
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
// import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import CheckboxItem from "./dropdown-menu-checkbox-item.svelte";
import Content from "./dropdown-menu-content.svelte";
import Group from "./dropdown-menu-group.svelte";
@@ -12,8 +12,8 @@ import Trigger from "./dropdown-menu-trigger.svelte";
import SubContent from "./dropdown-menu-sub-content.svelte";
import SubTrigger from "./dropdown-menu-sub-trigger.svelte";
import GroupHeading from "./dropdown-menu-group-heading.svelte";
const Sub = DropdownMenuPrimitive.Sub;
const Root = DropdownMenuPrimitive.Root;
import Sub from "./dropdown-menu-sub.svelte";
import Root from "./dropdown-menu-root.svelte";
export {
CheckboxItem,

View File

@@ -15,7 +15,7 @@
import type { PageData } from './$types';
import { browser } from '$app/environment';
import { companyStore } from '$lib/stores/company.svelte';
import { Plus, RefreshCw, FileText, RotateCcw, Boxes } from 'lucide-svelte';
import { Plus, RefreshCw, FileText, RotateCcw, Boxes, Package } from 'lucide-svelte';
// IMPORTANTE: Asegúrate de tener instalada svelte-sonner para las notificaciones
import { toast } from "svelte-sonner";
@@ -376,6 +376,8 @@
}
}
import { avisoConsolidadoReportsApi } from '$lib/api/dashboard/a76/reports/reports-aviso-consolidado';
async function handleDownloadConsolidated(invoice: any) {
if (!companyStore.activeCompany) {
toast.error("No hay empresa seleccionada");
@@ -383,7 +385,7 @@
}
try {
// 1. Trigger: Iniciar la tarea en Celery (Consolidado)
// 1. Trigger: Iniciar la tarea en Celery (Consolidado Importación)
const { task_id } = await consolidatedReportsApi.triggerPdfGeneration(
invoice.id,
companyStore.activeCompany.id
@@ -400,6 +402,55 @@
}
}
async function handleDownloadAvisoConsolidado(invoice: any) {
if (!companyStore.activeCompany) {
toast.error("No hay empresa seleccionada");
return;
}
try {
// 1. Trigger: Iniciar la tarea en Celery (Aviso Consolidado Exportación)
const { task_id } = await avisoConsolidadoReportsApi.triggerPdfGeneration(
invoice.id,
companyStore.activeCompany.id
);
// 2. Abrir diálogo de progreso
currentTaskId = task_id;
currentStatusFunction = avisoConsolidadoReportsApi.getTaskStatus;
showProgressDialog = true;
} catch (error) {
console.error(error);
toast.error("No se pudo iniciar la descarga del Aviso Consolidado");
}
}
async function handleDownloadPackingList(invoice: any) {
if (!companyStore.activeCompany) {
toast.error("No hay empresa seleccionada");
return;
}
try {
// 1. Trigger: Start task in Celery
const { task_id } = await invoicesReportsApi.triggerPackingListGeneration(
invoice.id,
companyStore.activeCompany.id
);
// 2. Open progress dialog
currentTaskId = task_id;
// Use the specific status function for Packing List
currentStatusFunction = invoicesReportsApi.getPackingListTaskStatus;
showProgressDialog = true;
} catch (error) {
console.error(error);
toast.error("No se pudo iniciar la descarga del Packing List");
}
}
function onPdfComplete(result: any) {
// Esta función se llama cuando el diálogo reporta SUCCESS
try {
@@ -652,10 +703,21 @@
<FileText class="h-4 w-4 mr-2" />
Factura
</Button>
<Button variant="outline" size="sm" onclick={() => selectedInvoice && handleDownloadConsolidated(selectedInvoice)} disabled={!selectedInvoice}>
<Boxes class="h-4 w-4 mr-2" />
Consolidado
</Button>
<Button variant="outline" size="sm" onclick={() => selectedInvoice && handleDownloadAvisoConsolidado(selectedInvoice)} disabled={!selectedInvoice}>
<Boxes class="h-4 w-4 mr-2" />
Aviso Consolidado
</Button>
<Button variant="outline" size="sm" onclick={() => selectedInvoice && handleDownloadPackingList(selectedInvoice)} disabled={!selectedInvoice}>
<Package class="h-4 w-4 mr-2" />
Packing List
</Button>
</div>
</div>
</div>