GuDingZiChan/backend/src/controllers/myAssetController.ts

195 lines
6.6 KiB
TypeScript

import type { Request, Response } from 'express'
import prisma from '../utils/prisma'
import { errorResponse, successResponse } from '../utils/response'
const parseChildrenSnapshot = (value: string | null, rootEquipmentId: number) => {
if (!value) return { items: [] as any[], valid: true }
try {
const parsed = JSON.parse(value)
if (!Array.isArray(parsed)) return { items: [] as any[], valid: false }
const depthById = new Map<number, number>([[rootEquipmentId, 0]])
const items = parsed
.filter((item) => item && typeof item === 'object')
.map((item: Record<string, unknown>) => {
const id = Number(item.id)
const rawParentId = Number(item.parent_id ?? item.parent_equipment_id)
const parentId = Number.isInteger(rawParentId) && rawParentId > 0
? rawParentId
: rootEquipmentId
const rawDepth = Number(item.depth)
const parentDepth = depthById.get(parentId)
const depth = Number.isInteger(rawDepth) && rawDepth > 0
? rawDepth
: (parentDepth ?? 0) + 1
if (Number.isInteger(id) && id > 0) depthById.set(id, depth)
return {
...item,
id,
parent_id: parentId,
parent_equipment_id: parentId,
root_equipment_id: rootEquipmentId,
depth,
relation_source: 'borrow_snapshot' as const
}
})
.filter((item) => Number.isInteger(item.id) && item.id > 0)
return { items, valid: true }
} catch {
return { items: [] as any[], valid: false }
}
}
const currentAccessorySelect = {
id: true,
equipment_code: true,
equipment_name: true,
equipment_model: true,
equipment_type: true,
status: true,
parent_id: true,
mount_status: true,
department_id: true,
storage_location_id: true
} as const
const loadCurrentAccessories = async (rootEquipmentIds: number[]) => {
const uniqueRootIds = [...new Set(rootEquipmentIds)]
const result = new Map<number, any[]>(uniqueRootIds.map((id) => [id, []]))
const nodeMeta = new Map<number, { rootEquipmentId: number; depth: number }>(
uniqueRootIds.map((id) => [id, { rootEquipmentId: id, depth: 0 }])
)
const visited = new Set(uniqueRootIds)
let parentIds = uniqueRootIds
let depth = 0
while (parentIds.length > 0) {
if (depth >= 100) {
throw Object.assign(new Error('资产父子关系超过 100 层,请先检查资产关系'), { status: 409 })
}
const children = await prisma.equipment.findMany({
where: { parent_id: { in: parentIds } },
select: currentAccessorySelect,
orderBy: { id: 'asc' }
})
if (children.length === 0) break
for (const child of children) {
if (visited.has(child.id)) {
throw Object.assign(
new Error(`资产 ${child.equipment_code} 存在循环或重复父子关系,请先检查资产关系`),
{ status: 409 }
)
}
const parentMeta = child.parent_id ? nodeMeta.get(child.parent_id) : null
if (!parentMeta) {
throw Object.assign(
new Error(`资产 ${child.equipment_code} 的父子关系不完整,请先检查资产关系`),
{ status: 409 }
)
}
const item = {
...child,
parent_equipment_id: child.parent_id,
root_equipment_id: parentMeta.rootEquipmentId,
depth: parentMeta.depth + 1,
relation_source: 'current' as const
}
visited.add(child.id)
nodeMeta.set(child.id, {
rootEquipmentId: parentMeta.rootEquipmentId,
depth: parentMeta.depth + 1
})
result.get(parentMeta.rootEquipmentId)?.push(item)
}
parentIds = children.map((child) => child.id)
depth += 1
}
return result
}
const getRelationshipFingerprint = (items: Array<{ id: number; parent_id?: number | null }>) =>
items
.map((item) => `${item.id}:${item.parent_id ?? 'null'}`)
.sort()
.join('|')
export const getMyAssets = async (_req: Request, res: Response) => {
try {
const personId = Number(res.locals.user?.person_id)
if (!Number.isInteger(personId) || personId <= 0) {
return errorResponse(res, '当前账号尚未绑定员工档案', 403)
}
const person = await prisma.person.findUnique({
where: { id: personId },
select: {
id: true,
real_name: true,
nickname: true,
job_number: true,
status: true,
source_employee_status: true,
mainDepartment: { select: { id: true, name: true } }
}
})
if (!person || person.status !== 'active') {
return errorResponse(res, '绑定的员工档案不存在或已离职', 403)
}
const records = await prisma.borrowRecord.findMany({
where: {
person_id: personId,
status: '已借出'
},
orderBy: [{ borrow_time: 'desc' }, { id: 'desc' }],
include: {
equipment: {
include: {
storageLocation: { select: { id: true, name: true, code: true } },
department: { select: { id: true, name: true } },
children: { orderBy: { id: 'asc' }, select: currentAccessorySelect }
}
}
}
})
const currentAccessoriesByRoot = await loadCurrentAccessories(
records.map((record) => record.equipment_id)
)
successResponse(res, {
person,
assets: records.map((record) => {
const currentAccessories = currentAccessoriesByRoot.get(record.equipment_id) || []
const snapshot = parseChildrenSnapshot(record.children_snapshot, record.equipment_id)
const relationshipChanged = !snapshot.valid ||
getRelationshipFingerprint(currentAccessories) !== getRelationshipFingerprint(snapshot.items)
const currentDirectChildren = currentAccessories.filter((item) => item.depth === 1)
const legacyChildren = currentDirectChildren.length > 0
? currentDirectChildren
: snapshot.items
return {
borrow_record_id: record.id,
borrow_time: record.borrow_time,
expected_return_time: record.expected_return_time,
is_long_term: record.is_long_term,
notes: record.notes,
equipment: { ...record.equipment, children: currentDirectChildren },
children: legacyChildren,
current_accessories: currentAccessories,
borrowed_accessories_snapshot: snapshot.items,
borrowed_accessories_snapshot_valid: snapshot.valid,
accessory_relationship_changed: relationshipChanged
}
})
}, '获取成功')
} catch (error: any) {
errorResponse(res, error.message || '获取本人资产失败', error.status || 500)
}
}