feat: 恢复IT资产详情直接维护

This commit is contained in:
WuYongJie 2026-07-18 16:12:20 +08:00
parent 963dcc06af
commit f69406e888
4 changed files with 98 additions and 2 deletions

View File

@ -64,6 +64,34 @@ const includeStorageLocation = {
}
}
const EQUIPMENT_UPDATE_FIELD_LABELS: Record<string, string> = {
equipment_code: '资产编码',
equipment_name: '资产名称',
equipment_model: '资产型号',
equipment_type: '资产类型',
asset_type_id: '资产分类',
unit_price: '单价',
total_quantity: '库存总数量',
purchase_date: '采购日期',
supplier: '供应商',
description: '资产描述',
storage_location_id: '存放地址'
}
const buildEquipmentUpdateOperationDetails = (
currentEquipment: Record<string, any>,
updateData: Record<string, unknown>
) => {
const changedFields = Object.keys(EQUIPMENT_UPDATE_FIELD_LABELS).filter((field) =>
Object.prototype.hasOwnProperty.call(updateData, field)
)
return `直接维护资产资料:${changedFields.map((field) => {
const before = currentEquipment[field] ?? '-'
const after = updateData[field] ?? '-'
return `${EQUIPMENT_UPDATE_FIELD_LABELS[field]} ${before} → ${after}`
}).join(';')}`
}
const activeBorrowSelect = {
borrow_records: {
where: {
@ -1334,6 +1362,19 @@ export const updateEquipment = async (req: Request, res: Response) => {
await refreshParentTotalValueWithChildren(tx, currentEquipment.parent_id)
}
const operator = res.locals.user
await tx.operationLog.create({
data: {
user_id: operator?.id || null,
username: operator?.username || operator?.name || '未知用户',
operation_type: 'asset_basic_update',
equipment_id: currentEquipment.id,
equipment_code: currentEquipment.equipment_code,
operation_details: buildEquipmentUpdateOperationDetails(currentEquipment, updateData),
ip_address: req.ip || null
}
})
return true
})

View File

@ -21,8 +21,9 @@ test('行政可以登记和审批但不能借还、调拨或挂载', () => {
assert.ok(!permissions.includes(PERMISSIONS.ASSETS_MOUNT))
})
test('IT可以流转、发起报废和盘点,但不能审批或管理用户', () => {
test('IT可以直接维护资产资料、流转、发起报废和盘点,但不能审批或管理用户', () => {
const permissions = getPermissionsForRole('it')
assert.ok(permissions.includes(PERMISSIONS.ASSETS_CORRECT))
assert.ok(permissions.includes(PERMISSIONS.ASSETS_BORROW))
assert.ok(permissions.includes(PERMISSIONS.ASSETS_TRANSFER))
assert.ok(permissions.includes(PERMISSIONS.ASSETS_PRINT_LABELS))

View File

@ -45,6 +45,7 @@ const ROLE_PERMISSIONS: Record<UserRole, readonly Permission[]> = {
],
it: [
PERMISSIONS.ASSETS_VIEW_ALL,
PERMISSIONS.ASSETS_CORRECT,
PERMISSIONS.ASSETS_BORROW,
PERMISSIONS.ASSETS_TRANSFER,
PERMISSIONS.ASSETS_MAINTENANCE,

View File

@ -71,7 +71,12 @@
<div class="field-stack">
<label class="field-label">使用部门</label>
<select v-model="form.department_id" class="select" :disabled="isEdit || !canTransfer">
<select
v-model="form.department_id"
class="select"
:disabled="isEdit ? !canChangeDepartmentDirectly : !canTransfer"
@change="handleDepartmentChange"
>
<option value="">请选择使用部门</option>
<template v-for="department in departments" :key="department.id">
<option :value="department.id">{{ department.name }}</option>
@ -431,6 +436,7 @@ import { createEquipment, updateEquipment, getEquipmentDetail, generateEquipment
import { getStorageLocationOptions } from '@/api/storageLocation'
import { getAssetTypeOptions } from '@/api/assetType'
import { getDepartmentOptions } from '@/api/department'
import { batchTransfer } from '@/api/batch'
import { getMaintenanceList, createMaintenance } from '@/api/maintenance'
import { getEquipmentDisposalHistory, createDisposal } from '@/api/disposal'
import type { Equipment, StorageLocation, AssetType, Department, CodeGenerationMode, EquipmentStatus } from '@/types'
@ -507,6 +513,7 @@ const showDisposalForm = ref(false)
const showBarcodePrint = ref(false)
const currentEquipment = ref<Equipment | null>(null)
const selectedDisposalItemIds = ref<number[]>([])
const departmentTransferLoading = ref(false)
interface DisposalTreeItem {
id: number
@ -541,6 +548,10 @@ const canEditEquipmentCode = computed(() => {
if (!isEdit.value) return true
return canEditBasic.value && currentEquipment.value?.can_correct_code === true
})
const canChangeDepartmentDirectly = computed(() => {
if (!isEdit.value || !canTransfer.value || departmentTransferLoading.value) return false
return !currentEquipment.value?.parent_id
})
const canStartMaintenance = computed(() => {
if (!canMaintain.value || !currentEquipment.value || currentEquipment.value.parent_id) return false
return !['已借出', '维护中', '待报废', '已报废'].includes(currentEquipment.value.status)
@ -856,6 +867,48 @@ const fetchDisposalRecords = async () => {
} catch (error) { console.error('获取报废记录失败:', error) }
}
const handleDepartmentChange = async () => {
if (!isEdit.value || !currentEquipment.value) return
const currentDepartmentId = currentEquipment.value.department_id ?? undefined
const targetDepartmentId = form.value.department_id ? Number(form.value.department_id) : undefined
if (targetDepartmentId === currentDepartmentId) return
if (!targetDepartmentId) {
form.value.department_id = currentDepartmentId
alert('转为闲置请在资产管理列表中使用“批量转移”。')
return
}
const targetDepartment = departments.value.find((department) => department.id === targetDepartmentId)
const confirmed = confirm(`确认将“${currentEquipment.value.equipment_name}”调拨至“${targetDepartment?.name || '所选部门'}”?`)
if (!confirmed) {
form.value.department_id = currentDepartmentId
return
}
departmentTransferLoading.value = true
try {
const response = await batchTransfer({
equipment_ids: [currentEquipment.value.id],
target_type: 'department',
department_id: targetDepartmentId,
reason: `详情页调拨:${currentEquipment.value.department?.name || '未分配部门'} → ${targetDepartment?.name || '所选部门'}`
})
const result = response.data?.results?.[0]
if (!response.data || response.data.success_count !== 1 || result?.status !== 'success') {
throw new Error(result?.message || '资产调拨失败')
}
await fetchEquipmentData()
alert('使用部门已更新')
} catch (error: any) {
form.value.department_id = currentDepartmentId
alert(error?.message || '资产调拨失败')
} finally {
departmentTransferLoading.value = false
}
}
const submitMaintenance = async () => {
if (!canStartMaintenance.value) return
try {