195 lines
6.6 KiB
TypeScript
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)
|
|
}
|
|
}
|