feat: Implementacion para generar un excel para inventario y modificacion a permisos

This commit is contained in:
Juan Felipe Zapata Moreno 2026-02-04 16:45:39 -06:00
parent c9251e0c8f
commit 69c015d51b
5 changed files with 111 additions and 2 deletions

View File

@ -73,6 +73,7 @@ onMounted(() => {
to="pos.client-tiers.index"
/>
<Link
v-if="hasPermission('invoice-requests.index')"
icon="request_quote"
name="pos.billingRequests"
to="pos.billingRequests.index"

View File

@ -1,13 +1,13 @@
import { hasPermission } from '@Plugins/RolePermission.js';
// Ruta API
const apiTo = (name, params = {}) => route(`factura.${name}`, params)
const apiTo = (name, params = {}) => route(`Solicitudes de factura.${name}`, params)
// Ruta visual
const viewTo = ({ name = '', params = {}, query = {} }) => ({ name: `pos.factura.${name}`, params, query })
// Determina si un usuario puede hacer algo en base a los permisos
const can = (permission) => hasPermission(`factura.${permission}`)
const can = (permission) => hasPermission(`invoice-requests.${permission}`)
export {
can,

View File

@ -4,6 +4,7 @@ import { useRouter } from 'vue-router';
import { useSearcher, apiURL } from '@Services/Api';
import { formatCurrency } from '@/utils/formatters';
import { can } from './Module.js';
import reportService from '@Services/reportService';
const router = useRouter();
@ -23,6 +24,7 @@ const showDeleteModal = ref(false);
const showImportModal = ref(false);
const editingProduct = ref(null);
const deletingProduct = ref(null);
const isExporting = ref(false);
/** Métodos */
const searcher = useSearcher({
@ -106,6 +108,28 @@ const confirmDelete = async (id) => {
}
};
const exportReport = async () => {
try {
isExporting.value = true;
// Puedes agregar filtros aquí si lo necesitas
const filters = {
// category_id: 5, // Opcional: filtrar por categoría específica
// with_serials_only: true, // Opcional: solo productos con seguimiento de seriales
// low_stock_threshold: 10 // Opcional: solo productos con stock bajo o igual al umbral
};
await reportService.exportInventoryToExcel(filters);
Notify.success('Reporte exportado exitosamente');
} catch (error) {
console.error('Error al exportar:', error);
Notify.error('Error al exportar el reporte');
} finally {
isExporting.value = false;
}
};
/** Ciclos */
onMounted(() => {
searcher.search();
@ -119,6 +143,15 @@ onMounted(() => {
placeholder="Buscar por nombre o SKU..."
@search="(x) => searcher.search(x)"
>
<button
class="flex items-center gap-2 px-3 py-2 bg-emerald-600 hover:bg-emerald-700 text-white text-sm font-semibold rounded-lg transition-colors shadow-sm disabled:opacity-50 disabled:cursor-not-allowed"
@click="exportReport"
:disabled="isExporting"
title="Exportar inventario a Excel"
>
<GoogleIcon :name="isExporting ? 'hourglass_empty' : 'download'" class="text-xl" :class="{ 'animate-spin': isExporting }" />
{{ isExporting ? 'Exportando...' : 'Exportar' }}
</button>
<button
v-if="can('import')"
class="flex items-center gap-2 px-3 py-2 bg-green-600 hover:bg-green-700 text-white text-sm font-semibold rounded-lg transition-colors shadow-sm"

View File

@ -92,11 +92,13 @@ const router = createRouter({
{
path: 'client-tiers',
name: 'pos.client-tiers.index',
beforeEnter: (to, from, next) => can(next, 'client-tiers.index'),
component: () => import('@Pages/POS/Tiers/Index.vue')
},
{
path: 'billing-requests',
name: 'pos.billingRequests.index',
beforeEnter: (to, from, next) => can(next, 'invoice-requests.index'),
component: () => import('@Pages/POS/Clients/BillingRequests.vue')
}
]

View File

@ -51,6 +51,79 @@ const reportService = {
}
});
});
},
/**
* Exports inventory report to Excel file.
* @param {Object} filters - Optional filters for the export.
* @param {number|null} filters.category_id - Filter by category ID.
* @param {boolean|null} filters.with_serials_only - Only products with serial number tracking.
* @param {number|null} filters.low_stock_threshold - Filter products below this stock level.
* @returns {Promise<void>}
*/
async exportInventoryToExcel(filters = {}) {
try {
// Construir URL con parámetros
let url = apiURL('reports/inventory/excel');
const params = [];
if (filters.category_id) params.push(`category_id=${filters.category_id}`);
if (filters.with_serials_only !== undefined) params.push(`with_serials_only=${filters.with_serials_only ? 'true' : 'false'}`);
if (filters.low_stock_threshold) params.push(`low_stock_threshold=${filters.low_stock_threshold}`);
if (params.length > 0) {
url += `?${params.join('&')}`;
}
// Hacer petición con axios para descargar archivo
const response = await window.axios.get(url, {
responseType: 'blob',
headers: {
'Authorization': `Bearer ${sessionStorage.token}`,
}
});
// Crear blob
const blob = new Blob([response.data], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
});
// Generar el nombre del archivo con fecha actual
const now = new Date();
const timestamp = now.toISOString().split('T')[0]; // YYYY-MM-DD
const filename = `inventario_${timestamp}.xlsx`;
// Crear URL del blob
const downloadUrl = window.URL.createObjectURL(blob);
// Crear elemento <a> temporal para descargar
const link = document.createElement('a');
link.href = downloadUrl;
link.download = filename;
document.body.appendChild(link);
link.click();
// Limpiar
document.body.removeChild(link);
window.URL.revokeObjectURL(downloadUrl);
return Promise.resolve();
} catch (error) {
console.error('Error al exportar inventario:', error);
// Manejar errores de blob
if (error.response && error.response.data instanceof Blob) {
const text = await error.response.data.text();
try {
const json = JSON.parse(text);
return Promise.reject(new Error(json.message || 'Error al exportar el reporte'));
} catch {
return Promise.reject(new Error('Error al exportar el reporte'));
}
}
return Promise.reject(error);
}
}
};