Merge branch 'feature/descargo_peps' into development
Integración de funcionalidad de Descargo PEPS con reportes de Packing List y Aviso Consolidado: - Frontend: Agregados imports de dischargeReportsApi y ClipboardList - Frontend: Agregada función handleDownloadDescargo con cálculo PEPS - Frontend: Agregado botón condicional de Descargo PEPS (solo exportaciones) - Frontend: Integradas funciones de Aviso Consolidado y Packing List - Backend: Agregados routers y tasks de descargo y otros reportes - Backend: Configurado Celery con todas las tareas de reportes - Backend: Corregido ForeignKey en line_quantities.package_id - Resueltos conflictos manteniendo funcionalidades de ambas ramas
This commit is contained in:
35
frontend/src/lib/api/dashboard/a24/inv.ts
Normal file
35
frontend/src/lib/api/dashboard/a24/inv.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
|
||||
import axios from 'axios';
|
||||
import { PUBLIC_API_URL } from '$env/static/public';
|
||||
|
||||
/**
|
||||
* Cliente API para el módulo de Inventarios (A24)
|
||||
*/
|
||||
export const invApi = {
|
||||
/**
|
||||
* Ejecuta el proceso de asignación PEPS (FIFO) para una factura de exportación
|
||||
* @param invoiceId ID de la factura de exportación
|
||||
* @returns Promesa con la respuesta del servidor
|
||||
*/
|
||||
assignFifo: async (invoiceId: number) => {
|
||||
try {
|
||||
const token = localStorage.getItem('access_token');
|
||||
const response = await axios.post(
|
||||
`${PUBLIC_API_URL}/api/v1/a76/reports/exportacion/descargo/fifo-assign/${invoiceId}`,
|
||||
{},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
}
|
||||
);
|
||||
return { data: response.data, error: null };
|
||||
} catch (error: any) {
|
||||
console.error('Error executing FIFO:', error);
|
||||
return {
|
||||
data: null,
|
||||
error: error.response?.data?.detail || 'Error al ejecutar cálculo PEPS'
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
|
||||
const BASE_URL = import.meta.env.VITE_API_URL || '';
|
||||
|
||||
export const dischargeReportsApi = {
|
||||
|
||||
triggerPdfGeneration: async (invoiceId: number, companyId: number) => {
|
||||
const params = new URLSearchParams({ company_id: companyId.toString() });
|
||||
// Endpoint matches routes.py
|
||||
const endpoint = `${BASE_URL}/v1/a76/reports/exportacion/descargo/${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 Reporte de Descarga');
|
||||
return await response.json();
|
||||
},
|
||||
|
||||
getTaskStatus: async (taskId: string) => {
|
||||
const endpoint = `${BASE_URL}/v1/a76/reports/exportacion/descargo/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 Reporte de Descarga');
|
||||
return await response.json();
|
||||
},
|
||||
|
||||
assignFifo: async (invoiceId: number) => {
|
||||
// Endpoint: /a76/reports/exportacion/descargo/fifo-assign/{invoice_id}
|
||||
const endpoint = `${BASE_URL}/v1/a76/reports/exportacion/descargo/fifo-assign/${invoiceId}`;
|
||||
const token = localStorage.getItem('access_token');
|
||||
|
||||
try {
|
||||
const response = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errData = await response.json().catch(() => ({}));
|
||||
return { data: null, error: errData.detail || 'Error al ejecutar PEPS' };
|
||||
}
|
||||
|
||||
return { data: await response.json(), error: null };
|
||||
} catch (e: any) {
|
||||
return { data: null, error: e.message || 'Error de conexión PEPS' };
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -60,15 +60,14 @@
|
||||
setTimeout(() => {
|
||||
onComplete(response.result);
|
||||
}, 500);
|
||||
}
|
||||
else if (response.state === 'FAILURE') {
|
||||
} else if (response.state === 'FAILURE') {
|
||||
hasError = true;
|
||||
// Intenta mostrar el mensaje de error real si viene en 'result'
|
||||
const errMsg = response.result ? String(response.result) : "Error desconocido";
|
||||
const errMsg = response.result ? String(response.result) : 'Error desconocido';
|
||||
statusMessage = `Error: ${errMsg}`;
|
||||
stopPolling();
|
||||
toast.error(`Falló la generación: ${errMsg}`);
|
||||
console.error("Task failed with result:", response);
|
||||
console.error('Task failed with result:', response);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error polling task status:", error);
|
||||
|
||||
@@ -1,25 +1,27 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { page } from '$app/stores';
|
||||
import { onMount } from 'svelte';
|
||||
import { page } from '$app/stores';
|
||||
import { goto } from '$app/navigation';
|
||||
import InvoiceDownloadModal from '$lib/components/dashboard/invoices/invoice-download-modal.svelte';
|
||||
import { invoicesApi, type Invoice, type OperationType } from '$lib/api/dashboard/a76/invoices';
|
||||
import { invoicesReportsApi } from '$lib/api/dashboard/a76/reports/reports-invoices';
|
||||
import { consolidatedReportsApi } from '$lib/api/dashboard/a76/reports/reports-consolidated';
|
||||
import DataTable from '$lib/components/dashboard/invoices/data-table.svelte';
|
||||
import { createColumns } from '$lib/components/dashboard/invoices/columns.js';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import type { PageData } from './$types';
|
||||
import { browser } from '$app/environment';
|
||||
import { companyStore } from '$lib/stores/company.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";
|
||||
import PdfProgressDialog from '$lib/components/dashboard/invoices/pdf-progress-dialog.svelte';
|
||||
import { invoicesApi, type Invoice, type OperationType } from '$lib/api/dashboard/a76/invoices';
|
||||
import { invoicesReportsApi } from '$lib/api/dashboard/a76/reports/reports-invoices';
|
||||
import { consolidatedReportsApi } from '$lib/api/dashboard/a76/reports/reports-consolidated';
|
||||
import { dischargeReportsApi } from '$lib/api/dashboard/a76/reports/reports-descargo';
|
||||
import { avisoConsolidadoReportsApi } from '$lib/api/dashboard/a76/reports/reports-aviso-consolidado';
|
||||
import DataTable from '$lib/components/dashboard/invoices/data-table.svelte';
|
||||
import { createColumns } from '$lib/components/dashboard/invoices/columns.js';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import type { PageData } from './$types';
|
||||
import { browser } from '$app/environment';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
import { Plus, RefreshCw, FileText, RotateCcw, Boxes, Package, ClipboardList } from 'lucide-svelte';
|
||||
|
||||
// IMPORTANTE: Asegúrate de tener instalada svelte-sonner para las notificaciones
|
||||
import { toast } from 'svelte-sonner';
|
||||
import PdfProgressDialog from '$lib/components/dashboard/invoices/pdf-progress-dialog.svelte';
|
||||
import { useShortcuts } from '$lib/hooks/use-shortcuts';
|
||||
import { obtenerAtajosListaFacturas } from '$lib/config/shortcuts/invoice-list-shortcuts';
|
||||
|
||||
@@ -388,80 +390,108 @@
|
||||
}
|
||||
}
|
||||
|
||||
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');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. Trigger: Iniciar la tarea en Celery (Consolidado Importación)
|
||||
const { task_id } = await consolidatedReportsApi.triggerPdfGeneration(
|
||||
invoice.id,
|
||||
companyStore.activeCompany.id
|
||||
);
|
||||
try {
|
||||
// 1. Trigger: Iniciar la tarea en Celery (Consolidado Importación)
|
||||
const { task_id } = await consolidatedReportsApi.triggerPdfGeneration(
|
||||
invoice.id,
|
||||
companyStore.activeCompany.id
|
||||
);
|
||||
|
||||
// 2. Abrir diálogo de progreso
|
||||
currentTaskId = task_id;
|
||||
currentStatusFunction = consolidatedReportsApi.getTaskStatus;
|
||||
showProgressDialog = true;
|
||||
// 2. Abrir diálogo de progreso
|
||||
currentTaskId = task_id;
|
||||
currentStatusFunction = consolidatedReportsApi.getTaskStatus;
|
||||
showProgressDialog = true;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast.error('No se pudo iniciar la descarga del consolidado');
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast.error("No se pudo iniciar la descarga del consolidado");
|
||||
}
|
||||
}
|
||||
async function handleDownloadDescargo(invoice: any) {
|
||||
if (!companyStore.activeCompany) {
|
||||
toast.error('No hay empresa seleccionada');
|
||||
return;
|
||||
}
|
||||
|
||||
async function handleDownloadAvisoConsolidado(invoice: any) {
|
||||
if (!companyStore.activeCompany) {
|
||||
toast.error("No hay empresa seleccionada");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// 0. Trigger: Ejecutar Asignación PEPS (FIFO)
|
||||
toast.info('Calculando asignación PEPS...');
|
||||
const fifoResponse = await dischargeReportsApi.assignFifo(invoice.id);
|
||||
|
||||
try {
|
||||
// 1. Trigger: Iniciar la tarea en Celery (Aviso Consolidado Exportación)
|
||||
const { task_id } = await avisoConsolidadoReportsApi.triggerPdfGeneration(
|
||||
invoice.id,
|
||||
companyStore.activeCompany.id
|
||||
);
|
||||
if (fifoResponse.error) {
|
||||
toast.error('Error al calcular PEPS: ' + fifoResponse.error);
|
||||
return;
|
||||
}
|
||||
toast.success('Cálculo PEPS completado');
|
||||
|
||||
// 2. Abrir diálogo de progreso
|
||||
currentTaskId = task_id;
|
||||
currentStatusFunction = avisoConsolidadoReportsApi.getTaskStatus;
|
||||
showProgressDialog = true;
|
||||
// 1. Trigger: Iniciar la tarea en Celery
|
||||
const { task_id } = await dischargeReportsApi.triggerPdfGeneration(
|
||||
invoice.id,
|
||||
companyStore.activeCompany.id
|
||||
);
|
||||
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast.error("No se pudo iniciar la descarga del Aviso Consolidado");
|
||||
}
|
||||
}
|
||||
// 2. Abrir diálogo de progreso
|
||||
currentTaskId = task_id;
|
||||
currentStatusFunction = dischargeReportsApi.getTaskStatus;
|
||||
showProgressDialog = true;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast.error('No se pudo iniciar la descarga del reporte PEPS');
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
);
|
||||
async function handleDownloadAvisoConsolidado(invoice: any) {
|
||||
if (!companyStore.activeCompany) {
|
||||
toast.error('No hay empresa seleccionada');
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Open progress dialog
|
||||
currentTaskId = task_id;
|
||||
// Use the specific status function for Packing List
|
||||
currentStatusFunction = invoicesReportsApi.getPackingListTaskStatus;
|
||||
showProgressDialog = true;
|
||||
try {
|
||||
// 1. Trigger: Iniciar la tarea en Celery (Aviso Consolidado Exportación)
|
||||
const { task_id } = await avisoConsolidadoReportsApi.triggerPdfGeneration(
|
||||
invoice.id,
|
||||
companyStore.activeCompany.id
|
||||
);
|
||||
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast.error("No se pudo iniciar la descarga del Packing List");
|
||||
}
|
||||
}
|
||||
// 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
|
||||
@@ -762,15 +792,37 @@
|
||||
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 && handleDownloadAvisoConsolidado(selectedInvoice)}
|
||||
disabled={!selectedInvoice}
|
||||
>
|
||||
<Boxes class="mr-2 h-4 w-4" />
|
||||
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>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => selectedInvoice && handleDownloadPackingList(selectedInvoice)}
|
||||
disabled={!selectedInvoice}
|
||||
>
|
||||
<Package class="mr-2 h-4 w-4" />
|
||||
Packing List
|
||||
</Button>
|
||||
|
||||
{#if selectedInvoice?.operation_type === 'exp'}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => selectedInvoice && handleDownloadDescargo(selectedInvoice)}
|
||||
disabled={!selectedInvoice}
|
||||
>
|
||||
<ClipboardList class="mr-2 h-4 w-4" />
|
||||
Descargo PEPS
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user