обновил бэк закупок
This commit is contained in:
103
server/routers/procurement/routes/auth.js
Normal file
103
server/routers/procurement/routes/auth.js
Normal file
@@ -0,0 +1,103 @@
|
||||
const express = require('express')
|
||||
const router = express.Router()
|
||||
const User = require('../models/User')
|
||||
const Company = require('../models/Company')
|
||||
const { generateToken } = require('../middleware/auth')
|
||||
|
||||
// Регистрация
|
||||
router.post('/register', async (req, res) => {
|
||||
try {
|
||||
const { email, password, firstName, lastName, position, phone, fullName, inn, ogrn, legalForm, industry, companySize, website } = req.body;
|
||||
|
||||
// Проверка обязательных полей
|
||||
if (!email || !password || !firstName || !lastName || !fullName) {
|
||||
return res.status(400).json({ error: 'Missing required fields' });
|
||||
}
|
||||
|
||||
// Проверка существования пользователя
|
||||
const existingUser = await User.findOne({ email });
|
||||
if (existingUser) {
|
||||
return res.status(409).json({ error: 'User already exists' });
|
||||
}
|
||||
|
||||
// Создать компанию
|
||||
const company = await Company.create({
|
||||
fullName,
|
||||
inn,
|
||||
ogrn,
|
||||
legalForm,
|
||||
industry,
|
||||
companySize,
|
||||
website
|
||||
});
|
||||
|
||||
// Создать пользователя
|
||||
const user = await User.create({
|
||||
email,
|
||||
password,
|
||||
firstName,
|
||||
lastName,
|
||||
position,
|
||||
phone,
|
||||
companyId: company._id
|
||||
});
|
||||
|
||||
// Генерировать токен
|
||||
const token = generateToken(user._id, user.email);
|
||||
|
||||
res.status(201).json({
|
||||
tokens: {
|
||||
accessToken: token,
|
||||
refreshToken: token
|
||||
},
|
||||
user: user.toJSON(),
|
||||
company: company.toObject()
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Registration error:', error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Логин
|
||||
router.post('/login', async (req, res) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
if (!email || !password) {
|
||||
return res.status(400).json({ error: 'Email and password required' });
|
||||
}
|
||||
|
||||
// Найти пользователя
|
||||
const user = await User.findOne({ email });
|
||||
if (!user) {
|
||||
return res.status(401).json({ error: 'Invalid credentials' });
|
||||
}
|
||||
|
||||
// Проверить пароль
|
||||
const isValid = await user.comparePassword(password);
|
||||
if (!isValid) {
|
||||
return res.status(401).json({ error: 'Invalid credentials' });
|
||||
}
|
||||
|
||||
// Загрузить компанию
|
||||
const company = await Company.findById(user.companyId);
|
||||
|
||||
// Генерировать токен
|
||||
const token = generateToken(user._id, user.email);
|
||||
|
||||
res.json({
|
||||
tokens: {
|
||||
accessToken: token,
|
||||
refreshToken: token
|
||||
},
|
||||
user: user.toJSON(),
|
||||
company: company?.toObject() || null
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router
|
||||
186
server/routers/procurement/routes/buy.js
Normal file
186
server/routers/procurement/routes/buy.js
Normal file
@@ -0,0 +1,186 @@
|
||||
const express = require('express')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const router = express.Router()
|
||||
|
||||
// Create remote-assets/docs directory if it doesn't exist
|
||||
const docsDir = path.resolve('server/remote-assets/docs')
|
||||
if (!fs.existsSync(docsDir)) {
|
||||
fs.mkdirSync(docsDir, { recursive: true })
|
||||
}
|
||||
|
||||
// In-memory store for documents metadata
|
||||
const buyDocs = []
|
||||
|
||||
function generateId() {
|
||||
return `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`
|
||||
}
|
||||
|
||||
// GET /buy/docs?ownerCompanyId=...
|
||||
router.get('/docs', (req, res) => {
|
||||
const { ownerCompanyId } = req.query
|
||||
console.log('[BUY API] GET /docs', { ownerCompanyId, totalDocs: buyDocs.length })
|
||||
let result = buyDocs
|
||||
if (ownerCompanyId) {
|
||||
result = result.filter((d) => d.ownerCompanyId === ownerCompanyId)
|
||||
}
|
||||
result = result.map(doc => ({
|
||||
...doc,
|
||||
url: `/api/buy/docs/${doc.id}/file`
|
||||
}))
|
||||
res.json(result)
|
||||
})
|
||||
|
||||
// POST /buy/docs
|
||||
router.post('/docs', (req, res) => {
|
||||
const { ownerCompanyId, name, type, fileData } = req.body || {}
|
||||
console.log('[BUY API] POST /docs', { ownerCompanyId, name, type })
|
||||
if (!ownerCompanyId || !name || !type) {
|
||||
return res.status(400).json({ error: 'ownerCompanyId, name and type are required' })
|
||||
}
|
||||
|
||||
if (!fileData) {
|
||||
return res.status(400).json({ error: 'fileData is required' })
|
||||
}
|
||||
|
||||
const id = generateId()
|
||||
|
||||
// Save file to disk
|
||||
try {
|
||||
const binaryData = Buffer.from(fileData, 'base64')
|
||||
const filePath = path.join(docsDir, `${id}.${type}`)
|
||||
fs.writeFileSync(filePath, binaryData)
|
||||
console.log(`[BUY API] File saved to ${filePath}, size: ${binaryData.length} bytes`)
|
||||
|
||||
const size = binaryData.length
|
||||
const url = `/api/buy/docs/${id}/file`
|
||||
const doc = {
|
||||
id,
|
||||
ownerCompanyId,
|
||||
name,
|
||||
type,
|
||||
size,
|
||||
url,
|
||||
filePath,
|
||||
acceptedBy: [],
|
||||
createdAt: new Date().toISOString(),
|
||||
}
|
||||
buyDocs.unshift(doc)
|
||||
console.log('[BUY API] Document created:', id)
|
||||
res.status(201).json(doc)
|
||||
} catch (e) {
|
||||
console.error(`[BUY API] Error saving file: ${e.message}`)
|
||||
res.status(500).json({ error: 'Failed to save file' })
|
||||
}
|
||||
})
|
||||
|
||||
router.post('/docs/:id/accept', (req, res) => {
|
||||
const { id } = req.params
|
||||
const { companyId } = req.body || {}
|
||||
console.log('[BUY API] POST /docs/:id/accept', { id, companyId })
|
||||
const doc = buyDocs.find((d) => d.id === id)
|
||||
if (!doc) {
|
||||
console.log('[BUY API] Document not found:', id)
|
||||
return res.status(404).json({ error: 'Document not found' })
|
||||
}
|
||||
if (!companyId) {
|
||||
return res.status(400).json({ error: 'companyId is required' })
|
||||
}
|
||||
if (!doc.acceptedBy.includes(companyId)) {
|
||||
doc.acceptedBy.push(companyId)
|
||||
}
|
||||
res.json({ id: doc.id, acceptedBy: doc.acceptedBy })
|
||||
})
|
||||
|
||||
router.get('/docs/:id/delete', (req, res) => {
|
||||
const { id } = req.params
|
||||
console.log('[BUY API] GET /docs/:id/delete', { id, totalDocs: buyDocs.length })
|
||||
const index = buyDocs.findIndex((d) => d.id === id)
|
||||
if (index === -1) {
|
||||
console.log('[BUY API] Document not found for deletion:', id)
|
||||
return res.status(404).json({ error: 'Document not found' })
|
||||
}
|
||||
const deletedDoc = buyDocs.splice(index, 1)[0]
|
||||
|
||||
// Delete file from disk
|
||||
if (deletedDoc.filePath && fs.existsSync(deletedDoc.filePath)) {
|
||||
try {
|
||||
fs.unlinkSync(deletedDoc.filePath)
|
||||
console.log(`[BUY API] File deleted: ${deletedDoc.filePath}`)
|
||||
} catch (e) {
|
||||
console.error(`[BUY API] Error deleting file: ${e.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
console.log('[BUY API] Document deleted via GET:', id, { remainingDocs: buyDocs.length })
|
||||
res.json({ id: deletedDoc.id, success: true })
|
||||
})
|
||||
|
||||
router.delete('/docs/:id', (req, res) => {
|
||||
const { id } = req.params
|
||||
console.log('[BUY API] DELETE /docs/:id', { id, totalDocs: buyDocs.length })
|
||||
const index = buyDocs.findIndex((d) => d.id === id)
|
||||
if (index === -1) {
|
||||
console.log('[BUY API] Document not found for deletion:', id)
|
||||
return res.status(404).json({ error: 'Document not found' })
|
||||
}
|
||||
const deletedDoc = buyDocs.splice(index, 1)[0]
|
||||
|
||||
// Delete file from disk
|
||||
if (deletedDoc.filePath && fs.existsSync(deletedDoc.filePath)) {
|
||||
try {
|
||||
fs.unlinkSync(deletedDoc.filePath)
|
||||
console.log(`[BUY API] File deleted: ${deletedDoc.filePath}`)
|
||||
} catch (e) {
|
||||
console.error(`[BUY API] Error deleting file: ${e.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
console.log('[BUY API] Document deleted:', id, { remainingDocs: buyDocs.length })
|
||||
res.json({ id: deletedDoc.id, success: true })
|
||||
})
|
||||
|
||||
// GET /buy/docs/:id/file - Serve the file
|
||||
router.get('/docs/:id/file', (req, res) => {
|
||||
const { id } = req.params
|
||||
console.log('[BUY API] GET /docs/:id/file', { id })
|
||||
|
||||
const doc = buyDocs.find(d => d.id === id)
|
||||
if (!doc) {
|
||||
console.log('[BUY API] Document not found:', id)
|
||||
return res.status(404).json({ error: 'Document not found' })
|
||||
}
|
||||
|
||||
const filePath = path.join(docsDir, `${id}.${doc.type}`)
|
||||
if (!fs.existsSync(filePath)) {
|
||||
console.log('[BUY API] File not found on disk:', filePath)
|
||||
return res.status(404).json({ error: 'File not found on disk' })
|
||||
}
|
||||
|
||||
try {
|
||||
const fileBuffer = fs.readFileSync(filePath)
|
||||
|
||||
const mimeTypes = {
|
||||
'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'pdf': 'application/pdf'
|
||||
}
|
||||
|
||||
const mimeType = mimeTypes[doc.type] || 'application/octet-stream'
|
||||
const sanitizedName = doc.name.replace(/[^\w\s\-\.]/g, '_')
|
||||
|
||||
res.setHeader('Content-Type', mimeType)
|
||||
// RFC 5987 encoding: filename for ASCII fallback, filename* for UTF-8 with percent-encoding
|
||||
const encodedFilename = encodeURIComponent(`${doc.name}.${doc.type}`)
|
||||
res.setHeader('Content-Disposition', `attachment; filename="${sanitizedName}.${doc.type}"; filename*=UTF-8''${encodedFilename}`)
|
||||
res.setHeader('Content-Length', fileBuffer.length)
|
||||
|
||||
console.log(`[BUY API] Serving file ${id} from ${filePath} (${fileBuffer.length} bytes)`)
|
||||
res.send(fileBuffer)
|
||||
} catch (e) {
|
||||
console.error(`[BUY API] Error serving file: ${e.message}`)
|
||||
res.status(500).json({ error: 'Error serving file' })
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = router
|
||||
103
server/routers/procurement/routes/companies.js
Normal file
103
server/routers/procurement/routes/companies.js
Normal file
@@ -0,0 +1,103 @@
|
||||
const express = require('express')
|
||||
const router = express.Router()
|
||||
const Company = require('../models/Company')
|
||||
const { verifyToken } = require('../middleware/auth')
|
||||
|
||||
// Получить все компании
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const { page = 1, limit = 10, search = '', industry = '' } = req.query;
|
||||
|
||||
let query = {};
|
||||
|
||||
if (search) {
|
||||
query.$text = { $search: search };
|
||||
}
|
||||
|
||||
if (industry) {
|
||||
query.industry = industry;
|
||||
}
|
||||
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const companies = await Company.find(query)
|
||||
.limit(Number(limit))
|
||||
.skip(Number(skip))
|
||||
.sort({ rating: -1 });
|
||||
|
||||
const total = await Company.countDocuments(query);
|
||||
|
||||
res.json({
|
||||
companies,
|
||||
total,
|
||||
page: Number(page),
|
||||
limit: Number(limit),
|
||||
pages: Math.ceil(total / limit)
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Получить компанию по ID
|
||||
router.get('/:id', async (req, res) => {
|
||||
try {
|
||||
const company = await Company.findById(req.params.id).populate('ownerId', 'firstName lastName email');
|
||||
|
||||
if (!company) {
|
||||
return res.status(404).json({ error: 'Company not found' });
|
||||
}
|
||||
|
||||
res.json(company);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Обновить компанию (требует авторизации)
|
||||
const updateCompanyHandler = async (req, res) => {
|
||||
try {
|
||||
const company = await Company.findByIdAndUpdate(
|
||||
req.params.id,
|
||||
{ ...req.body, updatedAt: new Date() },
|
||||
{ new: true }
|
||||
);
|
||||
|
||||
if (!company) {
|
||||
return res.status(404).json({ error: 'Company not found' });
|
||||
}
|
||||
|
||||
res.json(company);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
router.put('/:id', verifyToken, updateCompanyHandler);
|
||||
router.patch('/:id', verifyToken, updateCompanyHandler);
|
||||
|
||||
// Поиск с AI анализом
|
||||
router.post('/ai-search', async (req, res) => {
|
||||
try {
|
||||
const { query } = req.body;
|
||||
|
||||
if (!query) {
|
||||
return res.status(400).json({ error: 'Query required' });
|
||||
}
|
||||
|
||||
// Простой поиск по текстовым полям
|
||||
const companies = await Company.find({
|
||||
$text: { $search: query }
|
||||
}).limit(10);
|
||||
|
||||
res.json({
|
||||
companies,
|
||||
total: companies.length,
|
||||
aiSuggestion: `Found ${companies.length} companies matching "${query}"`
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router
|
||||
114
server/routers/procurement/routes/experience.js
Normal file
114
server/routers/procurement/routes/experience.js
Normal file
@@ -0,0 +1,114 @@
|
||||
const express = require('express')
|
||||
const router = express.Router()
|
||||
const { verifyToken } = require('../middleware/auth')
|
||||
|
||||
// In-memory хранилище для опыта работы (mock)
|
||||
let experiences = [];
|
||||
|
||||
// GET /experience - Получить список опыта работы компании
|
||||
router.get('/', verifyToken, (req, res) => {
|
||||
try {
|
||||
const { companyId } = req.query;
|
||||
|
||||
if (!companyId) {
|
||||
return res.status(400).json({ error: 'companyId is required' });
|
||||
}
|
||||
|
||||
const companyExperiences = experiences.filter(exp => exp.companyId === companyId);
|
||||
res.json(companyExperiences);
|
||||
} catch (error) {
|
||||
console.error('Get experience error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// POST /experience - Создать запись опыта работы
|
||||
router.post('/', verifyToken, (req, res) => {
|
||||
try {
|
||||
const { companyId, data } = req.body;
|
||||
|
||||
if (!companyId || !data) {
|
||||
return res.status(400).json({ error: 'companyId and data are required' });
|
||||
}
|
||||
|
||||
const { confirmed, customer, subject, volume, contact, comment } = data;
|
||||
|
||||
if (!customer || !subject) {
|
||||
return res.status(400).json({ error: 'customer and subject are required' });
|
||||
}
|
||||
|
||||
const newExperience = {
|
||||
id: `exp-${Date.now()}`,
|
||||
companyId,
|
||||
confirmed: confirmed || false,
|
||||
customer,
|
||||
subject,
|
||||
volume: volume || '',
|
||||
contact: contact || '',
|
||||
comment: comment || '',
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString()
|
||||
};
|
||||
|
||||
experiences.push(newExperience);
|
||||
|
||||
res.status(201).json(newExperience);
|
||||
} catch (error) {
|
||||
console.error('Create experience error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// PUT /experience/:id - Обновить запись опыта работы
|
||||
router.put('/:id', verifyToken, (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { data } = req.body;
|
||||
|
||||
if (!data) {
|
||||
return res.status(400).json({ error: 'data is required' });
|
||||
}
|
||||
|
||||
const index = experiences.findIndex(exp => exp.id === id);
|
||||
|
||||
if (index === -1) {
|
||||
return res.status(404).json({ error: 'Experience not found' });
|
||||
}
|
||||
|
||||
const updatedExperience = {
|
||||
...experiences[index],
|
||||
...data,
|
||||
updatedAt: new Date().toISOString()
|
||||
};
|
||||
|
||||
experiences[index] = updatedExperience;
|
||||
|
||||
res.json(updatedExperience);
|
||||
} catch (error) {
|
||||
console.error('Update experience error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// DELETE /experience/:id - Удалить запись опыта работы
|
||||
router.delete('/:id', verifyToken, (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const index = experiences.findIndex(exp => exp.id === id);
|
||||
|
||||
if (index === -1) {
|
||||
return res.status(404).json({ error: 'Experience not found' });
|
||||
}
|
||||
|
||||
experiences.splice(index, 1);
|
||||
|
||||
res.json({ message: 'Experience deleted successfully' });
|
||||
} catch (error) {
|
||||
console.error('Delete experience error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router
|
||||
|
||||
130
server/routers/procurement/routes/messages.js
Normal file
130
server/routers/procurement/routes/messages.js
Normal file
@@ -0,0 +1,130 @@
|
||||
const express = require('express')
|
||||
const router = express.Router()
|
||||
const Message = require('../models/Message')
|
||||
const { verifyToken } = require('../middleware/auth')
|
||||
|
||||
// Mock данные для тредов
|
||||
const mockThreads = [
|
||||
{
|
||||
id: 'thread-1',
|
||||
lastMessage: 'Добрый день! Интересует поставка металлопроката.',
|
||||
lastMessageAt: new Date(Date.now() - 3600000).toISOString(),
|
||||
participants: ['company-123', 'company-1']
|
||||
},
|
||||
{
|
||||
id: 'thread-2',
|
||||
lastMessage: 'Можем предложить скидку 15% на оптовую партию.',
|
||||
lastMessageAt: new Date(Date.now() - 7200000).toISOString(),
|
||||
participants: ['company-123', 'company-2']
|
||||
},
|
||||
{
|
||||
id: 'thread-3',
|
||||
lastMessage: 'Спасибо за предложение, рассмотрим.',
|
||||
lastMessageAt: new Date(Date.now() - 86400000).toISOString(),
|
||||
participants: ['company-123', 'company-4']
|
||||
}
|
||||
];
|
||||
|
||||
// Mock данные для сообщений
|
||||
const mockMessages = {
|
||||
'thread-1': [
|
||||
{ id: 'msg-1', senderCompanyId: 'company-1', text: 'Добрый день! Интересует поставка металлопроката.', timestamp: new Date(Date.now() - 3600000).toISOString() },
|
||||
{ id: 'msg-2', senderCompanyId: 'company-123', text: 'Здравствуйте! Какой объем вас интересует?', timestamp: new Date(Date.now() - 3500000).toISOString() }
|
||||
],
|
||||
'thread-2': [
|
||||
{ id: 'msg-3', senderCompanyId: 'company-2', text: 'Можем предложить скидку 15% на оптовую партию.', timestamp: new Date(Date.now() - 7200000).toISOString() }
|
||||
],
|
||||
'thread-3': [
|
||||
{ id: 'msg-4', senderCompanyId: 'company-4', text: 'Спасибо за предложение, рассмотрим.', timestamp: new Date(Date.now() - 86400000).toISOString() }
|
||||
]
|
||||
};
|
||||
|
||||
// Получить все потоки для компании
|
||||
router.get('/threads', verifyToken, async (req, res) => {
|
||||
try {
|
||||
// Попытка получить из MongoDB
|
||||
try {
|
||||
const threads = await Message.aggregate([
|
||||
{
|
||||
$match: {
|
||||
$or: [
|
||||
{ senderCompanyId: req.user.companyId },
|
||||
{ recipientCompanyId: req.user.companyId }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
$sort: { timestamp: -1 }
|
||||
},
|
||||
{
|
||||
$group: {
|
||||
_id: '$threadId',
|
||||
lastMessage: { $first: '$text' },
|
||||
lastMessageAt: { $first: '$timestamp' }
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
if (threads && threads.length > 0) {
|
||||
return res.json(threads);
|
||||
}
|
||||
} catch (dbError) {
|
||||
console.log('MongoDB unavailable, using mock data');
|
||||
}
|
||||
|
||||
// Fallback на mock данные
|
||||
res.json(mockThreads);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Получить сообщения потока
|
||||
router.get('/threads/:threadId', verifyToken, async (req, res) => {
|
||||
try {
|
||||
// Попытка получить из MongoDB
|
||||
try {
|
||||
const messages = await Message.find({ threadId: req.params.threadId })
|
||||
.sort({ timestamp: 1 })
|
||||
.populate('senderCompanyId', 'shortName fullName')
|
||||
.populate('recipientCompanyId', 'shortName fullName');
|
||||
|
||||
if (messages && messages.length > 0) {
|
||||
return res.json(messages);
|
||||
}
|
||||
} catch (dbError) {
|
||||
console.log('MongoDB unavailable, using mock data');
|
||||
}
|
||||
|
||||
// Fallback на mock данные
|
||||
const threadMessages = mockMessages[req.params.threadId] || [];
|
||||
res.json(threadMessages);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Отправить сообщение
|
||||
router.post('/', verifyToken, async (req, res) => {
|
||||
try {
|
||||
const { threadId, text, recipientCompanyId } = req.body;
|
||||
|
||||
if (!text || !threadId) {
|
||||
return res.status(400).json({ error: 'Text and threadId required' });
|
||||
}
|
||||
|
||||
const message = await Message.create({
|
||||
threadId,
|
||||
senderCompanyId: req.user.companyId,
|
||||
recipientCompanyId,
|
||||
text,
|
||||
timestamp: new Date()
|
||||
});
|
||||
|
||||
res.status(201).json(message);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router
|
||||
130
server/routers/procurement/routes/products.js
Normal file
130
server/routers/procurement/routes/products.js
Normal file
@@ -0,0 +1,130 @@
|
||||
const express = require('express')
|
||||
const router = express.Router()
|
||||
const { verifyToken } = require('../middleware/auth')
|
||||
|
||||
// In-memory хранилище для продуктов/услуг (mock)
|
||||
let products = [];
|
||||
|
||||
// GET /products - Получить список продуктов/услуг компании
|
||||
router.get('/', verifyToken, (req, res) => {
|
||||
try {
|
||||
const { companyId } = req.query;
|
||||
|
||||
if (!companyId) {
|
||||
return res.status(400).json({ error: 'companyId is required' });
|
||||
}
|
||||
|
||||
const companyProducts = products.filter(p => p.companyId === companyId);
|
||||
res.json(companyProducts);
|
||||
} catch (error) {
|
||||
console.error('Get products error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// POST /products - Создать продукт/услугу
|
||||
router.post('/', verifyToken, (req, res) => {
|
||||
try {
|
||||
const { companyId, name, category, description, price, unit } = req.body;
|
||||
|
||||
if (!companyId || !name) {
|
||||
return res.status(400).json({ error: 'companyId and name are required' });
|
||||
}
|
||||
|
||||
const newProduct = {
|
||||
id: `prod-${Date.now()}`,
|
||||
companyId,
|
||||
name,
|
||||
category: category || 'other',
|
||||
description: description || '',
|
||||
price: price || '',
|
||||
unit: unit || '',
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString()
|
||||
};
|
||||
|
||||
products.push(newProduct);
|
||||
|
||||
res.status(201).json(newProduct);
|
||||
} catch (error) {
|
||||
console.error('Create product error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// PUT /products/:id - Обновить продукт/услугу
|
||||
router.put('/:id', verifyToken, (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const updates = req.body;
|
||||
|
||||
const index = products.findIndex(p => p.id === id);
|
||||
|
||||
if (index === -1) {
|
||||
return res.status(404).json({ error: 'Product not found' });
|
||||
}
|
||||
|
||||
const updatedProduct = {
|
||||
...products[index],
|
||||
...updates,
|
||||
updatedAt: new Date().toISOString()
|
||||
};
|
||||
|
||||
products[index] = updatedProduct;
|
||||
|
||||
res.json(updatedProduct);
|
||||
} catch (error) {
|
||||
console.error('Update product error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// PATCH /products/:id - Частичное обновление продукта/услуги
|
||||
router.patch('/:id', verifyToken, (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const updates = req.body;
|
||||
|
||||
const index = products.findIndex(p => p.id === id);
|
||||
|
||||
if (index === -1) {
|
||||
return res.status(404).json({ error: 'Product not found' });
|
||||
}
|
||||
|
||||
const updatedProduct = {
|
||||
...products[index],
|
||||
...updates,
|
||||
updatedAt: new Date().toISOString()
|
||||
};
|
||||
|
||||
products[index] = updatedProduct;
|
||||
|
||||
res.json(updatedProduct);
|
||||
} catch (error) {
|
||||
console.error('Patch product error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// DELETE /products/:id - Удалить продукт/услугу
|
||||
router.delete('/:id', verifyToken, (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const index = products.findIndex(p => p.id === id);
|
||||
|
||||
if (index === -1) {
|
||||
return res.status(404).json({ error: 'Product not found' });
|
||||
}
|
||||
|
||||
products.splice(index, 1);
|
||||
|
||||
res.json({ message: 'Product deleted successfully' });
|
||||
} catch (error) {
|
||||
console.error('Delete product error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router
|
||||
|
||||
99
server/routers/procurement/routes/search.js
Normal file
99
server/routers/procurement/routes/search.js
Normal file
@@ -0,0 +1,99 @@
|
||||
const express = require('express')
|
||||
const router = express.Router()
|
||||
const { verifyToken } = require('../middleware/auth')
|
||||
const mockCompaniesData = require('../mocks/companies.json')
|
||||
const mockCompanies = mockCompaniesData.mockCompanies
|
||||
|
||||
// Маппинг отраслей для фильтрации
|
||||
const industryMapping = {
|
||||
'it': 'IT',
|
||||
'finance': 'Финансы',
|
||||
'manufacturing': 'Производство',
|
||||
'construction': 'Строительство',
|
||||
'retail': 'Торговля',
|
||||
'wholesale': 'Торговля',
|
||||
'logistics': 'Логистика',
|
||||
'healthcare': 'Медицина'
|
||||
};
|
||||
|
||||
// GET /search/companies - Поиск компаний
|
||||
router.get('/companies', verifyToken, (req, res) => {
|
||||
try {
|
||||
const {
|
||||
query = '',
|
||||
industries = '',
|
||||
companySizes = '',
|
||||
geographies = '',
|
||||
minRating = 0,
|
||||
hasReviews,
|
||||
hasAccepts
|
||||
} = req.query;
|
||||
|
||||
let result = [...mockCompanies];
|
||||
|
||||
// Фильтр по текстовому запросу
|
||||
if (query.trim()) {
|
||||
const q = query.toLowerCase();
|
||||
result = result.filter(c =>
|
||||
c.fullName.toLowerCase().includes(q) ||
|
||||
c.shortName.toLowerCase().includes(q) ||
|
||||
c.slogan.toLowerCase().includes(q) ||
|
||||
c.industry.toLowerCase().includes(q)
|
||||
);
|
||||
}
|
||||
|
||||
// Фильтр по отраслям
|
||||
if (industries) {
|
||||
const selectedIndustries = industries.split(',');
|
||||
result = result.filter(c => {
|
||||
const mappedIndustries = selectedIndustries.map(i => industryMapping[i] || i);
|
||||
return mappedIndustries.some(ind =>
|
||||
c.industry.toLowerCase().includes(ind.toLowerCase())
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Фильтр по размеру компании
|
||||
if (companySizes) {
|
||||
const sizes = companySizes.split(',');
|
||||
result = result.filter(c => {
|
||||
if (!c.companySize) return false;
|
||||
return sizes.some(size => {
|
||||
if (size === '1-10') return c.companySize === '1-10';
|
||||
if (size === '11-50') return c.companySize === '10-50';
|
||||
if (size === '51-250') return c.companySize.includes('50') || c.companySize.includes('100') || c.companySize.includes('250');
|
||||
if (size === '251-500') return c.companySize.includes('250') || c.companySize.includes('500');
|
||||
if (size === '500+') return c.companySize === '500+';
|
||||
return false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Фильтр по рейтингу
|
||||
const rating = parseFloat(minRating);
|
||||
if (rating > 0) {
|
||||
result = result.filter(c => c.rating >= rating);
|
||||
}
|
||||
|
||||
// Фильтр по наличию отзывов
|
||||
if (hasReviews === 'true') {
|
||||
result = result.filter(c => c.verified === true);
|
||||
}
|
||||
|
||||
// Фильтр по акцептам документов
|
||||
if (hasAccepts === 'true') {
|
||||
result = result.filter(c => c.verified === true);
|
||||
}
|
||||
|
||||
res.json({
|
||||
companies: result,
|
||||
total: result.length
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Search error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router
|
||||
|
||||
Reference in New Issue
Block a user