365 lines
12 KiB
TypeScript
365 lines
12 KiB
TypeScript
import { Request, Response } from 'express'
|
|
import prisma from '../utils/prisma'
|
|
import { successResponse, errorResponse } from '../utils/response'
|
|
import { logOperation } from './operationLogController'
|
|
import { calculateEquipmentValues, syncMountedChildrenState } from '../utils/assetValue'
|
|
|
|
const buildBorrowNotes = (notes?: string, isLongTerm?: boolean) => {
|
|
const longTerm = Boolean(isLongTerm)
|
|
return longTerm && notes
|
|
? `${notes};在职长期`
|
|
: longTerm
|
|
? '在职长期'
|
|
: notes || undefined
|
|
}
|
|
|
|
const getChildrenSnapshot = async (equipmentId: number) => {
|
|
const children = await prisma.equipment.findMany({
|
|
where: { parent_id: equipmentId },
|
|
select: {
|
|
id: true,
|
|
equipment_code: true,
|
|
equipment_name: true,
|
|
equipment_model: true,
|
|
equipment_type: true,
|
|
unit_price: true
|
|
}
|
|
})
|
|
|
|
return children.length > 0 ? JSON.stringify(children) : undefined
|
|
}
|
|
|
|
const batchBorrowTransaction = async (
|
|
equipment: any,
|
|
person: any,
|
|
expectedReturnTime?: string,
|
|
isLongTerm?: boolean,
|
|
notes?: string
|
|
) => {
|
|
const childrenSnapshot = await getChildrenSnapshot(equipment.id)
|
|
const finalNotes = buildBorrowNotes(notes, isLongTerm)
|
|
|
|
return prisma.$transaction(async (tx) => {
|
|
await tx.borrowRecord.create({
|
|
data: {
|
|
equipment_id: equipment.id,
|
|
equipment_code: equipment.equipment_code,
|
|
person_id: person.id,
|
|
person_name: person.real_name,
|
|
person_nickname: person.nickname,
|
|
person_identifier: person.id_number,
|
|
expected_return_time: isLongTerm ? null : (expectedReturnTime ? new Date(expectedReturnTime) : null),
|
|
is_long_term: Boolean(isLongTerm),
|
|
notes: finalNotes,
|
|
status: '已借出',
|
|
children_snapshot: childrenSnapshot
|
|
}
|
|
})
|
|
|
|
const nextRemainingQuantity = Math.max(Number(equipment.remaining_quantity) - 1, 0)
|
|
const nextStatus = nextRemainingQuantity > 0 ? '可用' : '已借出'
|
|
const values = calculateEquipmentValues({
|
|
unitPrice: Number(equipment.unit_price),
|
|
totalQuantity: Number(equipment.total_quantity),
|
|
remainingQuantity: nextRemainingQuantity,
|
|
status: nextStatus
|
|
})
|
|
const updated = await tx.equipment.updateMany({
|
|
where: {
|
|
id: equipment.id,
|
|
status: '可用',
|
|
remaining_quantity: equipment.remaining_quantity
|
|
},
|
|
data: {
|
|
remaining_quantity: nextRemainingQuantity,
|
|
total_value: values.total_value,
|
|
borrowed_value: values.borrowed_value,
|
|
available_value: values.available_value,
|
|
status: nextStatus
|
|
}
|
|
})
|
|
|
|
if (updated.count !== 1) {
|
|
throw new Error('资产库存已变化,请刷新后重试')
|
|
}
|
|
|
|
await syncMountedChildrenState(tx, equipment.id, '已借出')
|
|
})
|
|
}
|
|
|
|
const batchReturnTransaction = async (
|
|
equipment: any,
|
|
borrowRecord: any,
|
|
returnCondition: string,
|
|
notes?: string
|
|
) => {
|
|
return prisma.$transaction(async (tx) => {
|
|
const updatedRecord = await tx.borrowRecord.updateMany({
|
|
where: {
|
|
id: borrowRecord.id,
|
|
status: '已借出'
|
|
},
|
|
data: {
|
|
return_time: new Date(),
|
|
status: '已归还',
|
|
return_condition: returnCondition,
|
|
notes
|
|
}
|
|
})
|
|
|
|
if (updatedRecord.count !== 1) {
|
|
throw new Error('该借用记录已归还,请刷新后重试')
|
|
}
|
|
|
|
const nextRemainingQuantity = Math.min(
|
|
Number(equipment.total_quantity),
|
|
Number(equipment.remaining_quantity) + 1
|
|
)
|
|
const values = calculateEquipmentValues({
|
|
unitPrice: Number(equipment.unit_price),
|
|
totalQuantity: Number(equipment.total_quantity),
|
|
remainingQuantity: nextRemainingQuantity,
|
|
status: '可用'
|
|
})
|
|
const updatedEquipment = await tx.equipment.updateMany({
|
|
where: {
|
|
id: equipment.id,
|
|
remaining_quantity: { lt: equipment.total_quantity }
|
|
},
|
|
data: {
|
|
remaining_quantity: nextRemainingQuantity,
|
|
total_value: values.total_value,
|
|
borrowed_value: values.borrowed_value,
|
|
available_value: values.available_value,
|
|
status: '可用'
|
|
}
|
|
})
|
|
|
|
if (updatedEquipment.count !== 1) {
|
|
throw new Error('资产库存已达到上限,请检查归还记录')
|
|
}
|
|
|
|
await syncMountedChildrenState(tx, equipment.id, '可用')
|
|
})
|
|
}
|
|
|
|
// 批量借出
|
|
export const batchBorrow = async (req: Request, res: Response) => {
|
|
try {
|
|
const { equipment_ids, person_id, expected_return_time, is_long_term, notes } = req.body
|
|
|
|
if (!Array.isArray(equipment_ids) || equipment_ids.length === 0) {
|
|
return errorResponse(res, '请选择要借出的资产')
|
|
}
|
|
|
|
const person = await prisma.person.findUnique({ where: { id: Number(person_id) } })
|
|
if (!person || person.status !== 'active') {
|
|
return errorResponse(res, '使用者不存在或已禁用')
|
|
}
|
|
|
|
const results: any[] = []
|
|
let successCount = 0
|
|
let failedCount = 0
|
|
|
|
for (const equipmentId of equipment_ids) {
|
|
try {
|
|
const equipment = await prisma.equipment.findUnique({ where: { id: Number(equipmentId) } })
|
|
|
|
if (!equipment) {
|
|
results.push({ equipment_id: equipmentId, status: 'failed', message: '资产不存在' })
|
|
failedCount++
|
|
continue
|
|
}
|
|
|
|
if (equipment.parent_id) {
|
|
results.push({
|
|
equipment_id: equipmentId,
|
|
equipment_code: equipment.equipment_code,
|
|
status: 'failed',
|
|
message: '该子资产当前已挂载,请先卸载后再借用'
|
|
})
|
|
failedCount++
|
|
continue
|
|
}
|
|
|
|
if (equipment.status !== '可用' || equipment.remaining_quantity <= 0) {
|
|
results.push({ equipment_id: equipmentId, equipment_code: equipment.equipment_code, status: 'failed', message: '资产不可借用' })
|
|
failedCount++
|
|
continue
|
|
}
|
|
|
|
await batchBorrowTransaction(
|
|
equipment,
|
|
person,
|
|
expected_return_time,
|
|
is_long_term,
|
|
notes
|
|
)
|
|
|
|
results.push({ equipment_id: equipmentId, equipment_code: equipment.equipment_code, status: 'success', message: '借出成功' })
|
|
successCount++
|
|
} catch (error: any) {
|
|
results.push({ equipment_id: equipmentId, status: 'failed', message: error.message })
|
|
failedCount++
|
|
}
|
|
}
|
|
|
|
// 记录操作日志
|
|
const userId = res.locals.user?.id
|
|
const username = res.locals.user?.username || res.locals.user?.name || 'unknown'
|
|
const successCodes = results.filter(r => r.status === 'success').map(r => r.equipment_code).join(', ')
|
|
if (successCount > 0) {
|
|
await logOperation(
|
|
userId,
|
|
username,
|
|
'批量借出',
|
|
`批量借出 ${successCount} 件资产给 ${person.real_name}(${person.nickname}),资产编码:${successCodes}`,
|
|
undefined,
|
|
undefined,
|
|
req.ip
|
|
)
|
|
}
|
|
|
|
successResponse(res, { success_count: successCount, failed_count: failedCount, results }, '批量借出完成')
|
|
} catch (error: any) {
|
|
errorResponse(res, error.message || '批量借出失败')
|
|
}
|
|
}
|
|
|
|
// 批量归还
|
|
export const batchReturn = async (req: Request, res: Response) => {
|
|
try {
|
|
const { equipment_ids, return_condition = '正常', notes } = req.body
|
|
|
|
if (!Array.isArray(equipment_ids) || equipment_ids.length === 0) {
|
|
return errorResponse(res, '请选择要归还的资产')
|
|
}
|
|
|
|
const results: any[] = []
|
|
let successCount = 0
|
|
let failedCount = 0
|
|
|
|
for (const equipmentId of equipment_ids) {
|
|
try {
|
|
const equipment = await prisma.equipment.findUnique({ where: { id: Number(equipmentId) } })
|
|
|
|
if (!equipment) {
|
|
results.push({ equipment_id: equipmentId, status: 'failed', message: '资产不存在' })
|
|
failedCount++
|
|
continue
|
|
}
|
|
|
|
const activeBorrowRecords = await prisma.borrowRecord.findMany({
|
|
where: { equipment_id: equipment.id, status: '已借出' },
|
|
orderBy: { borrow_time: 'desc' },
|
|
take: 2
|
|
})
|
|
const borrowRecord = activeBorrowRecords[0]
|
|
|
|
if (!borrowRecord) {
|
|
results.push({ equipment_id: equipmentId, equipment_code: equipment.equipment_code, status: 'failed', message: '未找到借用记录' })
|
|
failedCount++
|
|
continue
|
|
}
|
|
|
|
if (activeBorrowRecords.length > 1) {
|
|
results.push({ equipment_id: equipmentId, equipment_code: equipment.equipment_code, status: 'failed', message: '存在多条未归还记录,请使用单个归还' })
|
|
failedCount++
|
|
continue
|
|
}
|
|
|
|
await batchReturnTransaction(equipment, borrowRecord, return_condition, notes)
|
|
|
|
results.push({ equipment_id: equipmentId, equipment_code: equipment.equipment_code, status: 'success', message: '归还成功' })
|
|
successCount++
|
|
} catch (error: any) {
|
|
results.push({ equipment_id: equipmentId, status: 'failed', message: error.message })
|
|
failedCount++
|
|
}
|
|
}
|
|
|
|
// 记录操作日志
|
|
const userId = res.locals.user?.id
|
|
const username = res.locals.user?.username || res.locals.user?.name || 'unknown'
|
|
const successCodes = results.filter(r => r.status === 'success').map(r => r.equipment_code).join(', ')
|
|
if (successCount > 0) {
|
|
await logOperation(
|
|
userId,
|
|
username,
|
|
'批量归还',
|
|
`批量归还 ${successCount} 件资产,归还状态:${return_condition},资产编码:${successCodes}`,
|
|
undefined,
|
|
undefined,
|
|
req.ip
|
|
)
|
|
}
|
|
|
|
successResponse(res, { success_count: successCount, failed_count: failedCount, results }, '批量归还完成')
|
|
} catch (error: any) {
|
|
errorResponse(res, error.message || '批量归还失败')
|
|
}
|
|
}
|
|
|
|
// 批量转移部门
|
|
export const batchTransfer = async (req: Request, res: Response) => {
|
|
try {
|
|
const { equipment_ids, department_id } = req.body
|
|
|
|
if (!Array.isArray(equipment_ids) || equipment_ids.length === 0) {
|
|
return errorResponse(res, '请选择要转移的资产')
|
|
}
|
|
|
|
const department = await prisma.department.findUnique({ where: { id: Number(department_id) } })
|
|
if (!department || department.status !== 'active') {
|
|
return errorResponse(res, '目标部门不存在或已停用')
|
|
}
|
|
|
|
const results: any[] = []
|
|
let successCount = 0
|
|
let failedCount = 0
|
|
|
|
for (const equipmentId of equipment_ids) {
|
|
try {
|
|
const equipment = await prisma.equipment.findUnique({ where: { id: Number(equipmentId) } })
|
|
|
|
if (!equipment) {
|
|
results.push({ equipment_id: equipmentId, status: 'failed', message: '资产不存在' })
|
|
failedCount++
|
|
continue
|
|
}
|
|
|
|
await prisma.equipment.update({
|
|
where: { id: equipment.id },
|
|
data: { department_id: department.id }
|
|
})
|
|
|
|
results.push({ equipment_id: equipmentId, equipment_code: equipment.equipment_code, status: 'success', message: '转移成功' })
|
|
successCount++
|
|
} catch (error: any) {
|
|
results.push({ equipment_id: equipmentId, status: 'failed', message: error.message })
|
|
failedCount++
|
|
}
|
|
}
|
|
|
|
// 记录操作日志
|
|
const userId = res.locals.user?.id
|
|
const username = res.locals.user?.username || res.locals.user?.name || 'unknown'
|
|
const successCodes = results.filter(r => r.status === 'success').map(r => r.equipment_code).join(', ')
|
|
if (successCount > 0) {
|
|
await logOperation(
|
|
userId,
|
|
username,
|
|
'批量转移',
|
|
`批量转移 ${successCount} 件资产到部门 ${department.name},资产编码:${successCodes}`,
|
|
undefined,
|
|
undefined,
|
|
req.ip
|
|
)
|
|
}
|
|
|
|
successResponse(res, { success_count: successCount, failed_count: failedCount, results }, '批量转移完成')
|
|
} catch (error: any) {
|
|
errorResponse(res, error.message || '批量转移失败')
|
|
}
|
|
}
|