GuDingZiChan/backend/src/controllers/batchController.ts

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 || '批量转移失败')
}
}