- Portfolio CRUD: добавление, редактирование, удаление, переключение публикации - Services CRUD: полное управление услугами с возможностью активации/деактивации - Banner system: новая модель Banner с CRUD операциями и аналитикой кликов - Telegram integration: расширенные настройки бота, обнаружение чатов, отправка сообщений - Media management: улучшенная загрузка файлов с оптимизацией изображений и превью - UI improvements: обновлённые админ-панели с rich-text редактором и drag&drop загрузкой - Database: добавлена таблица banners с полями для баннеров и аналитики
316 lines
9.1 KiB
JavaScript
316 lines
9.1 KiB
JavaScript
const express = require('express');
|
|
const router = express.Router();
|
|
const { Service } = require('../models');
|
|
|
|
// Get all services for calculator
|
|
router.get('/services', async (req, res) => {
|
|
try {
|
|
const services = await Service.findAll({
|
|
where: { isActive: true },
|
|
attributes: ['id', 'name', 'pricing', 'category', 'features', 'estimatedTime'],
|
|
order: [['category', 'ASC'], ['name', 'ASC']]
|
|
});
|
|
|
|
const servicesByCategory = services.reduce((acc, service) => {
|
|
if (!acc[service.category]) {
|
|
acc[service.category] = [];
|
|
}
|
|
acc[service.category].push(service);
|
|
return acc;
|
|
}, {});
|
|
|
|
res.json({
|
|
success: true,
|
|
services: servicesByCategory,
|
|
allServices: services
|
|
});
|
|
} catch (error) {
|
|
console.error('Calculator services error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Error fetching services'
|
|
});
|
|
}
|
|
});
|
|
|
|
// Calculate project estimate
|
|
router.post('/calculate', async (req, res) => {
|
|
try {
|
|
const {
|
|
selectedServices = [],
|
|
projectComplexity = 'medium',
|
|
timeline = 'standard',
|
|
additionalFeatures = [],
|
|
customRequirements = ''
|
|
} = req.body;
|
|
|
|
if (!selectedServices.length) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Please select at least one service'
|
|
});
|
|
}
|
|
|
|
// Get selected services details
|
|
const services = await Service.findAll({
|
|
where: {
|
|
id: selectedServices,
|
|
isActive: true
|
|
}
|
|
});
|
|
|
|
if (services.length !== selectedServices.length) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Some selected services are not available'
|
|
});
|
|
}
|
|
|
|
// Calculate base cost
|
|
let baseCost = 0;
|
|
let totalTime = 0; // in days
|
|
|
|
services.forEach(service => {
|
|
baseCost += service.pricing.basePrice;
|
|
totalTime += (service.estimatedTime.min + service.estimatedTime.max) / 2;
|
|
});
|
|
|
|
// Apply complexity multiplier
|
|
const complexityMultipliers = {
|
|
'simple': 0.7,
|
|
'medium': 1.0,
|
|
'complex': 1.5,
|
|
'enterprise': 2.0
|
|
};
|
|
|
|
const complexityMultiplier = complexityMultipliers[projectComplexity] || 1.0;
|
|
|
|
// Apply timeline multiplier
|
|
const timelineMultipliers = {
|
|
'rush': 1.8, // Less than 2 weeks
|
|
'fast': 1.4, // 2-4 weeks
|
|
'standard': 1.0, // 1-3 months
|
|
'flexible': 0.8 // 3+ months
|
|
};
|
|
|
|
const timelineMultiplier = timelineMultipliers[timeline] || 1.0;
|
|
|
|
// Additional features cost
|
|
const featureCosts = {
|
|
'seo-optimization': 300000,
|
|
'analytics-setup': 150000,
|
|
'social-integration': 200000,
|
|
'payment-gateway': 500000,
|
|
'multilingual': 400000,
|
|
'admin-panel': 600000,
|
|
'api-integration': 350000,
|
|
'mobile-responsive': 250000,
|
|
'ssl-certificate': 100000,
|
|
'backup-system': 200000
|
|
};
|
|
|
|
let additionalCost = 0;
|
|
additionalFeatures.forEach(feature => {
|
|
additionalCost += featureCosts[feature] || 0;
|
|
});
|
|
|
|
// Custom requirements cost (estimated based on description length and complexity)
|
|
let customCost = 0;
|
|
if (customRequirements && customRequirements.length > 50) {
|
|
customCost = Math.min(customRequirements.length * 1000, 1000000); // Max 1M KRW
|
|
}
|
|
|
|
// Calculate final estimate
|
|
const subtotal = baseCost * complexityMultiplier * timelineMultiplier;
|
|
const total = subtotal + additionalCost + customCost;
|
|
|
|
// Calculate time estimate
|
|
const timeMultiplier = complexityMultiplier * timelineMultiplier;
|
|
const estimatedDays = Math.ceil(totalTime * timeMultiplier);
|
|
const estimatedWeeks = Math.ceil(estimatedDays / 7);
|
|
|
|
// Create price ranges (±20%)
|
|
const minPrice = Math.round(total * 0.8);
|
|
const maxPrice = Math.round(total * 1.2);
|
|
|
|
// Breakdown
|
|
const breakdown = {
|
|
baseServices: Math.round(baseCost),
|
|
complexityAdjustment: Math.round(baseCost * (complexityMultiplier - 1)),
|
|
timelineAdjustment: Math.round(baseCost * complexityMultiplier * (timelineMultiplier - 1)),
|
|
additionalFeatures: additionalCost,
|
|
customRequirements: customCost,
|
|
subtotal: Math.round(subtotal),
|
|
total: Math.round(total)
|
|
};
|
|
|
|
const result = {
|
|
success: true,
|
|
estimate: {
|
|
total: Math.round(total),
|
|
range: {
|
|
min: minPrice,
|
|
max: maxPrice,
|
|
formatted: `₩${minPrice.toLocaleString()} - ₩${maxPrice.toLocaleString()}`
|
|
},
|
|
breakdown,
|
|
timeline: {
|
|
days: estimatedDays,
|
|
weeks: estimatedWeeks,
|
|
formatted: estimatedWeeks === 1 ? '1 week' : `${estimatedWeeks} weeks`
|
|
},
|
|
currency: 'KRW',
|
|
confidence: calculateConfidence(services.length, projectComplexity, customRequirements),
|
|
recommendations: generateRecommendations(projectComplexity, timeline, services)
|
|
},
|
|
selectedServices: services.map(s => ({
|
|
id: s._id,
|
|
name: s.name,
|
|
category: s.category,
|
|
basePrice: s.pricing.basePrice
|
|
})),
|
|
calculatedAt: new Date()
|
|
};
|
|
|
|
res.json(result);
|
|
} catch (error) {
|
|
console.error('Calculator calculation error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Error calculating estimate'
|
|
});
|
|
}
|
|
});
|
|
|
|
// Helper function to calculate confidence level
|
|
function calculateConfidence(serviceCount, complexity, customRequirements) {
|
|
let confidence = 85; // Base confidence
|
|
|
|
// Adjust based on service count
|
|
if (serviceCount === 1) confidence += 10;
|
|
else if (serviceCount > 5) confidence -= 10;
|
|
|
|
// Adjust based on complexity
|
|
if (complexity === 'simple') confidence += 10;
|
|
else if (complexity === 'enterprise') confidence -= 15;
|
|
|
|
// Adjust based on custom requirements
|
|
if (customRequirements && customRequirements.length > 200) {
|
|
confidence -= 20;
|
|
}
|
|
|
|
return Math.max(60, Math.min(95, confidence));
|
|
}
|
|
|
|
// Helper function to generate recommendations
|
|
function generateRecommendations(complexity, timeline, services) {
|
|
const recommendations = [];
|
|
|
|
if (complexity === 'enterprise' && timeline === 'rush') {
|
|
recommendations.push('Consider extending timeline for enterprise-level projects to ensure quality');
|
|
}
|
|
|
|
if (services.length > 6) {
|
|
recommendations.push('Consider breaking down into phases for better project management');
|
|
}
|
|
|
|
if (timeline === 'flexible') {
|
|
recommendations.push('Flexible timeline allows for better cost optimization and quality assurance');
|
|
}
|
|
|
|
const hasDesign = services.some(s => s.category === 'design');
|
|
const hasDevelopment = services.some(s => s.category === 'development');
|
|
|
|
if (hasDevelopment && !hasDesign) {
|
|
recommendations.push('Consider adding UI/UX design services for better user experience');
|
|
}
|
|
|
|
return recommendations;
|
|
}
|
|
|
|
// Get pricing guidelines
|
|
router.get('/pricing-guide', async (req, res) => {
|
|
try {
|
|
const pricingGuide = {
|
|
serviceCategories: {
|
|
'development': {
|
|
name: 'Web Development',
|
|
priceRange: '₩500,000 - ₩5,000,000',
|
|
description: 'Custom web applications and websites'
|
|
},
|
|
'design': {
|
|
name: 'UI/UX Design',
|
|
priceRange: '₩300,000 - ₩2,000,000',
|
|
description: 'User interface and experience design'
|
|
},
|
|
'marketing': {
|
|
name: 'Digital Marketing',
|
|
priceRange: '₩200,000 - ₩1,500,000',
|
|
description: 'SEO, social media, and online marketing'
|
|
},
|
|
'consulting': {
|
|
name: 'Technical Consulting',
|
|
priceRange: '₩150,000 - ₩1,000,000',
|
|
description: 'Technology strategy and consultation'
|
|
}
|
|
},
|
|
complexityFactors: {
|
|
'simple': {
|
|
name: 'Simple Project',
|
|
multiplier: '0.7x',
|
|
description: 'Basic functionality, standard design'
|
|
},
|
|
'medium': {
|
|
name: 'Medium Project',
|
|
multiplier: '1.0x',
|
|
description: 'Moderate complexity, custom features'
|
|
},
|
|
'complex': {
|
|
name: 'Complex Project',
|
|
multiplier: '1.5x',
|
|
description: 'Advanced features, integrations'
|
|
},
|
|
'enterprise': {
|
|
name: 'Enterprise Project',
|
|
multiplier: '2.0x',
|
|
description: 'Large scale, high complexity'
|
|
}
|
|
},
|
|
timelineImpact: {
|
|
'rush': {
|
|
name: 'Rush (< 2 weeks)',
|
|
multiplier: '+80%',
|
|
description: 'Requires overtime and priority handling'
|
|
},
|
|
'fast': {
|
|
name: 'Fast (2-4 weeks)',
|
|
multiplier: '+40%',
|
|
description: 'Accelerated timeline'
|
|
},
|
|
'standard': {
|
|
name: 'Standard (1-3 months)',
|
|
multiplier: 'Standard',
|
|
description: 'Normal project timeline'
|
|
},
|
|
'flexible': {
|
|
name: 'Flexible (3+ months)',
|
|
multiplier: '-20%',
|
|
description: 'Extended timeline allows optimization'
|
|
}
|
|
}
|
|
};
|
|
|
|
res.json({
|
|
success: true,
|
|
pricingGuide
|
|
});
|
|
} catch (error) {
|
|
console.error('Pricing guide error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Error fetching pricing guide'
|
|
});
|
|
}
|
|
});
|
|
|
|
module.exports = router; |