const Property = require('../property/propertyModel') const Category = require('../category/categoryModel') const PropertyStatus = require('../propertyStatus/propertyStatusModel') const Warehouse = require('../warehouse/warehouseModel') const WarehouseItem = require('../warehouseItem/warehouseItemModel') const WarehouseItemTransaction = require('../warehouseItem/warehouseItemTransactionModel') const User = require('../user/userModel') const Role = require('../role/roleModel') const { getSignedUrl } = require('../file/fileService') /** * Aggregates live dashboard statistics across properties, warehouses, inventory, and users */ const getDashboardStats = async () => { // 1. Overview counts const [ totalProperties, totalAreaResult, totalWarehouseItems, totalWarehouses, totalWarehouseTransactions, totalUsers, totalCategories, totalRoles, ] = await Promise.all([ Property.countDocuments({ isActive: { $ne: false } }), Property.aggregate([ { $match: { isActive: { $ne: false }, area: { $gt: 0 } } }, { $group: { _id: null, totalArea: { $sum: '$area' } } }, ]), WarehouseItem.countDocuments({ isActive: { $ne: false } }), Warehouse.countDocuments(), WarehouseItemTransaction.countDocuments(), User.countDocuments({ isActive: { $ne: false } }), Category.countDocuments(), Role.countDocuments(), ]) const totalArea = totalAreaResult?.[0]?.totalArea || 0 // 2. Properties grouped by Category const propertiesByCategoryRaw = await Property.aggregate([ { $match: { isActive: { $ne: false } } }, { $group: { _id: '$category', count: { $sum: 1 }, totalArea: { $sum: { $ifNull: ['$area', 0] } }, }, }, { $lookup: { from: 'categories', localField: '_id', foreignField: '_id', as: 'categoryDoc', }, }, { $unwind: { path: '$categoryDoc', preserveNullAndEmptyArrays: true } }, { $sort: { count: -1 } }, ]) const propertiesByCategory = propertiesByCategoryRaw.map((item) => ({ categoryId: item._id, name: item.categoryDoc?.nativeName || item.categoryDoc?.englishName || 'بدون دسته‌بندی', englishName: item.categoryDoc?.englishName || 'Uncategorized', count: item.count, totalArea: item.totalArea || 0, percentage: totalProperties > 0 ? Math.round((item.count / totalProperties) * 100) : 0, })) // 3. Properties grouped by Status const propertiesByStatusRaw = await Property.aggregate([ { $match: { isActive: { $ne: false } } }, { $group: { _id: '$status', count: { $sum: 1 }, }, }, { $lookup: { from: 'propertystatuses', localField: '_id', foreignField: '_id', as: 'statusDoc', }, }, { $unwind: { path: '$statusDoc', preserveNullAndEmptyArrays: true } }, { $sort: { count: -1 } }, ]) const propertiesByStatus = propertiesByStatusRaw.map((item) => ({ statusId: item._id, name: item.statusDoc?.nativeName || item.statusDoc?.englishName || 'نامشخص', englishName: item.statusDoc?.englishName || 'Unknown', color: item.statusDoc?.color || '#3b82f6', count: item.count, percentage: totalProperties > 0 ? Math.round((item.count / totalProperties) * 100) : 0, })) // 4. Warehouse transactions summary (IN vs OUT) const warehouseFlowRaw = await WarehouseItemTransaction.aggregate([ { $group: { _id: '$type', count: { $sum: 1 }, totalQuantity: { $sum: '$quantity' }, }, }, ]) const warehouseFlow = { inCount: warehouseFlowRaw.find((f) => f._id === 'IN')?.count || 0, outCount: warehouseFlowRaw.find((f) => f._id === 'OUT')?.count || 0, inQuantity: warehouseFlowRaw.find((f) => f._id === 'IN')?.totalQuantity || 0, outQuantity: warehouseFlowRaw.find((f) => f._id === 'OUT')?.totalQuantity || 0, } // 5. Recent registered properties (Last 6) const recentPropertiesRaw = await Property.find({ isActive: { $ne: false } }) .sort({ createdAt: -1 }) .limit(6) .populate('category', 'nativeName englishName') .populate('status', 'nativeName englishName color') .lean() const recentProperties = await Promise.all( recentPropertiesRaw.map(async (prop) => { let thumb = null if (prop.mainImageUrls && prop.mainImageUrls.length > 0) { thumb = await getSignedUrl(prop.mainImageUrls[0], 'properties').catch(() => null) } return { _id: prop._id, propertyCode: prop.propertyCode || prop._id.toString().slice(-6), name: prop.name || 'ملک بدون عنوان', category: prop.category?.nativeName || 'عمومی', status: prop.status?.nativeName || 'عادی', statusColor: prop.status?.color || '#3b82f6', ownerName: prop.ownerName || '—', area: prop.area || 0, dong: prop.dong || 6, imageUrl: thumb || 'https://cloud.mbmanager.ir/images/item-no-image.png', registrationPlate: prop.registrationPlate || '—', createdAt: prop.createdAt, } }) ) // 6. Recent warehouse transactions (Last 6) const recentWarehouseTransactionsRaw = await WarehouseItemTransaction.find() .sort({ createdAt: -1 }) .limit(6) .populate('item', 'nameFa nameEn code unitFa') .populate('warehouse', 'name location') .lean() const recentWarehouseTransactions = recentWarehouseTransactionsRaw.map((tx) => ({ _id: tx._id, type: tx.type, // 'IN' | 'OUT' quantity: tx.quantity, unit: tx.unit || tx.item?.unitFa || 'عدد', itemName: tx.item?.nameFa || 'کالای نامشخص', itemCode: tx.item?.code || '—', warehouseName: tx.warehouse?.name || 'انبار مرکزی', reason: tx.reason || (tx.type === 'IN' ? 'ورود به انبار' : 'خروج از انبار'), actorName: tx.actorName || 'کاربر سیستم', date: tx.date || tx.createdAt, })) // 7. Monthly Property & Warehouse Trend (Last 6 months) const sixMonthsAgo = new Date() sixMonthsAgo.setMonth(sixMonthsAgo.getMonth() - 5) sixMonthsAgo.setDate(1) sixMonthsAgo.setHours(0, 0, 0, 0) const monthlyPropertiesRaw = await Property.aggregate([ { $match: { createdAt: { $gte: sixMonthsAgo }, isActive: { $ne: false } } }, { $group: { _id: { year: { $year: '$createdAt' }, month: { $month: '$createdAt' }, }, count: { $sum: 1 }, }, }, { $sort: { '_id.year': 1, '_id.month': 1 } }, ]) const monthlyWarehouseRaw = await WarehouseItemTransaction.aggregate([ { $match: { createdAt: { $gte: sixMonthsAgo } } }, { $group: { _id: { year: { $year: '$createdAt' }, month: { $month: '$createdAt' }, type: '$type', }, count: { $sum: 1 }, }, }, { $sort: { '_id.year': 1, '_id.month': 1 } }, ]) return { overview: { totalProperties, totalArea, totalWarehouseItems, totalWarehouses, totalWarehouseTransactions, totalUsers, totalCategories, totalRoles, }, propertiesByCategory, propertiesByStatus, warehouseFlow, recentProperties, recentWarehouseTransactions, monthlyTrend: { properties: monthlyPropertiesRaw, warehouse: monthlyWarehouseRaw, }, } } module.exports = { getDashboardStats, }