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;
|
||||
});
|
||||
|
||||
// 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)
|
||||
return !isExplicitRoute;
|
||||
}
|
||||
|
||||
@ -54,7 +54,7 @@ const depreciationOptions = [
|
||||
|
||||
<div class="space-y-2">
|
||||
<label class="text-sm font-semibold text-surface-800 dark:text-surface-100">
|
||||
Valor Residual (MXN)
|
||||
Valor Residual
|
||||
</label>
|
||||
<InputNumber
|
||||
v-model="form.residual_value"
|
||||
|
||||
@ -52,6 +52,18 @@ defineProps<Props>();
|
||||
showClear
|
||||
/>
|
||||
</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">
|
||||
<label class="text-sm font-semibold text-surface-800 dark:text-surface-100">Notas o Comentarios (Opcional)</label>
|
||||
<Textarea
|
||||
|
||||
@ -30,6 +30,7 @@ const form = ref<FixedAssetAssignmentFormData>({
|
||||
assetId: null,
|
||||
employeeId: null,
|
||||
authorizedById: null,
|
||||
deliveredById: null,
|
||||
assignedAt: new Date().toISOString().slice(0, 10),
|
||||
receiptFolio: '',
|
||||
notes: ''
|
||||
@ -97,6 +98,7 @@ const save = async () => {
|
||||
assigned_at: form.value.assignedAt,
|
||||
receipt_folio: form.value.receiptFolio || undefined,
|
||||
authorized_by: form.value.authorizedById ?? undefined,
|
||||
delivered_by: form.value.deliveredById ?? 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`);
|
||||
};
|
||||
|
||||
const downloadResguardo = (assignment: AssetAssignment) => {
|
||||
const url = fixedAssetsService.getResguardoUrl(assignment.asset_id, assignment.id);
|
||||
window.open(url, '_blank');
|
||||
const downloadingId = ref<number | null>(null);
|
||||
|
||||
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);
|
||||
@ -130,7 +138,6 @@ onMounted(loadAssignments);
|
||||
<table class="min-w-full border-collapse">
|
||||
<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">
|
||||
<th class="px-4 py-3">ID</th>
|
||||
<th class="px-4 py-3">Activo</th>
|
||||
<th class="px-4 py-3">Empleado</th>
|
||||
<th class="px-4 py-3">Fecha Entrega</th>
|
||||
@ -151,9 +158,6 @@ onMounted(loadAssignments);
|
||||
:key="assignment.id"
|
||||
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">
|
||||
<p class="font-semibold text-surface-900 dark:text-surface-0">
|
||||
{{ assignment.asset?.inventory_warehouse?.product?.name ?? assignment.asset?.sku ?? '—' }}
|
||||
@ -187,6 +191,7 @@ onMounted(loadAssignments);
|
||||
size="small"
|
||||
severity="secondary"
|
||||
v-tooltip.top="'Descargar resguardo'"
|
||||
:loading="downloadingId === assignment.id"
|
||||
@click="downloadResguardo(assignment)"
|
||||
/>
|
||||
<Button
|
||||
|
||||
@ -117,13 +117,14 @@ export interface AssetAssignmentsPaginatedResponse {
|
||||
|
||||
export interface UserOption {
|
||||
id: number;
|
||||
name: string;
|
||||
paternal: string;
|
||||
maternal: string;
|
||||
full_name: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
export interface UsersResponse {
|
||||
status: string;
|
||||
data: { data: UserOption[] };
|
||||
data: UserOption[];
|
||||
}
|
||||
|
||||
interface AssignAssetData {
|
||||
@ -131,6 +132,7 @@ interface AssignAssetData {
|
||||
assigned_at?: string;
|
||||
receipt_folio?: string;
|
||||
authorized_by?: number;
|
||||
delivered_by?: number;
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
@ -183,7 +185,7 @@ class FixedAssetsService {
|
||||
if (filters.page) params.page = filters.page;
|
||||
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;
|
||||
}
|
||||
|
||||
@ -198,13 +200,24 @@ class FixedAssetsService {
|
||||
}
|
||||
|
||||
async getUsers(): Promise<UserOption[]> {
|
||||
const response = await api.get<UsersResponse>('/api/admin/users', { params: { paginate: false } });
|
||||
return response.data.data.data;
|
||||
const response = await api.get<UsersResponse>('/api/rh/employees', { params: { paginate: false } });
|
||||
return response.data.data.map((e: any) => ({
|
||||
...e,
|
||||
full_name: `${e.name} ${e.paternal} ${e.maternal ?? ''}`.trim(),
|
||||
}));
|
||||
}
|
||||
|
||||
getResguardoUrl(assetId: number, assignmentId: number): string {
|
||||
const base = import.meta.env.VITE_API_URL || '';
|
||||
return `${base}/api/asset-assignments-public/${assetId}/assignments/${assignmentId}/resguardo`;
|
||||
async downloadResguardo(assetId: number, assignmentId: number): Promise<void> {
|
||||
const response = await api.get(
|
||||
`/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;
|
||||
employeeId: number | null;
|
||||
authorizedById: number | null;
|
||||
deliveredById: number | null;
|
||||
assignedAt: string;
|
||||
receiptFolio: string;
|
||||
notes: string;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user