From 71f3f353ab9d7faca6d44037533e374946907fa0 Mon Sep 17 00:00:00 2001 From: innoavvlasov Date: Tue, 4 Nov 2025 18:20:19 +0300 Subject: [PATCH] update project --- server/routers/procurement/models/Company.js | 6 + server/routers/procurement/routes/auth.js | 43 +++ .../routers/procurement/routes/companies.js | 43 ++- server/routers/procurement/routes/reviews.js | 75 ++++- .../procurement/scripts/recreate-test-user.js | 262 ++++++++++++++++-- 5 files changed, 386 insertions(+), 43 deletions(-) diff --git a/server/routers/procurement/models/Company.js b/server/routers/procurement/models/Company.js index dc34466..f9010b3 100644 --- a/server/routers/procurement/models/Company.js +++ b/server/routers/procurement/models/Company.js @@ -49,6 +49,12 @@ const companySchema = new mongoose.Schema({ type: Boolean, default: false }, + metrics: { + type: { + profileViews: { type: Number, default: 0 } + }, + default: {} + }, createdAt: { type: Date, default: Date.now diff --git a/server/routers/procurement/routes/auth.js b/server/routers/procurement/routes/auth.js index c9a2bba..4f64182 100644 --- a/server/routers/procurement/routes/auth.js +++ b/server/routers/procurement/routes/auth.js @@ -463,6 +463,49 @@ router.patch('/profile', verifyToken, async (req, res) => { return res.status(result.status).json(result.body); } + if (action === 'updateProfile') { + await waitForDatabaseConnection(); + + const { firstName, lastName, position, phone } = payload; + + if (!firstName && !lastName && !position && !phone) { + return res.status(400).json({ error: 'At least one field must be provided' }); + } + + const user = await User.findById(req.userId); + if (!user) { + return res.status(404).json({ error: 'User not found' }); + } + + if (firstName) user.firstName = firstName; + if (lastName) user.lastName = lastName; + if (position !== undefined) user.position = position; + if (phone !== undefined) user.phone = phone; + user.updatedAt = new Date(); + + await user.save(); + + const company = user.companyId ? await Company.findById(user.companyId) : null; + + return res.json({ + message: 'Profile updated successfully', + user: { + id: user._id.toString(), + email: user.email, + firstName: user.firstName, + lastName: user.lastName, + position: user.position, + phone: user.phone, + companyId: user.companyId?.toString() + }, + company: company ? { + id: company._id.toString(), + name: company.fullName, + inn: company.inn + } : null + }); + } + res.json({ message: 'Profile endpoint' }); } catch (error) { console.error('Profile update error:', error); diff --git a/server/routers/procurement/routes/companies.js b/server/routers/procurement/routes/companies.js index 5fd84e9..914cd72 100644 --- a/server/routers/procurement/routes/companies.js +++ b/server/routers/procurement/routes/companies.js @@ -84,13 +84,30 @@ router.get('/my/stats', verifyToken, async (req, res) => { : Promise.resolve(0), ]); + // Подсчитываем просмотры профиля из запросов к профилю компании + const profileViews = company?.metrics?.profileViews || 0; + + // Получаем статистику за последнюю неделю для изменений + const weekAgo = new Date(); + weekAgo.setDate(weekAgo.getDate() - 7); + + const sentRequestsLastWeek = await Request.countDocuments({ + senderCompanyId: companyIdString, + createdAt: { $gte: weekAgo } + }); + + const receivedRequestsLastWeek = await Request.countDocuments({ + recipientCompanyId: companyIdString, + createdAt: { $gte: weekAgo } + }); + const stats = { - profileViews: company?.metrics?.profileViews || 0, - profileViewsChange: 0, + profileViews: profileViews, + profileViewsChange: 0, // Можно добавить отслеживание просмотров, если нужно sentRequests, - sentRequestsChange: 0, + sentRequestsChange: sentRequestsLastWeek, receivedRequests, - receivedRequestsChange: 0, + receivedRequestsChange: receivedRequestsLastWeek, newMessages: unreadMessages, rating: Number.isFinite(company?.rating) ? Number(company.rating) : 0, }; @@ -231,6 +248,24 @@ router.get('/:id', async (req, res) => { }); } + // Отслеживаем просмотр профиля (если это не владелец компании) + const userId = req.userId; + if (userId) { + const User = require('../models/User'); + const user = await User.findById(userId); + if (user && user.companyId && user.companyId.toString() !== company._id.toString()) { + // Инкрементируем просмотры профиля + if (!company.metrics) { + company.metrics = {}; + } + if (!company.metrics.profileViews) { + company.metrics.profileViews = 0; + } + company.metrics.profileViews = (company.metrics.profileViews || 0) + 1; + await company.save(); + } + } + res.json({ ...company.toObject(), id: company._id diff --git a/server/routers/procurement/routes/reviews.js b/server/routers/procurement/routes/reviews.js index 820c9f8..a3740ed 100644 --- a/server/routers/procurement/routes/reviews.js +++ b/server/routers/procurement/routes/reviews.js @@ -2,6 +2,7 @@ const express = require('express'); const router = express.Router(); const { verifyToken } = require('../middleware/auth'); const Review = require('../models/Review'); +const Company = require('../models/Company'); // Функция для логирования с проверкой DEV переменной const log = (message, data = '') => { @@ -14,6 +15,35 @@ const log = (message, data = '') => { } }; +// Функция для пересчета рейтинга компании +const updateCompanyRating = async (companyId) => { + try { + const reviews = await Review.find({ companyId }); + + if (reviews.length === 0) { + await Company.findByIdAndUpdate(companyId, { + rating: 0, + reviews: 0, + updatedAt: new Date() + }); + return; + } + + const totalRating = reviews.reduce((sum, review) => sum + review.rating, 0); + const averageRating = totalRating / reviews.length; + + await Company.findByIdAndUpdate(companyId, { + rating: averageRating, + reviews: reviews.length, + updatedAt: new Date() + }); + + log('[Reviews] Updated company rating:', companyId, 'New rating:', averageRating); + } catch (error) { + console.error('[Reviews] Error updating company rating:', error.message); + } +}; + // GET /reviews/company/:companyId - получить отзывы компании router.get('/company/:companyId', verifyToken, async (req, res) => { try { @@ -42,30 +72,54 @@ router.post('/', verifyToken, async (req, res) => { if (!companyId || !rating || !comment) { return res.status(400).json({ - error: 'companyId, rating, and comment are required', + error: 'Заполните все обязательные поля: компания, рейтинг и комментарий', }); } if (rating < 1 || rating > 5) { return res.status(400).json({ - error: 'Rating must be between 1 and 5', + error: 'Рейтинг должен быть от 1 до 5', }); } - if (comment.trim().length < 10 || comment.trim().length > 1000) { + const trimmedComment = comment.trim(); + if (trimmedComment.length < 10) { return res.status(400).json({ - error: 'Comment must be between 10 and 1000 characters', + error: 'Отзыв должен содержать минимум 10 символов', + }); + } + + if (trimmedComment.length > 1000) { + return res.status(400).json({ + error: 'Отзыв не должен превышать 1000 символов', + }); + } + + // Получить данные пользователя из БД для актуальной информации + const User = require('../models/User'); + const Company = require('../models/Company'); + + const user = await User.findById(req.userId); + const userCompany = user && user.companyId ? await Company.findById(user.companyId) : null; + + if (!user) { + return res.status(404).json({ + error: 'Пользователь не найден', }); } // Создать новый отзыв const newReview = new Review({ companyId, - authorCompanyId: req.companyId, - authorName: req.user.firstName + ' ' + req.user.lastName, - authorCompany: req.user.companyName || 'Company', + authorCompanyId: user.companyId || req.companyId, + authorName: user.firstName && user.lastName + ? `${user.firstName} ${user.lastName}` + : req.user?.firstName && req.user?.lastName + ? `${req.user.firstName} ${req.user.lastName}` + : 'Аноним', + authorCompany: userCompany?.fullName || userCompany?.shortName || req.user?.companyName || 'Компания', rating: parseInt(rating), - comment: comment.trim(), + comment: trimmedComment, verified: true, createdAt: new Date(), updatedAt: new Date() @@ -75,11 +129,14 @@ router.post('/', verifyToken, async (req, res) => { log('[Reviews] New review created:', savedReview._id); + // Пересчитываем рейтинг компании + await updateCompanyRating(companyId); + res.status(201).json(savedReview); } catch (error) { console.error('[Reviews] Error creating review:', error.message); res.status(500).json({ - error: 'Internal server error', + error: 'Ошибка при сохранении отзыва', message: error.message, }); } diff --git a/server/routers/procurement/scripts/recreate-test-user.js b/server/routers/procurement/scripts/recreate-test-user.js index 40694c9..57a287f 100644 --- a/server/routers/procurement/scripts/recreate-test-user.js +++ b/server/routers/procurement/scripts/recreate-test-user.js @@ -5,29 +5,31 @@ require('dotenv').config(); // Импорт моделей const User = require(path.join(__dirname, '..', 'models', 'User')); const Company = require(path.join(__dirname, '..', 'models', 'Company')); +const Request = require(path.join(__dirname, '..', 'models', 'Request')); const primaryUri = process.env.MONGODB_URI || 'mongodb://localhost:27017/procurement_db'; const fallbackUri = process.env.MONGODB_AUTH_URI || 'mongodb://admin:password@localhost:27017/procurement_db?authSource=admin'; const connectWithFallback = async () => { + // Сначала пробуем FALLBACK (с аутентификацией) try { - console.log('\n📡 Подключение к MongoDB (PRIMARY)...'); - await mongoose.connect(primaryUri, { useNewUrlParser: true, useUnifiedTopology: true }); - console.log('✅ Подключено к PRIMARY MongoDB'); - } catch (primaryError) { - console.error('❌ Ошибка PRIMARY подключения:', primaryError.message); - - const requiresFallback = - primaryError.code === 18 || primaryError.code === 13 || String(primaryError.message || '').includes('auth'); - - if (!requiresFallback) { - throw primaryError; - } - - console.log('\n📡 Подключение к MongoDB (FALLBACK)...'); + console.log('\n📡 Подключение к MongoDB (с аутентификацией)...'); await mongoose.connect(fallbackUri, { useNewUrlParser: true, useUnifiedTopology: true }); - console.log('✅ Подключено к FALLBACK MongoDB'); + console.log('✅ Подключено к MongoDB'); + return; + } catch (fallbackError) { + console.log('❌ Ошибка подключения с аутентификацией:', fallbackError.message); + } + + // Если не получилось, пробуем без аутентификации + try { + console.log('\n📡 Подключение к MongoDB (без аутентификации)...'); + await mongoose.connect(primaryUri, { useNewUrlParser: true, useUnifiedTopology: true }); + console.log('✅ Подключено к MongoDB'); + } catch (primaryError) { + console.error('❌ Не удалось подключиться к MongoDB:', primaryError.message); + throw primaryError; } }; @@ -58,17 +60,26 @@ const recreateTestUser = async () => { const company = await Company.create({ _id: presetCompanyId, fullName: 'ООО "Тестовая Компания"', + shortName: 'Тестовая Компания', inn: '1234567890', ogrn: '1234567890123', legalForm: 'ООО', industry: 'IT', - companySize: '50-100', + companySize: '51-250', website: 'https://test-company.ru', + phone: '+7 (999) 123-45-67', + email: 'info@test-company.ru', description: 'Тестовая компания для разработки', - address: 'г. Москва, ул. Тестовая, д. 1', + legalAddress: 'г. Москва, ул. Тестовая, д. 1', + actualAddress: 'г. Москва, ул. Тестовая, д. 1', + foundedYear: 2015, + employeeCount: '51-250', + revenue: 'До 120 млн ₽', rating: 4.5, - reviewsCount: 10, - dealsCount: 25, + reviews: 10, + verified: true, + partnerGeography: ['moscow', 'russia_all'], + slogan: 'Ваш надежный партнер в IT', }); console.log(' ✓ Компания создана:', company.fullName); @@ -99,19 +110,210 @@ const recreateTestUser = async () => { console.log(' Пароль: SecurePass123!'); console.log(''); - // Обновить существующие mock компании - console.log('\n🔄 Обновление существующих mock компаний...'); - const updates = [ - { inn: '7707083894', updates: { companySize: '51-250', partnerGeography: ['moscow', 'russia_all'] } }, - { inn: '7707083895', updates: { companySize: '500+', partnerGeography: ['moscow', 'russia_all'] } }, - { inn: '7707083896', updates: { companySize: '11-50', partnerGeography: ['moscow', 'russia_all'] } }, - { inn: '7707083897', updates: { companySize: '51-250', partnerGeography: ['moscow', 'russia_all'] } }, - { inn: '7707083898', updates: { companySize: '251-500', partnerGeography: ['moscow', 'russia_all'] } }, + // Создать дополнительные тестовые компании для поиска + console.log('\n🏢 Создание дополнительных тестовых компаний...'); + const testCompanies = [ + { + fullName: 'ООО "ТехноСтрой"', + shortName: 'ТехноСтрой', + inn: '7707083894', + ogrn: '1077707083894', + legalForm: 'ООО', + industry: 'Строительство', + companySize: '51-250', + website: 'https://technostroy.ru', + phone: '+7 (495) 111-22-33', + email: 'info@technostroy.ru', + description: 'Строительство промышленных объектов', + foundedYear: 2010, + employeeCount: '51-250', + revenue: 'До 2 млрд ₽', + rating: 4.2, + reviews: 15, + verified: true, + partnerGeography: ['moscow', 'russia_all'], + slogan: 'Строим будущее вместе', + }, + { + fullName: 'АО "ФинансГрупп"', + shortName: 'ФинансГрупп', + inn: '7707083895', + ogrn: '1077707083895', + legalForm: 'АО', + industry: 'Финансы', + companySize: '500+', + website: 'https://finansgrupp.ru', + phone: '+7 (495) 222-33-44', + email: 'contact@finansgrupp.ru', + description: 'Финансовые услуги для бизнеса', + foundedYear: 2005, + employeeCount: '500+', + revenue: 'Более 2 млрд ₽', + rating: 4.8, + reviews: 50, + verified: true, + partnerGeography: ['moscow', 'russia_all', 'international'], + slogan: 'Финансовая стабильность', + }, + { + fullName: 'ООО "ИТ Решения"', + shortName: 'ИТ Решения', + inn: '7707083896', + ogrn: '1077707083896', + legalForm: 'ООО', + industry: 'IT', + companySize: '11-50', + website: 'https://it-solutions.ru', + phone: '+7 (495) 333-44-55', + email: 'hello@it-solutions.ru', + description: 'Разработка программного обеспечения', + foundedYear: 2018, + employeeCount: '11-50', + revenue: 'До 60 млн ₽', + rating: 4.5, + reviews: 8, + verified: true, + partnerGeography: ['moscow', 'spb', 'russia_all'], + slogan: 'Инновации для вашего бизнеса', + }, + { + fullName: 'ООО "ЛогистикПро"', + shortName: 'ЛогистикПро', + inn: '7707083897', + ogrn: '1077707083897', + legalForm: 'ООО', + industry: 'Логистика', + companySize: '51-250', + website: 'https://logistikpro.ru', + phone: '+7 (495) 444-55-66', + email: 'info@logistikpro.ru', + description: 'Транспортные и логистические услуги', + foundedYear: 2012, + employeeCount: '51-250', + revenue: 'До 120 млн ₽', + rating: 4.3, + reviews: 20, + verified: true, + partnerGeography: ['russia_all', 'cis'], + slogan: 'Доставим в срок', + }, + { + fullName: 'ООО "ПродуктТрейд"', + shortName: 'ПродуктТрейд', + inn: '7707083898', + ogrn: '1077707083898', + legalForm: 'ООО', + industry: 'Оптовая торговля', + companySize: '251-500', + website: 'https://produkttrade.ru', + phone: '+7 (495) 555-66-77', + email: 'sales@produkttrade.ru', + description: 'Оптовая торговля продуктами питания', + foundedYear: 2008, + employeeCount: '251-500', + revenue: 'До 2 млрд ₽', + rating: 4.1, + reviews: 30, + verified: true, + partnerGeography: ['moscow', 'russia_all'], + slogan: 'Качество и надежность', + }, + { + fullName: 'ООО "МедСервис"', + shortName: 'МедСервис', + inn: '7707083899', + ogrn: '1077707083899', + legalForm: 'ООО', + industry: 'Здравоохранение', + companySize: '11-50', + website: 'https://medservice.ru', + phone: '+7 (495) 666-77-88', + email: 'info@medservice.ru', + description: 'Медицинские услуги и оборудование', + foundedYear: 2016, + employeeCount: '11-50', + revenue: 'До 60 млн ₽', + rating: 4.6, + reviews: 12, + verified: true, + partnerGeography: ['moscow', 'central'], + slogan: 'Забота о вашем здоровье', + }, ]; - for (const item of updates) { - await Company.updateOne({ inn: item.inn }, { $set: item.updates }); - console.log(` ✓ Компания обновлена: INN ${item.inn}`); + for (const companyData of testCompanies) { + await Company.updateOne( + { inn: companyData.inn }, + { $set: companyData }, + { upsert: true } + ); + console.log(` ✓ Компания создана/обновлена: ${companyData.shortName}`); + } + + // Создать тестовые запросы + console.log('\n📨 Создание тестовых запросов...'); + await Request.deleteMany({}); + + const companies = await Company.find().limit(10).exec(); + const testCompanyId = company._id.toString(); + 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); + + 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); + + 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, + }); + } + + if (requests.length > 0) { + await Request.insertMany(requests); + console.log(` ✓ Создано ${requests.length} тестовых запросов`); } await mongoose.connection.close();