Files
niumalll/backend/routes/suppliers.js
ylweng 5b6b50b60b feat(backend): 开发订单管理和供应商管理功能
- 新增订单管理页面,实现订单列表展示、搜索、分页等功能
- 新增供应商管理页面,实现供应商列表展示、搜索、分页等功能- 添加订单和供应商相关模型及数据库迁移
- 实现订单状态更新和供应商信息编辑功能
- 优化后端路由结构,移除不必要的代码
2025-09-18 23:51:25 +08:00

433 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const express = require('express');
const router = express.Router();
const Joi = require('joi');
const { Supplier } = require('../models');
const { Sequelize } = require('sequelize');
// 验证schemas
const supplierCreateSchema = Joi.object({
name: Joi.string().min(2).max(100).required(),
code: Joi.string().min(3).max(20).required(),
contact: Joi.string().min(2).max(50).required(),
phone: Joi.string().pattern(/^1[3-9]\d{9}$/).required(),
address: Joi.string().min(5).max(200).required(),
businessLicense: Joi.string().max(255).optional(),
qualificationLevel: Joi.string().valid('A+', 'A', 'B+', 'B', 'C').required(),
certifications: Joi.array().items(Joi.string()).optional(),
cattleTypes: Joi.array().items(Joi.string()).min(1).required(),
capacity: Joi.number().integer().min(1).required(),
region: Joi.string().valid('north', 'south', 'east', 'west', 'northeast', 'northwest', 'southeast', 'southwest', 'central').required()
});
const supplierUpdateSchema = Joi.object({
name: Joi.string().min(2).max(100),
contact: Joi.string().min(2).max(50),
phone: Joi.string().pattern(/^1[3-9]\d{9}$/),
address: Joi.string().min(5).max(200),
businessLicense: Joi.string().max(255).optional(),
qualificationLevel: Joi.string().valid('A+', 'A', 'B+', 'B', 'C'),
certifications: Joi.array().items(Joi.string()).optional(),
cattleTypes: Joi.array().items(Joi.string()).min(1),
capacity: Joi.number().integer().min(1),
region: Joi.string().valid('north', 'south', 'east', 'west', 'northeast', 'northwest', 'southeast', 'southwest', 'central'),
status: Joi.string().valid('active', 'inactive', 'suspended')
});
// 获取供应商列表
router.get('/', async (req, res) => {
try {
const {
page = 1,
pageSize = 20,
keyword,
region,
qualificationLevel,
status
} = req.query;
// 构建查询条件
const whereConditions = {};
// 状态筛选
if (status) {
whereConditions.status = status;
}
// 区域筛选
if (region) {
whereConditions.region = region;
}
// 资质等级筛选
if (qualificationLevel) {
whereConditions.qualificationLevel = qualificationLevel;
}
// 关键词搜索
if (keyword) {
whereConditions[Sequelize.Op.or] = [
{ name: { [Sequelize.Op.like]: `%${keyword}%` } },
{ code: { [Sequelize.Op.like]: `%${keyword}%` } },
{ contact: { [Sequelize.Op.like]: `%${keyword}%` } }
];
}
// 分页参数
const offset = (page - 1) * pageSize;
const limit = parseInt(pageSize);
// 查询数据库
const { rows, count } = await Supplier.findAndCountAll({
where: whereConditions,
offset,
limit,
order: [['created_at', 'DESC']]
});
// 解析JSON字段
const processedRows = rows.map(row => {
const rowData = row.toJSON();
if (rowData.cattleTypes && typeof rowData.cattleTypes === 'string') {
rowData.cattleTypes = JSON.parse(rowData.cattleTypes);
}
if (rowData.certifications && typeof rowData.certifications === 'string') {
rowData.certifications = JSON.parse(rowData.certifications);
}
return rowData;
});
res.json({
success: true,
data: {
list: processedRows,
pagination: {
page: parseInt(page),
pageSize: limit,
total: count,
totalPages: Math.ceil(count / limit)
}
}
});
} catch (error) {
console.error('获取供应商列表失败:', error);
res.status(500).json({
success: false,
message: '获取供应商列表失败',
error: error.message
});
}
});
// 获取供应商详情
router.get('/:id', async (req, res) => {
try {
const { id } = req.params;
// 查询数据库
const supplier = await Supplier.findByPk(id);
if (!supplier) {
return res.status(404).json({
success: false,
message: '供应商不存在'
});
}
// 解析JSON字段
const supplierData = supplier.toJSON();
if (supplierData.cattleTypes && typeof supplierData.cattleTypes === 'string') {
supplierData.cattleTypes = JSON.parse(supplierData.cattleTypes);
}
if (supplierData.certifications && typeof supplierData.certifications === 'string') {
supplierData.certifications = JSON.parse(supplierData.certifications);
}
res.json({
success: true,
data: supplierData
});
} catch (error) {
console.error('获取供应商详情失败:', error);
res.status(500).json({
success: false,
message: '获取供应商详情失败',
error: error.message
});
}
});
// 创建供应商
router.post('/', async (req, res) => {
try {
const { error, value } = supplierCreateSchema.validate(req.body);
if (error) {
return res.status(400).json({
success: false,
message: '参数验证失败',
errors: error.details.map(detail => detail.message)
});
}
// 检查编码是否重复
const existingSupplier = await Supplier.findOne({ where: { code: value.code } });
if (existingSupplier) {
return res.status(400).json({
success: false,
message: '供应商编码已存在'
});
}
// 检查电话是否重复
const existingPhone = await Supplier.findOne({ where: { phone: value.phone } });
if (existingPhone) {
return res.status(400).json({
success: false,
message: '供应商电话已存在'
});
}
// 创建新供应商
const newSupplier = await Supplier.create({
...value,
businessLicense: value.businessLicense || '',
certifications: value.certifications ? JSON.stringify(value.certifications) : JSON.stringify([]),
cattleTypes: JSON.stringify(value.cattleTypes),
rating: 0,
cooperationStartDate: new Date(),
status: 'active'
});
res.status(201).json({
success: true,
message: '供应商创建成功',
data: newSupplier
});
} catch (error) {
console.error('创建供应商失败:', error);
res.status(500).json({
success: false,
message: '创建供应商失败',
error: error.message
});
}
});
// 更新供应商
router.put('/:id', async (req, res) => {
try {
const { id } = req.params;
const { error, value } = supplierUpdateSchema.validate(req.body);
if (error) {
return res.status(400).json({
success: false,
message: '参数验证失败',
errors: error.details.map(detail => detail.message)
});
}
// 查找供应商
const supplier = await Supplier.findByPk(id);
if (!supplier) {
return res.status(404).json({
success: false,
message: '供应商不存在'
});
}
// 如果更新了电话号码,检查是否重复
if (value.phone && value.phone !== supplier.phone) {
const existingPhone = await Supplier.findOne({ where: { phone: value.phone } });
if (existingPhone) {
return res.status(400).json({
success: false,
message: '供应商电话已存在'
});
}
}
// 更新供应商信息
await supplier.update({
...value,
businessLicense: value.businessLicense !== undefined ? value.businessLicense : undefined,
certifications: value.certifications !== undefined ? JSON.stringify(value.certifications) : undefined,
cattleTypes: value.cattleTypes ? JSON.stringify(value.cattleTypes) : undefined
});
res.json({
success: true,
message: '供应商更新成功',
data: supplier
});
} catch (error) {
console.error('更新供应商失败:', error);
res.status(500).json({
success: false,
message: '更新供应商失败',
error: error.message
});
}
});
// 删除供应商
router.delete('/:id', async (req, res) => {
try {
const { id } = req.params;
// 查找供应商
const supplier = await Supplier.findByPk(id);
if (!supplier) {
return res.status(404).json({
success: false,
message: '供应商不存在'
});
}
// 删除供应商
await supplier.destroy();
res.json({
success: true,
message: '供应商删除成功'
});
} catch (error) {
console.error('删除供应商失败:', error);
res.status(500).json({
success: false,
message: '删除供应商失败',
error: error.message
});
}
});
// 获取供应商统计信息
router.get('/stats/overview', async (req, res) => {
try {
// 获取总数和活跃数
const totalSuppliers = await Supplier.count();
const activeSuppliers = await Supplier.count({ where: { status: 'active' } });
// 获取平均评分排除评分为0的供应商
const ratingResult = await Supplier.findOne({
attributes: [
[Sequelize.fn('AVG', Sequelize.col('rating')), 'averageRating']
],
where: {
rating: {
[Sequelize.Op.gt]: 0
}
}
});
const averageRating = ratingResult ? parseFloat(ratingResult.getDataValue('averageRating')).toFixed(2) : 0;
// 获取总产能
const capacityResult = await Supplier.findOne({
attributes: [
[Sequelize.fn('SUM', Sequelize.col('capacity')), 'totalCapacity']
]
});
const totalCapacity = capacityResult ? capacityResult.getDataValue('totalCapacity') : 0;
// 按等级统计
const levelStatsResult = await Supplier.findAll({
attributes: [
'qualificationLevel',
[Sequelize.fn('COUNT', Sequelize.col('id')), 'count']
],
group: ['qualificationLevel']
});
const levelStats = levelStatsResult.reduce((stats, item) => {
stats[item.qualificationLevel] = item.getDataValue('count');
return stats;
}, {});
// 按区域统计
const regionStatsResult = await Supplier.findAll({
attributes: [
'region',
[Sequelize.fn('COUNT', Sequelize.col('id')), 'count']
],
group: ['region']
});
const regionStats = regionStatsResult.reduce((stats, item) => {
stats[item.region] = item.getDataValue('count');
return stats;
}, {});
res.json({
success: true,
data: {
totalSuppliers,
activeSuppliers,
averageRating: parseFloat(averageRating),
totalCapacity,
levelStats,
regionStats
}
});
} catch (error) {
console.error('获取供应商统计信息失败:', error);
res.status(500).json({
success: false,
message: '获取供应商统计信息失败',
error: error.message
});
}
});
// 批量操作供应商
router.post('/batch', async (req, res) => {
try {
const { ids, action } = req.body;
if (!ids || !Array.isArray(ids) || ids.length === 0) {
return res.status(400).json({
success: false,
message: '请选择要操作的供应商'
});
}
if (!['activate', 'deactivate', 'delete'].includes(action)) {
return res.status(400).json({
success: false,
message: '无效的操作类型'
});
}
switch (action) {
case 'activate':
await Supplier.update(
{ status: 'active' },
{ where: { id: ids } }
);
break;
case 'deactivate':
await Supplier.update(
{ status: 'inactive' },
{ where: { id: ids } }
);
break;
case 'delete':
await Supplier.destroy({
where: {
id: ids
}
});
break;
}
res.json({
success: true,
message: '批量操作成功'
});
} catch (error) {
console.error('批量操作失败:', error);
res.status(500).json({
success: false,
message: '批量操作失败',
error: error.message
});
}
});
module.exports = router;