feat: agregar funcionalidad para gestionar la entrega de activos y mejorar la descarga de resguardos
This commit is contained in:
parent
8205d4203b
commit
dfbe572c79
@ -224,6 +224,22 @@ const isRouteActive = (to: string | undefined) => {
|
|||||||
return item.to === route.path;
|
return item.to === route.path;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Verificar si existe otro item del menú con un prefijo más específico que también matchea
|
||||||
|
// Ej: si estamos en /fixed-assets/assignments/create y el menú tiene /fixed-assets/assignments,
|
||||||
|
// entonces /fixed-assets no debe activarse porque /fixed-assets/assignments es más específico
|
||||||
|
const hasMoreSpecificMatch = visibleMenuItems.value.some(item => {
|
||||||
|
const checkItem = (itemTo: string | undefined) => {
|
||||||
|
if (!itemTo || itemTo === to) return false;
|
||||||
|
return itemTo.startsWith(to + '/') && route.path.startsWith(itemTo + '/') || itemTo.startsWith(to + '/') && route.path === itemTo;
|
||||||
|
};
|
||||||
|
if (item.items) {
|
||||||
|
return item.items.some(subItem => checkItem(subItem.to));
|
||||||
|
}
|
||||||
|
return checkItem(item.to);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (hasMoreSpecificMatch) return false;
|
||||||
|
|
||||||
// Si NO está explícitamente en el menú, entonces es una ruta hija (create, edit, etc)
|
// Si NO está explícitamente en el menú, entonces es una ruta hija (create, edit, etc)
|
||||||
return !isExplicitRoute;
|
return !isExplicitRoute;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -54,7 +54,7 @@ const depreciationOptions = [
|
|||||||
|
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<label class="text-sm font-semibold text-surface-800 dark:text-surface-100">
|
<label class="text-sm font-semibold text-surface-800 dark:text-surface-100">
|
||||||
Valor Residual (MXN)
|
Valor Residual
|
||||||
</label>
|
</label>
|
||||||
<InputNumber
|
<InputNumber
|
||||||
v-model="form.residual_value"
|
v-model="form.residual_value"
|
||||||
|
|||||||
@ -52,6 +52,18 @@ defineProps<Props>();
|
|||||||
showClear
|
showClear
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="space-y-2 md:col-span-2">
|
||||||
|
<label class="text-sm font-semibold text-surface-800 dark:text-surface-100">Entrega (Opcional)</label>
|
||||||
|
<Select
|
||||||
|
v-model="form.deliveredById"
|
||||||
|
:options="users"
|
||||||
|
optionLabel="full_name"
|
||||||
|
optionValue="id"
|
||||||
|
placeholder="Seleccione quien entrega..."
|
||||||
|
class="w-full"
|
||||||
|
showClear
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div class="space-y-2 md:col-span-2">
|
<div class="space-y-2 md:col-span-2">
|
||||||
<label class="text-sm font-semibold text-surface-800 dark:text-surface-100">Notas o Comentarios (Opcional)</label>
|
<label class="text-sm font-semibold text-surface-800 dark:text-surface-100">Notas o Comentarios (Opcional)</label>
|
||||||
<Textarea
|
<Textarea
|
||||||
|
|||||||
@ -30,6 +30,7 @@ const form = ref<FixedAssetAssignmentFormData>({
|
|||||||
assetId: null,
|
assetId: null,
|
||||||
employeeId: null,
|
employeeId: null,
|
||||||
authorizedById: null,
|
authorizedById: null,
|
||||||
|
deliveredById: null,
|
||||||
assignedAt: new Date().toISOString().slice(0, 10),
|
assignedAt: new Date().toISOString().slice(0, 10),
|
||||||
receiptFolio: '',
|
receiptFolio: '',
|
||||||
notes: ''
|
notes: ''
|
||||||
@ -97,6 +98,7 @@ const save = async () => {
|
|||||||
assigned_at: form.value.assignedAt,
|
assigned_at: form.value.assignedAt,
|
||||||
receipt_folio: form.value.receiptFolio || undefined,
|
receipt_folio: form.value.receiptFolio || undefined,
|
||||||
authorized_by: form.value.authorizedById ?? undefined,
|
authorized_by: form.value.authorizedById ?? undefined,
|
||||||
|
delivered_by: form.value.deliveredById ?? undefined,
|
||||||
notes: form.value.notes || undefined,
|
notes: form.value.notes || undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -77,9 +77,17 @@ const goToOffboarding = (assignment: AssetAssignment) => {
|
|||||||
router.push(`/fixed-assets/assignments/${assignment.asset_id}/${assignment.id}/offboarding`);
|
router.push(`/fixed-assets/assignments/${assignment.asset_id}/${assignment.id}/offboarding`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const downloadResguardo = (assignment: AssetAssignment) => {
|
const downloadingId = ref<number | null>(null);
|
||||||
const url = fixedAssetsService.getResguardoUrl(assignment.asset_id, assignment.id);
|
|
||||||
window.open(url, '_blank');
|
const downloadResguardo = async (assignment: AssetAssignment) => {
|
||||||
|
downloadingId.value = assignment.id;
|
||||||
|
try {
|
||||||
|
await fixedAssetsService.downloadResguardo(assignment.asset_id, assignment.id);
|
||||||
|
} catch {
|
||||||
|
toast.add({ severity: 'error', summary: 'Error', detail: 'No se pudo generar el resguardo.', life: 4000 });
|
||||||
|
} finally {
|
||||||
|
downloadingId.value = null;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(loadAssignments);
|
onMounted(loadAssignments);
|
||||||
@ -130,7 +138,6 @@ onMounted(loadAssignments);
|
|||||||
<table class="min-w-full border-collapse">
|
<table class="min-w-full border-collapse">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="bg-surface-50 text-left text-xs font-semibold uppercase tracking-wide text-surface-500 dark:bg-surface-800 dark:text-surface-300">
|
<tr class="bg-surface-50 text-left text-xs font-semibold uppercase tracking-wide text-surface-500 dark:bg-surface-800 dark:text-surface-300">
|
||||||
<th class="px-4 py-3">ID</th>
|
|
||||||
<th class="px-4 py-3">Activo</th>
|
<th class="px-4 py-3">Activo</th>
|
||||||
<th class="px-4 py-3">Empleado</th>
|
<th class="px-4 py-3">Empleado</th>
|
||||||
<th class="px-4 py-3">Fecha Entrega</th>
|
<th class="px-4 py-3">Fecha Entrega</th>
|
||||||
@ -151,9 +158,6 @@ onMounted(loadAssignments);
|
|||||||
:key="assignment.id"
|
:key="assignment.id"
|
||||||
class="border-t border-surface-200 text-sm dark:border-surface-700"
|
class="border-t border-surface-200 text-sm dark:border-surface-700"
|
||||||
>
|
>
|
||||||
<td class="px-4 py-3 font-medium text-surface-800 dark:text-surface-100">
|
|
||||||
#{{ assignment.id }}
|
|
||||||
</td>
|
|
||||||
<td class="px-4 py-3">
|
<td class="px-4 py-3">
|
||||||
<p class="font-semibold text-surface-900 dark:text-surface-0">
|
<p class="font-semibold text-surface-900 dark:text-surface-0">
|
||||||
{{ assignment.asset?.inventory_warehouse?.product?.name ?? assignment.asset?.sku ?? '—' }}
|
{{ assignment.asset?.inventory_warehouse?.product?.name ?? assignment.asset?.sku ?? '—' }}
|
||||||
@ -187,6 +191,7 @@ onMounted(loadAssignments);
|
|||||||
size="small"
|
size="small"
|
||||||
severity="secondary"
|
severity="secondary"
|
||||||
v-tooltip.top="'Descargar resguardo'"
|
v-tooltip.top="'Descargar resguardo'"
|
||||||
|
:loading="downloadingId === assignment.id"
|
||||||
@click="downloadResguardo(assignment)"
|
@click="downloadResguardo(assignment)"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@ -117,13 +117,14 @@ export interface AssetAssignmentsPaginatedResponse {
|
|||||||
|
|
||||||
export interface UserOption {
|
export interface UserOption {
|
||||||
id: number;
|
id: number;
|
||||||
|
name: string;
|
||||||
|
paternal: string;
|
||||||
|
maternal: string;
|
||||||
full_name: string;
|
full_name: string;
|
||||||
email: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UsersResponse {
|
export interface UsersResponse {
|
||||||
status: string;
|
data: UserOption[];
|
||||||
data: { data: UserOption[] };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AssignAssetData {
|
interface AssignAssetData {
|
||||||
@ -131,6 +132,7 @@ interface AssignAssetData {
|
|||||||
assigned_at?: string;
|
assigned_at?: string;
|
||||||
receipt_folio?: string;
|
receipt_folio?: string;
|
||||||
authorized_by?: number;
|
authorized_by?: number;
|
||||||
|
delivered_by?: number;
|
||||||
notes?: string;
|
notes?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,7 +185,7 @@ class FixedAssetsService {
|
|||||||
if (filters.page) params.page = filters.page;
|
if (filters.page) params.page = filters.page;
|
||||||
if (filters.paginate !== undefined) params.paginate = filters.paginate;
|
if (filters.paginate !== undefined) params.paginate = filters.paginate;
|
||||||
|
|
||||||
const response = await api.get<AssetAssignmentsPaginatedResponse>('/api/asset-assignments', { params });
|
const response = await api.get<AssetAssignmentsPaginatedResponse>('/api/assets/assignments', { params });
|
||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,13 +200,24 @@ class FixedAssetsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getUsers(): Promise<UserOption[]> {
|
async getUsers(): Promise<UserOption[]> {
|
||||||
const response = await api.get<UsersResponse>('/api/admin/users', { params: { paginate: false } });
|
const response = await api.get<UsersResponse>('/api/rh/employees', { params: { paginate: false } });
|
||||||
return response.data.data.data;
|
return response.data.data.map((e: any) => ({
|
||||||
|
...e,
|
||||||
|
full_name: `${e.name} ${e.paternal} ${e.maternal ?? ''}`.trim(),
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
getResguardoUrl(assetId: number, assignmentId: number): string {
|
async downloadResguardo(assetId: number, assignmentId: number): Promise<void> {
|
||||||
const base = import.meta.env.VITE_API_URL || '';
|
const response = await api.get(
|
||||||
return `${base}/api/asset-assignments-public/${assetId}/assignments/${assignmentId}/resguardo`;
|
`/api/assets/${assetId}/assignments/${assignmentId}/resguardo`,
|
||||||
|
{ responseType: 'blob' }
|
||||||
|
);
|
||||||
|
const blobUrl = URL.createObjectURL(new Blob([response.data], { type: 'application/pdf' }));
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = blobUrl;
|
||||||
|
link.target = '_blank';
|
||||||
|
link.click();
|
||||||
|
URL.revokeObjectURL(blobUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -18,6 +18,7 @@ export interface FixedAssetAssignmentFormData {
|
|||||||
assetId: number | null;
|
assetId: number | null;
|
||||||
employeeId: number | null;
|
employeeId: number | null;
|
||||||
authorizedById: number | null;
|
authorizedById: number | null;
|
||||||
|
deliveredById: number | null;
|
||||||
assignedAt: string;
|
assignedAt: string;
|
||||||
receiptFolio: string;
|
receiptFolio: string;
|
||||||
notes: string;
|
notes: string;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user