feat: agregar gestión de depreciaciones de activos fijos y detalles de activos
This commit is contained in:
parent
c85200ed64
commit
5a2944663d
@ -103,6 +103,10 @@ const goToAssignment = () => {
|
||||
router.push('/fixed-assets/assignments');
|
||||
};
|
||||
|
||||
const goToDetails = (asset: Asset) => {
|
||||
router.push(`/fixed-assets/${asset.id}`);
|
||||
};
|
||||
|
||||
const goToEdit = (asset: Asset) => {
|
||||
router.push(`/fixed-assets/${asset.id}/edit`);
|
||||
};
|
||||
@ -255,6 +259,7 @@ const handleDelete = (asset: Asset) => {
|
||||
</td>
|
||||
<td class="px-4 py-4">
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<Button icon="pi pi-eye" text rounded size="small" severity="info" @click="goToDetails(asset)" />
|
||||
<Button icon="pi pi-pencil" text rounded size="small" @click="goToEdit(asset)" />
|
||||
<Button icon="pi pi-trash" text rounded size="small" severity="danger" @click="handleDelete(asset)" />
|
||||
</div>
|
||||
|
||||
@ -0,0 +1,147 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import Button from 'primevue/button';
|
||||
import Dialog from 'primevue/dialog';
|
||||
import InputNumber from 'primevue/inputnumber';
|
||||
import Select from 'primevue/select';
|
||||
import Textarea from 'primevue/textarea';
|
||||
import { assetDepreciationService } from '../../services/fixedAssetsService';
|
||||
|
||||
const props = defineProps<{ assetId: number }>();
|
||||
const emit = defineEmits<{ saved: [] }>();
|
||||
|
||||
const toast = useToast();
|
||||
const visible = ref(false);
|
||||
const saving = ref(false);
|
||||
|
||||
const currentYear = new Date().getFullYear();
|
||||
const currentMonth = new Date().getMonth() + 1;
|
||||
|
||||
const form = ref({
|
||||
year: currentYear,
|
||||
month: currentMonth,
|
||||
amount: null as number | null,
|
||||
notes: '',
|
||||
});
|
||||
|
||||
const yearOptions = Array.from({ length: 11 }, (_, i) => ({
|
||||
label: String(currentYear - 5 + i),
|
||||
value: currentYear - 5 + i,
|
||||
}));
|
||||
|
||||
const monthOptions = [
|
||||
{ label: 'Enero', value: 1 },
|
||||
{ label: 'Febrero', value: 2 },
|
||||
{ label: 'Marzo', value: 3 },
|
||||
{ label: 'Abril', value: 4 },
|
||||
{ label: 'Mayo', value: 5 },
|
||||
{ label: 'Junio', value: 6 },
|
||||
{ label: 'Julio', value: 7 },
|
||||
{ label: 'Agosto', value: 8 },
|
||||
{ label: 'Septiembre', value: 9 },
|
||||
{ label: 'Octubre', value: 10 },
|
||||
{ label: 'Noviembre', value: 11 },
|
||||
{ label: 'Diciembre', value: 12 },
|
||||
];
|
||||
|
||||
const isManual = computed(() => form.value.amount !== null && form.value.amount > 0);
|
||||
|
||||
const open = () => {
|
||||
form.value = { year: currentYear, month: currentMonth, amount: null, notes: '' };
|
||||
visible.value = true;
|
||||
};
|
||||
|
||||
const save = async () => {
|
||||
saving.value = true;
|
||||
try {
|
||||
await assetDepreciationService.registerDepreciation(props.assetId, {
|
||||
year: form.value.year,
|
||||
month: form.value.month,
|
||||
amount: form.value.amount ?? undefined,
|
||||
notes: form.value.notes || undefined,
|
||||
});
|
||||
toast.add({
|
||||
severity: 'success',
|
||||
summary: 'Depreciación registrada',
|
||||
detail: isManual.value ? 'Monto manual registrado correctamente.' : 'Depreciación calculada y registrada.',
|
||||
life: 3000,
|
||||
});
|
||||
visible.value = false;
|
||||
emit('saved');
|
||||
} catch (error: any) {
|
||||
const message = error.response?.data?.message || 'No se pudo registrar la depreciación.';
|
||||
toast.add({ severity: 'error', summary: 'Error', detail: message, life: 5000 });
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
defineExpose({ open });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Dialog v-model:visible="visible" modal header="Registrar Depreciación" class="w-full max-w-md">
|
||||
<div class="flex flex-col gap-4 py-2">
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div class="flex flex-col gap-1">
|
||||
<label class="text-sm font-medium text-surface-700 dark:text-surface-300">Año</label>
|
||||
<Select
|
||||
v-model="form.year"
|
||||
:options="yearOptions"
|
||||
optionLabel="label"
|
||||
optionValue="value"
|
||||
class="w-full"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
<label class="text-sm font-medium text-surface-700 dark:text-surface-300">Mes</label>
|
||||
<Select
|
||||
v-model="form.month"
|
||||
:options="monthOptions"
|
||||
optionLabel="label"
|
||||
optionValue="value"
|
||||
class="w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1">
|
||||
<label class="text-sm font-medium text-surface-700 dark:text-surface-300">
|
||||
Monto <span class="text-surface-400 font-normal">(opcional)</span>
|
||||
</label>
|
||||
<InputNumber
|
||||
v-model="form.amount"
|
||||
:min="0.01"
|
||||
:min-fraction-digits="2"
|
||||
:max-fraction-digits="2"
|
||||
mode="currency"
|
||||
currency="MXN"
|
||||
locale="es-MX"
|
||||
placeholder="Dejar vacío para calcular automáticamente"
|
||||
class="w-full"
|
||||
/>
|
||||
<p class="text-xs text-surface-400">
|
||||
<template v-if="isManual">
|
||||
Ingreso manual — se registrará el monto exacto indicado.
|
||||
</template>
|
||||
<template v-else>
|
||||
Sin monto — el sistema calculará según el método configurado en el activo.
|
||||
</template>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1">
|
||||
<label class="text-sm font-medium text-surface-700 dark:text-surface-300">
|
||||
Notas <span class="text-surface-400 font-normal">(opcional)</span>
|
||||
</label>
|
||||
<Textarea v-model="form.notes" rows="2" placeholder="Observaciones..." class="w-full" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<Button label="Cancelar" text severity="secondary" @click="visible = false" />
|
||||
<Button label="Guardar" icon="pi pi-check" :loading="saving" @click="save" />
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
@ -0,0 +1,144 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue';
|
||||
import Button from 'primevue/button';
|
||||
import { assetDepreciationService, type Asset, type AssetDepreciation } from '../../services/fixedAssetsService';
|
||||
import FixedAssetDepreciationDialog from './FixedAssetDepreciationDialog.vue';
|
||||
|
||||
const props = defineProps<{ asset: Asset }>();
|
||||
const emit = defineEmits<{ refresh: [] }>();
|
||||
|
||||
const depreciations = ref<AssetDepreciation[]>([]);
|
||||
const loading = ref(false);
|
||||
const dialogRef = ref<InstanceType<typeof FixedAssetDepreciationDialog> | null>(null);
|
||||
|
||||
const fetchDepreciations = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const response = await assetDepreciationService.getDepreciations(props.asset.id);
|
||||
depreciations.value = (response as any).data?.data ?? [];
|
||||
} catch (error) {
|
||||
console.error('Error al cargar depreciaciones:', error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(fetchDepreciations);
|
||||
|
||||
const onSaved = () => {
|
||||
fetchDepreciations();
|
||||
emit('refresh');
|
||||
};
|
||||
|
||||
const formatCurrency = (value: string | number) => {
|
||||
const num = typeof value === 'string' ? parseFloat(value) : value;
|
||||
return new Intl.NumberFormat('es-MX', { style: 'currency', currency: 'MXN' }).format(num);
|
||||
};
|
||||
|
||||
const formatPeriod = (year: number, month: number) => {
|
||||
return new Date(year, month - 1).toLocaleString('es-MX', { month: 'long', year: 'numeric' });
|
||||
};
|
||||
|
||||
const formatMethod = (method: string) => {
|
||||
const map: Record<string, string> = {
|
||||
straight_line: 'Línea Recta',
|
||||
declining_balance: 'Saldo Decreciente',
|
||||
double_declining_balance: 'Doble Saldo Decreciente',
|
||||
manual: 'Manual',
|
||||
};
|
||||
return map[method] ?? method;
|
||||
};
|
||||
|
||||
const formatDate = (date: string | null) => {
|
||||
if (!date) return '—';
|
||||
return new Date(date).toLocaleDateString('es-MX', { day: '2-digit', month: 'short', year: 'numeric' });
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<FixedAssetDepreciationDialog ref="dialogRef" :asset-id="asset.id" @saved="onSaved" />
|
||||
|
||||
<!-- Métricas resumen -->
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="rounded-xl border border-surface-200 dark:border-surface-700 p-4">
|
||||
<p class="text-xs font-semibold uppercase tracking-wide text-surface-400 mb-1">Valor en Libros</p>
|
||||
<p class="text-2xl font-bold text-surface-900 dark:text-surface-0">
|
||||
{{ formatCurrency(asset.book_value) }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="rounded-xl border border-surface-200 dark:border-surface-700 p-4">
|
||||
<p class="text-xs font-semibold uppercase tracking-wide text-surface-400 mb-1">Depreciación Acumulada</p>
|
||||
<p class="text-2xl font-bold text-surface-900 dark:text-surface-0">
|
||||
{{ formatCurrency(asset.accumulated_depreciation) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Acciones -->
|
||||
<div class="flex justify-end">
|
||||
<Button
|
||||
label="Registrar Depreciación"
|
||||
icon="pi pi-plus"
|
||||
size="small"
|
||||
@click="dialogRef?.open()"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Tabla historial -->
|
||||
<div class="overflow-x-auto rounded-xl border border-surface-200 dark:border-surface-700">
|
||||
<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">Período</th>
|
||||
<th class="px-4 py-3">Monto</th>
|
||||
<th class="px-4 py-3">Acumulado</th>
|
||||
<th class="px-4 py-3">Valor en Libros</th>
|
||||
<th class="px-4 py-3">Método</th>
|
||||
<th class="px-4 py-3">Procesado por</th>
|
||||
<th class="px-4 py-3">Fecha</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-if="loading">
|
||||
<td colspan="7" class="px-4 py-10 text-center text-surface-500">
|
||||
<i class="pi pi-spin pi-spinner mr-2"></i>Cargando historial...
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
v-else
|
||||
v-for="dep in depreciations"
|
||||
:key="dep.id"
|
||||
class="border-t border-surface-200 text-sm text-surface-700 dark:border-surface-700 dark:text-surface-200"
|
||||
>
|
||||
<td class="px-4 py-3 font-medium capitalize">
|
||||
{{ formatPeriod(dep.period_year, dep.period_month) }}
|
||||
</td>
|
||||
<td class="px-4 py-3 text-red-600 font-semibold">
|
||||
-{{ formatCurrency(dep.depreciation_amount) }}
|
||||
</td>
|
||||
<td class="px-4 py-3">{{ formatCurrency(dep.accumulated_depreciation) }}</td>
|
||||
<td class="px-4 py-3 font-semibold">{{ formatCurrency(dep.book_value) }}</td>
|
||||
<td class="px-4 py-3">
|
||||
<span class="inline-flex rounded-full px-2 py-0.5 text-xs font-medium bg-surface-100 text-surface-700 dark:bg-surface-700 dark:text-surface-200">
|
||||
{{ formatMethod(dep.method) }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-4 py-3">
|
||||
<template v-if="dep.processed_by">
|
||||
<span>{{ dep.processed_by.name }}</span>
|
||||
</template>
|
||||
<span v-else class="text-surface-400 text-xs">Sistema</span>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-surface-500 text-xs">{{ formatDate(dep.processed_at) }}</td>
|
||||
</tr>
|
||||
<tr v-if="!loading && depreciations.length === 0">
|
||||
<td colspan="7" class="px-4 py-10 text-center text-surface-500">
|
||||
No se han registrado depreciaciones para este activo.
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
193
src/modules/fixed-assets/components/assets/FixedAssetDetails.vue
Normal file
193
src/modules/fixed-assets/components/assets/FixedAssetDetails.vue
Normal file
@ -0,0 +1,193 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import Breadcrumb from 'primevue/breadcrumb';
|
||||
import Button from 'primevue/button';
|
||||
import Card from 'primevue/card';
|
||||
import TabView from 'primevue/tabview';
|
||||
import TabPanel from 'primevue/tabpanel';
|
||||
import Toast from 'primevue/toast';
|
||||
import fixedAssetsService, { type Asset } from '../../services/fixedAssetsService';
|
||||
import FixedAssetDepreciationTab from './FixedAssetDepreciationTab.vue';
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const toast = useToast();
|
||||
|
||||
const asset = ref<Asset | null>(null);
|
||||
const loading = ref(true);
|
||||
|
||||
const breadcrumbHome = { icon: 'pi pi-home', route: '/' };
|
||||
const breadcrumbItems = ref([
|
||||
{ label: 'Activos Fijos', route: '/fixed-assets' },
|
||||
{ label: '...' },
|
||||
]);
|
||||
|
||||
const fetchAsset = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const id = Number(route.params.id);
|
||||
const response = await fixedAssetsService.getAsset(id);
|
||||
asset.value = (response as any).data?.data ?? null;
|
||||
if (asset.value) {
|
||||
breadcrumbItems.value[1].label = asset.value.sku;
|
||||
}
|
||||
} catch {
|
||||
toast.add({ severity: 'error', summary: 'Error', detail: 'No se pudo cargar el activo.', life: 4000 });
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(fetchAsset);
|
||||
|
||||
const statusClasses: Record<number, string> = {
|
||||
1: 'bg-emerald-100 text-emerald-700',
|
||||
2: 'bg-gray-100 text-gray-700',
|
||||
3: 'bg-amber-100 text-amber-700',
|
||||
4: 'bg-red-100 text-red-700',
|
||||
};
|
||||
|
||||
const formatCurrency = (value: string | number | null | undefined) => {
|
||||
if (value === null || value === undefined) return '—';
|
||||
const num = typeof value === 'string' ? parseFloat(value) : value;
|
||||
return new Intl.NumberFormat('es-MX', { style: 'currency', currency: 'MXN' }).format(num);
|
||||
};
|
||||
|
||||
const methodLabels: Record<string, string> = {
|
||||
straight_line: 'Línea Recta',
|
||||
declining_balance: 'Saldo Decreciente',
|
||||
double_declining_balance: 'Doble Saldo Decreciente',
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<Toast position="bottom-right" />
|
||||
|
||||
<Breadcrumb :home="breadcrumbHome" :model="breadcrumbItems" />
|
||||
|
||||
<div v-if="loading" class="flex items-center justify-center py-20 text-surface-500">
|
||||
<i class="pi pi-spin pi-spinner mr-2 text-xl"></i> Cargando activo...
|
||||
</div>
|
||||
|
||||
<template v-else-if="asset">
|
||||
<!-- Header -->
|
||||
<div class="flex flex-wrap items-start justify-between gap-4">
|
||||
<div class="flex flex-col gap-2">
|
||||
<h1 class="text-surface-900 dark:text-white text-3xl md:text-4xl font-black leading-tight tracking-tight">
|
||||
{{ asset.inventory_warehouse?.product?.name ?? 'Activo sin producto' }}
|
||||
</h1>
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<div class="flex items-center gap-1.5 text-surface-500">
|
||||
<i class="pi pi-barcode text-sm"></i>
|
||||
<span class="text-sm font-mono">{{ asset.sku }}</span>
|
||||
</div>
|
||||
<template v-if="asset.asset_tag">
|
||||
<div class="size-1 bg-surface-300 rounded-full"></div>
|
||||
<div class="flex items-center gap-1.5 text-surface-500">
|
||||
<i class="pi pi-tag text-sm"></i>
|
||||
<span class="text-sm">{{ asset.asset_tag }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="size-1 bg-surface-300 rounded-full"></div>
|
||||
<span
|
||||
class="inline-flex rounded-full px-3 py-1 text-xs font-semibold"
|
||||
:class="statusClasses[asset.status?.id] ?? 'bg-gray-100 text-gray-700'"
|
||||
>
|
||||
{{ asset.status?.name ?? 'Desconocido' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
label="Editar"
|
||||
icon="pi pi-pencil"
|
||||
severity="secondary"
|
||||
outlined
|
||||
@click="router.push(`/fixed-assets/${asset.id}/edit`)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Tabs -->
|
||||
<Card class="shadow-sm">
|
||||
<template #content>
|
||||
<TabView class="w-full">
|
||||
<!-- Tab: Información general -->
|
||||
<TabPanel value="0">
|
||||
<template #header>
|
||||
<div class="flex items-center gap-2">
|
||||
<i class="pi pi-info-circle"></i>
|
||||
<span>Información</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-5 py-2">
|
||||
<div>
|
||||
<p class="text-xs font-semibold uppercase tracking-wide text-surface-400 mb-1">Producto</p>
|
||||
<p class="text-surface-900 dark:text-surface-0 font-medium">
|
||||
{{ asset.inventory_warehouse?.product?.name ?? '—' }}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs font-semibold uppercase tracking-wide text-surface-400 mb-1">Método de Depreciación</p>
|
||||
<p class="text-surface-900 dark:text-surface-0 font-medium">
|
||||
{{ methodLabels[asset.depreciation_method] ?? asset.depreciation_method ?? '—' }}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs font-semibold uppercase tracking-wide text-surface-400 mb-1">Vida Útil Estimada</p>
|
||||
<p class="text-surface-900 dark:text-surface-0 font-medium">
|
||||
{{ asset.estimated_useful_life ? `${asset.estimated_useful_life} meses` : '—' }}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs font-semibold uppercase tracking-wide text-surface-400 mb-1">Valor Residual</p>
|
||||
<p class="text-surface-900 dark:text-surface-0 font-medium">
|
||||
{{ formatCurrency(asset.residual_value) }}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs font-semibold uppercase tracking-wide text-surface-400 mb-1">Valor en Libros</p>
|
||||
<p class="text-2xl font-bold text-surface-900 dark:text-surface-0">
|
||||
{{ formatCurrency(asset.book_value) }}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs font-semibold uppercase tracking-wide text-surface-400 mb-1">Depreciación Acumulada</p>
|
||||
<p class="text-2xl font-bold text-red-600">
|
||||
{{ formatCurrency(asset.accumulated_depreciation) }}
|
||||
</p>
|
||||
</div>
|
||||
<div v-if="asset.warranty_days">
|
||||
<p class="text-xs font-semibold uppercase tracking-wide text-surface-400 mb-1">Días de Garantía</p>
|
||||
<p class="text-surface-900 dark:text-surface-0 font-medium">
|
||||
{{ asset.warranty_days }} días
|
||||
</p>
|
||||
</div>
|
||||
<div v-if="asset.warranty_end_date">
|
||||
<p class="text-xs font-semibold uppercase tracking-wide text-surface-400 mb-1">Fin de Garantía</p>
|
||||
<p class="text-surface-900 dark:text-surface-0 font-medium">
|
||||
{{ new Date(asset.warranty_end_date).toLocaleDateString('es-MX') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</TabPanel>
|
||||
|
||||
<!-- Tab: Depreciación -->
|
||||
<TabPanel value="1">
|
||||
<template #header>
|
||||
<div class="flex items-center gap-2">
|
||||
<i class="pi pi-chart-line"></i>
|
||||
<span>Depreciación</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<FixedAssetDepreciationTab :asset="asset" @refresh="fetchAsset" />
|
||||
</TabPanel>
|
||||
</TabView>
|
||||
</template>
|
||||
</Card>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
@ -125,7 +125,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>
|
||||
@ -146,9 +145,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 ?? '—' }}
|
||||
|
||||
@ -188,3 +188,48 @@ class FixedAssetsService {
|
||||
|
||||
export const fixedAssetsService = new FixedAssetsService();
|
||||
export default fixedAssetsService;
|
||||
|
||||
// ── Depreciation ──────────────────────────────────────────────────────────────
|
||||
|
||||
export interface AssetDepreciation {
|
||||
id: number;
|
||||
period_year: number;
|
||||
period_month: number;
|
||||
depreciation_amount: string;
|
||||
accumulated_depreciation: string;
|
||||
book_value: string;
|
||||
method: string;
|
||||
processed_by: { id: number; name: string } | null;
|
||||
processed_at: string | null;
|
||||
}
|
||||
|
||||
export interface AssetDepreciationResponse {
|
||||
status: string;
|
||||
data: { data: AssetDepreciation };
|
||||
}
|
||||
|
||||
export interface AssetDepreciationsListResponse {
|
||||
status: string;
|
||||
data: { data: AssetDepreciation[] };
|
||||
}
|
||||
|
||||
export interface RegisterDepreciationData {
|
||||
year: number;
|
||||
month: number;
|
||||
amount?: number | null;
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
class FixedAssetDepreciationService {
|
||||
async getDepreciations(assetId: number): Promise<AssetDepreciationsListResponse> {
|
||||
const response = await api.get<AssetDepreciationsListResponse>(`/api/assets/${assetId}/depreciations`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async registerDepreciation(assetId: number, data: RegisterDepreciationData): Promise<AssetDepreciationResponse> {
|
||||
const response = await api.post<AssetDepreciationResponse>(`/api/assets/${assetId}/depreciations`, data);
|
||||
return response.data;
|
||||
}
|
||||
}
|
||||
|
||||
export const assetDepreciationService = new FixedAssetDepreciationService();
|
||||
|
||||
@ -14,6 +14,7 @@ import ProductForm from '../modules/products/components/ProductForm.vue';
|
||||
import StoresIndex from '../modules/stores/components/StoresIndex.vue';
|
||||
import FixedAssetsIndex from '../modules/fixed-assets/components/FixedAssetsIndex.vue';
|
||||
import FixedAssetForm from '../modules/fixed-assets/components/assets/FixedAssetForm.vue';
|
||||
import FixedAssetDetails from '../modules/fixed-assets/components/assets/FixedAssetDetails.vue';
|
||||
import FixedAssetAssignmentsIndex from '../modules/fixed-assets/components/assignments/FixedAssetAssignmentsIndex.vue';
|
||||
import FixedAssetAssignmentForm from '../modules/fixed-assets/components/assignments/FixedAssetAssignmentForm.vue';
|
||||
import FixedAssetAssignmentOffboardingForm from '../modules/fixed-assets/components/assignments/FixedAssetAssignmentOffboardingForm.vue';
|
||||
@ -293,6 +294,15 @@ const routes: RouteRecordRaw[] = [
|
||||
requiresAuth: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: ':id',
|
||||
name: 'FixedAssetDetails',
|
||||
component: FixedAssetDetails,
|
||||
meta: {
|
||||
title: 'Detalle del Activo Fijo',
|
||||
requiresAuth: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'assignments',
|
||||
name: 'FixedAssetAssignmentsModule',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user