appointment_system/backend/src/services/adminServiceService.js

258 lines
7.1 KiB
JavaScript

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<Object>} - 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<Object>} - 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<Object>} - 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<boolean>} - 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<Object>} - 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;