const { Service, Category } = require('../models'); const CacheService = require('./cacheService'); const logger = require('../config/logger'); const { Op } = require('sequelize'); /** * Admin Service Management Business Logic */ class AdminServiceService { /** * Get all services with pagination and filtering (admin view) * @param {Object} options - Query options * @returns {Promise} - Paginated services */ static async getServices({ page = 1, limit = 10, categoryId, status, search }) { try { const offset = (page - 1) * limit; // Build query const where = {}; if (categoryId) { where.categoryId = categoryId; } if (status) { where.status = status; } if (search) { where[Op.or] = [ { titleZh: { [Op.like]: `%${search}%` } }, { titleEn: { [Op.like]: `%${search}%` } }, { titleEs: { [Op.like]: `%${search}%` } }, ]; } // Fetch from database const { count, rows } = await Service.findAndCountAll({ where, include: [ { model: Category, as: 'category', attributes: ['id', 'key', 'nameZh', 'nameEn', 'nameEs', 'icon'], }, ], order: [['sortOrder', 'ASC'], ['createdAt', 'DESC']], limit: parseInt(limit), offset: parseInt(offset), }); return { data: rows.map(service => service.toJSON()), pagination: { page: parseInt(page), limit: parseInt(limit), total: count, totalPages: Math.ceil(count / limit), }, }; } catch (error) { logger.error('Error fetching services (admin):', error); throw error; } } /** * Create a new service * @param {Object} serviceData - Service data * @returns {Promise} - Created service */ static async createService(serviceData) { try { // Validate category exists const category = await Category.findByPk(serviceData.categoryId); if (!category) { const error = new Error('Category not found'); error.statusCode = 404; error.code = 'CATEGORY_NOT_FOUND'; throw error; } // Validate required fields const requiredFields = ['categoryId', 'titleZh', 'titleEn', 'titleEs']; for (const field of requiredFields) { if (!serviceData[field]) { const error = new Error(`Missing required field: ${field}`); error.statusCode = 400; error.code = 'MISSING_REQUIRED_FIELD'; throw error; } } // Create service const service = await Service.create({ categoryId: serviceData.categoryId, serviceType: serviceData.serviceType || null, titleZh: serviceData.titleZh, titleEn: serviceData.titleEn, titleEs: serviceData.titleEs, descriptionZh: serviceData.descriptionZh || null, descriptionEn: serviceData.descriptionEn || null, descriptionEs: serviceData.descriptionEs || null, image: serviceData.image || null, price: serviceData.price || null, status: serviceData.status || 'active', sortOrder: serviceData.sortOrder || 0, }); // Invalidate cache await CacheService.invalidateServiceCache(); logger.info(`Service created: ${service.id}`); return service.toJSON(); } catch (error) { logger.error('Error creating service:', error); throw error; } } /** * Update a service * @param {string} serviceId - Service ID * @param {Object} updateData - Update data * @returns {Promise} - Updated service */ static async updateService(serviceId, updateData) { try { // Find service const service = await Service.findByPk(serviceId); if (!service) { const error = new Error('Service not found'); error.statusCode = 404; error.code = 'SERVICE_NOT_FOUND'; throw error; } // Validate category if being updated if (updateData.categoryId) { const category = await Category.findByPk(updateData.categoryId); if (!category) { const error = new Error('Category not found'); error.statusCode = 404; error.code = 'CATEGORY_NOT_FOUND'; throw error; } } // Update service const allowedFields = [ 'categoryId', 'serviceType', 'titleZh', 'titleEn', 'titleEs', 'descriptionZh', 'descriptionEn', 'descriptionEs', 'image', 'price', 'status', 'sortOrder', ]; const updateFields = {}; for (const field of allowedFields) { if (updateData[field] !== undefined) { updateFields[field] = updateData[field]; } } await service.update(updateFields); // Invalidate cache await CacheService.invalidateServiceCache(); await CacheService.invalidateServiceDetail(serviceId); logger.info(`Service updated: ${serviceId}`); return service.toJSON(); } catch (error) { logger.error(`Error updating service ${serviceId}:`, error); throw error; } } /** * Delete a service * @param {string} serviceId - Service ID * @returns {Promise} - Success status */ static async deleteService(serviceId) { try { // Find service const service = await Service.findByPk(serviceId); if (!service) { const error = new Error('Service not found'); error.statusCode = 404; error.code = 'SERVICE_NOT_FOUND'; throw error; } // Check if service has appointments const { Appointment } = require('../models'); const appointmentCount = await Appointment.count({ where: { serviceId }, }); if (appointmentCount > 0) { const error = new Error('Cannot delete service with existing appointments'); error.statusCode = 400; error.code = 'SERVICE_HAS_APPOINTMENTS'; throw error; } // Delete service await service.destroy(); // Invalidate cache await CacheService.invalidateServiceCache(); await CacheService.invalidateServiceDetail(serviceId); logger.info(`Service deleted: ${serviceId}`); return true; } catch (error) { logger.error(`Error deleting service ${serviceId}:`, error); throw error; } } /** * Get service by ID (admin view with all fields) * @param {string} serviceId - Service ID * @returns {Promise} - Service details */ static async getServiceById(serviceId) { try { const service = await Service.findByPk(serviceId, { include: [ { model: Category, as: 'category', attributes: ['id', 'key', 'nameZh', 'nameEn', 'nameEs', 'icon'], }, ], }); if (!service) { return null; } return service.toJSON(); } catch (error) { logger.error(`Error fetching service ${serviceId}:`, error); throw error; } } } module.exports = AdminServiceService;