import { Request, Response } from 'express' import { Prisma } from '@prisma/client' import prisma from '../utils/prisma' import { successResponse, errorResponse } from '../utils/response' import { getEquipmentDisplayAvailableValue, getEquipmentDisplayBorrowedValue, getEquipmentDisplayItemValue, getEquipmentDisplayTotalValue, sumSnapshotChildrenValue } from '../utils/assetValue' type DisplayValueRow = { equipment_type?: string | null status?: string | null total_value?: unknown borrowed_value?: unknown available_value?: unknown total_value_with_children?: unknown } type DisplayValueSummary = { total_value: number borrowed_value: number available_value: number maintenance_value: number } const toNumber = (value: unknown) => { if (value === null || value === undefined) return 0 const parsed = Number(value) return Number.isNaN(parsed) ? 0 : parsed } const buildEquipmentWhere = (equipmentType?: string, startDate?: string, endDate?: string) => { const where: Prisma.EquipmentWhereInput = {} if (equipmentType) { where.equipment_type = equipmentType } if (startDate || endDate) { where.created_at = {} if (startDate) { where.created_at.gte = new Date(startDate) } if (endDate) { const endOfDay = new Date(endDate) endOfDay.setHours(23, 59, 59, 999) where.created_at.lte = endOfDay } } return where } const summarizeDisplayValues = (rows: DisplayValueRow[]): DisplayValueSummary => rows.reduce( (summary, row) => { const totalValue = getEquipmentDisplayTotalValue(row) summary.total_value += totalValue summary.borrowed_value += getEquipmentDisplayBorrowedValue(row) summary.available_value += getEquipmentDisplayAvailableValue(row) if (row.status === '维护中') { summary.maintenance_value += totalValue } return summary }, { total_value: 0, borrowed_value: 0, available_value: 0, maintenance_value: 0 } ) // 获取首页统计数据 export const getDashboardStats = async (req: Request, res: Response) => { try { const startOfToday = new Date(new Date().setHours(0, 0, 0, 0)) const [ equipmentTotals, availableInventoryTotals, borrowableInventoryTotals, maintenanceTotals, inventoryByType, displayValueRows, todayBorrowCount, todayReturnCount, todayBorrowRecords, todayReturnRecords ] = await Promise.all([ prisma.equipment.aggregate({ _sum: { total_quantity: true, total_value: true, borrowed_value: true, available_value: true } }), prisma.equipment.aggregate({ where: { status: '可用' }, _sum: { remaining_quantity: true } }), prisma.equipment.aggregate({ where: { status: { in: ['可用', '已借出'] } }, _sum: { total_quantity: true, remaining_quantity: true } }), prisma.equipment.aggregate({ where: { status: '维护中' }, _sum: { total_quantity: true, total_value: true } }), prisma.equipment.groupBy({ by: ['equipment_type', 'status'], _sum: { total_quantity: true, remaining_quantity: true } }), prisma.equipment.findMany({ where: { parent_id: null }, select: { status: true, total_value: true, borrowed_value: true, available_value: true, total_value_with_children: true } }), prisma.borrowRecord.count({ where: { borrow_time: { gte: startOfToday } } }), prisma.borrowRecord.count({ where: { return_time: { gte: startOfToday } } }), prisma.borrowRecord.findMany({ where: { borrow_time: { gte: startOfToday } }, select: { children_snapshot: true, equipment: { select: { unit_price: true } } } }), prisma.borrowRecord.findMany({ where: { return_time: { gte: startOfToday } }, select: { children_snapshot: true, equipment: { select: { unit_price: true } } } }) ]) const totalEquipmentQuantity = toNumber(equipmentTotals._sum.total_quantity) const availableEquipmentQuantity = toNumber(availableInventoryTotals._sum.remaining_quantity) const borrowedEquipmentQuantity = Math.max( toNumber(borrowableInventoryTotals._sum.total_quantity) - toNumber(borrowableInventoryTotals._sum.remaining_quantity), 0 ) const maintenanceEquipmentQuantity = toNumber(maintenanceTotals._sum.total_quantity) const displayValueSummary = summarizeDisplayValues(displayValueRows) const totalValue = displayValueSummary.total_value const borrowedValue = displayValueSummary.borrowed_value const availableValue = displayValueSummary.available_value const maintenanceValue = displayValueSummary.maintenance_value const todayBorrowValue = todayBorrowRecords.reduce((sum, record) => { return sum + toNumber(record.equipment?.unit_price) + sumSnapshotChildrenValue(record.children_snapshot) }, 0) const todayReturnValue = todayReturnRecords.reduce((sum, record) => { return sum + toNumber(record.equipment?.unit_price) + sumSnapshotChildrenValue(record.children_snapshot) }, 0) const usageRate = totalEquipmentQuantity > 0 ? (borrowedEquipmentQuantity / totalEquipmentQuantity) * 100 : 0 const valueUsageRate = totalValue > 0 ? (borrowedValue / totalValue) * 100 : 0 successResponse(res, { total_equipment: totalEquipmentQuantity, available_equipment: availableEquipmentQuantity, borrowed_equipment: borrowedEquipmentQuantity, maintenance_equipment: maintenanceEquipmentQuantity, usage_rate: usageRate, today_borrow: todayBorrowCount, today_return: todayReturnCount, total_value: totalValue, borrowed_value: borrowedValue, available_value: availableValue, maintenance_value: maintenanceValue, value_usage_rate: valueUsageRate, today_borrow_value: todayBorrowValue, today_return_value: todayReturnValue, inventory_by_type: Array.from( inventoryByType.reduce((map, item) => { const equipmentType = item.equipment_type || '其他' const current = map.get(equipmentType) || { equipment_type: equipmentType, total_quantity: 0, available_quantity: 0, borrowed_quantity: 0 } const totalQuantity = toNumber(item._sum.total_quantity) const remainingQuantity = toNumber(item._sum.remaining_quantity) current.total_quantity += totalQuantity if (item.status === '可用') { current.available_quantity += remainingQuantity current.borrowed_quantity += Math.max(totalQuantity - remainingQuantity, 0) } else if (item.status === '已借出') { current.borrowed_quantity += Math.max(totalQuantity - remainingQuantity, 0) } map.set(equipmentType, current) return map }, new Map()).values() ) .sort((a, b) => b.total_quantity - a.total_quantity) .filter((item) => item.total_quantity > 0) }, '获取成功') } catch (error: any) { errorResponse(res, error.message || '获取统计数据失败') } } // 获取资产价值统计 export const getValueStatistics = async (req: Request, res: Response) => { try { const { equipment_type, start_date, end_date } = req.query const where = buildEquipmentWhere( typeof equipment_type === 'string' ? equipment_type : undefined, typeof start_date === 'string' ? start_date : undefined, typeof end_date === 'string' ? end_date : undefined ) const displayValueWhere: Prisma.EquipmentWhereInput = { ...where, parent_id: null } const [displayValueRows, topEquipmentRows] = await Promise.all([ prisma.equipment.findMany({ where: displayValueWhere, select: { equipment_type: true, status: true, total_value: true, borrowed_value: true, available_value: true, total_value_with_children: true } }), prisma.equipment.findMany({ where, select: { equipment_name: true, unit_price: true, total_quantity: true, total_value: true, total_value_with_children: true, borrowed_value: true, available_value: true, status: true }, }) ]) const summary = summarizeDisplayValues(displayValueRows) const valueByType = Array.from( displayValueRows.reduce((map, item) => { const equipmentType = item.equipment_type || '未分类' const current = map.get(equipmentType) || { equipment_type: equipmentType, total_value: 0, borrowed_value: 0, available_value: 0 } current.total_value += getEquipmentDisplayTotalValue(item) current.borrowed_value += getEquipmentDisplayBorrowedValue(item) current.available_value += getEquipmentDisplayAvailableValue(item) map.set(equipmentType, current) return map }, new Map()) .values() ) .sort((left, right) => right.total_value - left.total_value) .filter((item) => item.total_value > 0) const topEquipment = topEquipmentRows .map((eq) => ({ equipment_name: eq.equipment_name, quantity: toNumber(eq.total_quantity) || 1, unit_price: toNumber(eq.unit_price), item_value: getEquipmentDisplayItemValue(eq), stock_total_value: getEquipmentDisplayTotalValue(eq), stock_borrowed_value: getEquipmentDisplayBorrowedValue(eq), stock_available_value: getEquipmentDisplayAvailableValue(eq) })) .sort((a, b) => b.item_value - a.item_value || b.stock_total_value - a.stock_total_value) .slice(0, 10) successResponse(res, { total_value: summary.total_value, borrowed_value: summary.borrowed_value, available_value: summary.available_value, maintenance_value: summary.maintenance_value, value_by_type: valueByType, top_equipment: topEquipment }, '获取成功') } catch (error: any) { errorResponse(res, error.message || '获取价值统计失败') } }