From 41b5cb6faeb40233fe177b95b3a38bdee8da0919 Mon Sep 17 00:00:00 2001 From: innoavvlasov Date: Tue, 4 Nov 2025 22:39:29 +0300 Subject: [PATCH] update --- server/routers/procurement/index.js | 6 +- server/routers/procurement/models/Activity.js | 61 +++++++++ server/routers/procurement/routes/activity.js | 101 +++++++++++++++ .../procurement/scripts/seed-activities.js | 122 ++++++++++++++++++ .../procurement/scripts/seed-requests.js | 114 ++++++++++++++++ server/utils/mongoose.ts | 5 + 6 files changed, 408 insertions(+), 1 deletion(-) create mode 100644 server/routers/procurement/models/Activity.js create mode 100644 server/routers/procurement/routes/activity.js create mode 100644 server/routers/procurement/scripts/seed-activities.js create mode 100644 server/routers/procurement/scripts/seed-requests.js diff --git a/server/routers/procurement/index.js b/server/routers/procurement/index.js index 518ff7a..c912fa9 100644 --- a/server/routers/procurement/index.js +++ b/server/routers/procurement/index.js @@ -2,7 +2,9 @@ const express = require('express'); const cors = require('cors'); const dotenv = require('dotenv'); const fs = require('fs'); -const mongoose = require('mongoose'); + +// Импортировать mongoose из общего модуля (подключение происходит в server/utils/mongoose.ts) +const mongoose = require('../../utils/mongoose'); // Загрузить переменные окружения dotenv.config(); @@ -27,6 +29,7 @@ const reviewsRoutes = require('./routes/reviews'); const buyProductsRoutes = require('./routes/buyProducts'); const requestsRoutes = require('./routes/requests'); const homeRoutes = require('./routes/home'); +const activityRoutes = require('./routes/activity'); const app = express(); @@ -89,6 +92,7 @@ app.use('/products', productsRoutes); app.use('/reviews', reviewsRoutes); app.use('/requests', requestsRoutes); app.use('/home', homeRoutes); +app.use('/activities', activityRoutes); // Обработка ошибок app.use((err, req, res, next) => { diff --git a/server/routers/procurement/models/Activity.js b/server/routers/procurement/models/Activity.js new file mode 100644 index 0000000..1b94578 --- /dev/null +++ b/server/routers/procurement/models/Activity.js @@ -0,0 +1,61 @@ +const mongoose = require('mongoose'); + +const activitySchema = new mongoose.Schema({ + companyId: { + type: String, + required: true, + index: true + }, + userId: { + type: String, + required: true + }, + type: { + type: String, + enum: [ + 'message_received', + 'message_sent', + 'request_received', + 'request_sent', + 'request_response', + 'product_accepted', + 'review_received', + 'profile_updated', + 'product_added', + 'buy_product_added' + ], + required: true + }, + title: { + type: String, + required: true + }, + description: { + type: String + }, + relatedCompanyId: { + type: String + }, + relatedCompanyName: { + type: String + }, + metadata: { + type: mongoose.Schema.Types.Mixed + }, + read: { + type: Boolean, + default: false + }, + createdAt: { + type: Date, + default: Date.now, + index: true + } +}); + +// Индексы для оптимизации +activitySchema.index({ companyId: 1, createdAt: -1 }); +activitySchema.index({ companyId: 1, read: 1, createdAt: -1 }); + +module.exports = mongoose.model('Activity', activitySchema); + diff --git a/server/routers/procurement/routes/activity.js b/server/routers/procurement/routes/activity.js new file mode 100644 index 0000000..207ecc1 --- /dev/null +++ b/server/routers/procurement/routes/activity.js @@ -0,0 +1,101 @@ +const express = require('express'); +const router = express.Router(); +const { verifyToken } = require('../middleware/auth'); +const Activity = require('../models/Activity'); +const User = require('../models/User'); + +// Получить последние активности компании +router.get('/', verifyToken, async (req, res) => { + try { + const userId = req.userId; + const user = await User.findById(userId); + + if (!user || !user.companyId) { + return res.json({ activities: [] }); + } + + const companyId = user.companyId.toString(); + const limit = parseInt(req.query.limit) || 10; + + const activities = await Activity.find({ companyId }) + .sort({ createdAt: -1 }) + .limit(limit) + .lean(); + + res.json({ activities }); + } catch (error) { + console.error('Error getting activities:', error); + res.status(500).json({ error: error.message }); + } +}); + +// Отметить активность как прочитанную +router.patch('/:id/read', verifyToken, async (req, res) => { + try { + const userId = req.userId; + const user = await User.findById(userId); + + if (!user || !user.companyId) { + return res.status(403).json({ error: 'Access denied' }); + } + + const companyId = user.companyId.toString(); + const activityId = req.params.id; + + const activity = await Activity.findOne({ + _id: activityId, + companyId + }); + + if (!activity) { + return res.status(404).json({ error: 'Activity not found' }); + } + + activity.read = true; + await activity.save(); + + res.json({ success: true, activity }); + } catch (error) { + console.error('Error updating activity:', error); + res.status(500).json({ error: error.message }); + } +}); + +// Отметить все активности как прочитанные +router.post('/mark-all-read', verifyToken, async (req, res) => { + try { + const userId = req.userId; + const user = await User.findById(userId); + + if (!user || !user.companyId) { + return res.status(403).json({ error: 'Access denied' }); + } + + const companyId = user.companyId.toString(); + + await Activity.updateMany( + { companyId, read: false }, + { $set: { read: true } } + ); + + res.json({ success: true }); + } catch (error) { + console.error('Error marking all as read:', error); + res.status(500).json({ error: error.message }); + } +}); + +// Создать активность (вспомогательная функция) +router.createActivity = async (data) => { + try { + const activity = new Activity(data); + await activity.save(); + return activity; + } catch (error) { + console.error('Error creating activity:', error); + throw error; + } +}; + +module.exports = router; + diff --git a/server/routers/procurement/scripts/seed-activities.js b/server/routers/procurement/scripts/seed-activities.js new file mode 100644 index 0000000..9490f28 --- /dev/null +++ b/server/routers/procurement/scripts/seed-activities.js @@ -0,0 +1,122 @@ +const mongoose = require('mongoose'); +require('dotenv').config(); + +// Подключение моделей - прямые пути без path.join и __dirname +const Activity = require('../models/Activity'); +const User = require('../models/User'); +const Company = require('../models/Company'); + +const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017/procurement-platform'; + +const activityTemplates = [ + { + type: 'request_received', + title: 'Получен новый запрос', + description: 'Компания отправила вам запрос на поставку товаров', + }, + { + type: 'request_sent', + title: 'Запрос отправлен', + description: 'Ваш запрос был отправлен компании', + }, + { + type: 'request_response', + title: 'Получен ответ на запрос', + description: 'Компания ответила на ваш запрос', + }, + { + type: 'product_accepted', + title: 'Товар акцептован', + description: 'Ваш товар был акцептован компанией', + }, + { + type: 'message_received', + title: 'Новое сообщение', + description: 'Вы получили новое сообщение от компании', + }, + { + type: 'review_received', + title: 'Новый отзыв', + description: 'Компания оставила отзыв о сотрудничестве', + }, + { + type: 'profile_updated', + title: 'Профиль обновлен', + description: 'Информация о вашей компании была обновлена', + }, + { + type: 'buy_product_added', + title: 'Добавлен товар для закупки', + description: 'В раздел "Я покупаю" добавлен новый товар', + }, +]; + +async function seedActivities() { + try { + console.log('🌱 Connecting to MongoDB...'); + await mongoose.connect(MONGODB_URI); + console.log('✅ Connected to MongoDB'); + + // Найти тестового пользователя + const testUser = await User.findOne({ email: 'admin@test-company.ru' }); + if (!testUser) { + console.log('❌ Test user not found. Please run recreate-test-user.js first.'); + process.exit(1); + } + + const company = await Company.findById(testUser.companyId); + if (!company) { + console.log('❌ Company not found'); + process.exit(1); + } + + // Найти другие компании для связанных активностей + const otherCompanies = await Company.find({ + _id: { $ne: company._id } + }).limit(3); + + console.log('🗑️ Clearing existing activities...'); + await Activity.deleteMany({ companyId: company._id.toString() }); + + console.log('➕ Creating activities...'); + const activities = []; + + for (let i = 0; i < 8; i++) { + const template = activityTemplates[i % activityTemplates.length]; + const relatedCompany = otherCompanies[i % otherCompanies.length]; + + const activity = { + companyId: company._id.toString(), + userId: testUser._id.toString(), + type: template.type, + title: template.title, + description: template.description, + relatedCompanyId: relatedCompany?._id.toString(), + relatedCompanyName: relatedCompany?.shortName || relatedCompany?.fullName, + read: i >= 5, // Первые 5 непрочитанные + createdAt: new Date(Date.now() - i * 3600000), // Каждый час назад + }; + + activities.push(activity); + } + + await Activity.insertMany(activities); + + console.log(`✅ Created ${activities.length} activities`); + console.log('✨ Activities seeded successfully!'); + + await mongoose.connection.close(); + console.log('👋 Database connection closed'); + } catch (error) { + console.error('❌ Error seeding activities:', error); + process.exit(1); + } +} + +// Запуск +if (require.main === module) { + seedActivities(); +} + +module.exports = { seedActivities }; + diff --git a/server/routers/procurement/scripts/seed-requests.js b/server/routers/procurement/scripts/seed-requests.js new file mode 100644 index 0000000..f435260 --- /dev/null +++ b/server/routers/procurement/scripts/seed-requests.js @@ -0,0 +1,114 @@ +const mongoose = require('mongoose'); +const Request = require('../models/Request'); +const Company = require('../models/Company'); +const User = require('../models/User'); + +const mongoUri = process.env.MONGODB_URI || 'mongodb://admin:password@localhost:27017/procurement_db?authSource=admin'; + +async function seedRequests() { + try { + await mongoose.connect(mongoUri); + console.log('✅ Connected to MongoDB'); + + // Получаем все компании + const companies = await Company.find().limit(10).exec(); + if (companies.length < 2) { + console.error('❌ Need at least 2 companies in database'); + process.exit(1); + } + + // Получаем тестового пользователя + const testUser = await User.findOne({ email: 'admin@test-company.ru' }).exec(); + if (!testUser) { + console.error('❌ Test user not found'); + process.exit(1); + } + + const testCompanyId = testUser.companyId.toString(); + console.log('📋 Test company ID:', testCompanyId); + console.log('📋 Found', companies.length, 'companies'); + + // Удаляем старые запросы + await Request.deleteMany({}); + console.log('🗑️ Cleared old requests'); + + const requests = []; + const now = new Date(); + + // Создаем отправленные запросы (от тестовой компании) + for (let i = 0; i < 5; i++) { + const recipientCompany = companies[i % companies.length]; + if (recipientCompany._id.toString() === testCompanyId) { + continue; + } + + const createdAt = new Date(now.getTime() - i * 24 * 60 * 60 * 1000); // За последние 5 дней + + requests.push({ + senderCompanyId: testCompanyId, + recipientCompanyId: recipientCompany._id.toString(), + subject: `Запрос на поставку ${i + 1}`, + text: `Здравствуйте! Интересует поставка товаров/услуг. Запрос ${i + 1}. Прошу предоставить коммерческое предложение.`, + files: [], + responseFiles: [], + status: i % 3 === 0 ? 'accepted' : i % 3 === 1 ? 'rejected' : 'pending', + response: i % 3 === 0 + ? 'Благодарим за запрос! Готовы предоставить услуги. Отправили КП на почту.' + : i % 3 === 1 + ? 'К сожалению, в данный момент не можем предоставить эти услуги.' + : null, + respondedAt: i % 3 !== 2 ? new Date(createdAt.getTime() + 2 * 60 * 60 * 1000) : null, + createdAt, + updatedAt: i % 3 !== 2 ? new Date(createdAt.getTime() + 2 * 60 * 60 * 1000) : createdAt, + }); + } + + // Создаем полученные запросы (к тестовой компании) + for (let i = 0; i < 3; i++) { + const senderCompany = companies[(i + 2) % companies.length]; + if (senderCompany._id.toString() === testCompanyId) { + continue; + } + + const createdAt = new Date(now.getTime() - (i + 1) * 12 * 60 * 60 * 1000); // За последние 1.5 дня + + requests.push({ + senderCompanyId: senderCompany._id.toString(), + recipientCompanyId: testCompanyId, + subject: `Предложение о сотрудничестве ${i + 1}`, + text: `Добрый день! Предлагаем сотрудничество. Запрос ${i + 1}. Заинтересованы в вашей продукции.`, + files: [], + responseFiles: [], + status: 'pending', + response: null, + respondedAt: null, + createdAt, + updatedAt: createdAt, + }); + } + + // Сохраняем все запросы + const savedRequests = await Request.insertMany(requests); + console.log('✅ Created', savedRequests.length, 'test requests'); + + // Статистика + const sentCount = await Request.countDocuments({ senderCompanyId: testCompanyId }); + const receivedCount = await Request.countDocuments({ recipientCompanyId: testCompanyId }); + const withResponses = await Request.countDocuments({ senderCompanyId: testCompanyId, response: { $ne: null } }); + + console.log('📊 Statistics:'); + console.log(' - Sent requests:', sentCount); + console.log(' - Received requests:', receivedCount); + console.log(' - With responses:', withResponses); + + } catch (error) { + console.error('❌ Error:', error); + process.exit(1); + } finally { + await mongoose.connection.close(); + console.log('👋 Disconnected from MongoDB'); + } +} + +seedRequests(); + diff --git a/server/utils/mongoose.ts b/server/utils/mongoose.ts index f797605..34278ef 100644 --- a/server/utils/mongoose.ts +++ b/server/utils/mongoose.ts @@ -9,3 +9,8 @@ mongoose.connect(mongoUrl).then(() => { console.error(err) }) +export default mongoose + +// Для совместимости с CommonJS +module.exports = mongoose +module.exports.default = mongoose