2025-09-24 10:02:03 +08:00
|
|
|
|
const express = require('express');
|
|
|
|
|
|
const router = express.Router();
|
2025-09-26 14:38:44 +08:00
|
|
|
|
const {getDB} = require('../database');
|
|
|
|
|
|
const {auth, adminAuth} = require('../middleware/auth');
|
|
|
|
|
|
const {SelectBuilder} = require('../config/dbv2')
|
|
|
|
|
|
const {db} = require("../server");
|
|
|
|
|
|
const sql = require("../config/config");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
router.get('/names', auth, async (req, res) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const {status = 'active'} = req.query;
|
|
|
|
|
|
const {id: created_id} = req.user;
|
|
|
|
|
|
let query = `SELECT *
|
|
|
|
|
|
FROM spec_names
|
|
|
|
|
|
WHERE created_id = ${created_id}`;
|
|
|
|
|
|
const params = [];
|
|
|
|
|
|
|
|
|
|
|
|
if (status) {
|
|
|
|
|
|
query += ' and status = ?';
|
|
|
|
|
|
params.push(status);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
query += ' ORDER BY sort_order, id';
|
|
|
|
|
|
|
|
|
|
|
|
const [specNames] = await getDB().execute(query, params);
|
|
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
data: specNames
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取规格名称失败:', error);
|
|
|
|
|
|
res.status(500).json({success: false, message: '获取规格名称失败'});
|
2025-09-24 10:02:03 +08:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-09-26 14:38:44 +08:00
|
|
|
|
|
2025-09-24 10:02:03 +08:00
|
|
|
|
router.post('/names', auth, adminAuth, async (req, res) => {
|
2025-09-26 14:38:44 +08:00
|
|
|
|
const db = getDB();
|
|
|
|
|
|
try {
|
|
|
|
|
|
const {name, display_name, sort_order = 0} = req.body;
|
|
|
|
|
|
const {id: created_id} = req.user;
|
|
|
|
|
|
if (!name || !display_name) {
|
|
|
|
|
|
return res.status(400).json({success: false, message: '规格名称和显示名称不能为空'});
|
|
|
|
|
|
}
|
|
|
|
|
|
let spec_name = new SelectBuilder().from()
|
|
|
|
|
|
const [result] = await getDB().execute(
|
|
|
|
|
|
'INSERT INTO spec_names (name, display_name, sort_order,created_id) VALUES (?, ?, ?,?)',
|
|
|
|
|
|
[name, display_name, sort_order,created_id]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
res.status(201).json({
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
message: '规格名称创建成功',
|
|
|
|
|
|
data: {id: result.insertId}
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
if (error.code === 'ER_DUP_ENTRY') {
|
|
|
|
|
|
return res.status(400).json({success: false, message: '规格名称已存在'});
|
|
|
|
|
|
}
|
|
|
|
|
|
console.error('创建规格名称失败:', error);
|
|
|
|
|
|
res.status(500).json({success: false, message: '创建规格名称失败'});
|
2025-09-24 10:02:03 +08:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-09-26 14:38:44 +08:00
|
|
|
|
|
2025-09-24 10:02:03 +08:00
|
|
|
|
router.delete('/names/:id', auth, adminAuth, async (req, res) => {
|
2025-09-26 14:38:44 +08:00
|
|
|
|
try {
|
|
|
|
|
|
const {id} = req.params;
|
|
|
|
|
|
const {id:user_id,role} = req.user
|
|
|
|
|
|
|
|
|
|
|
|
let specCountQuery = new SelectBuilder()
|
|
|
|
|
|
.from('spec_names')
|
|
|
|
|
|
.select('COUNT(*) as total')
|
|
|
|
|
|
.where('id=?',id)
|
|
|
|
|
|
if(role !== 'admin'){
|
|
|
|
|
|
specCountQuery.where('created_id=?',user_id)
|
|
|
|
|
|
}
|
|
|
|
|
|
let [spec] = await specCountQuery.execute(db)
|
|
|
|
|
|
if (spec.total === 0) {
|
|
|
|
|
|
return res.status(404).json({success: false, message: '规格名称不存在'});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查该规格名称下是否还有规格值
|
|
|
|
|
|
const [specValues] = await getDB().execute(
|
|
|
|
|
|
'SELECT COUNT(*) as count FROM spec_values WHERE spec_name_id = ?',
|
|
|
|
|
|
[id]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (specValues[0].count > 0) {
|
|
|
|
|
|
return res.status(400).json({success: false, message: '该规格名称下还有规格值,请先删除所有规格值'});
|
|
|
|
|
|
}
|
|
|
|
|
|
// 删除规格名称
|
|
|
|
|
|
await getDB().execute(
|
|
|
|
|
|
'DELETE FROM spec_names WHERE id = ?',
|
|
|
|
|
|
[id]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
message: '规格名称删除成功'
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('删除规格名称失败:', error);
|
|
|
|
|
|
res.status(500).json({success: false, message: '删除规格名称失败'});
|
2025-09-24 10:02:03 +08:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-09-26 14:38:44 +08:00
|
|
|
|
|
2025-09-24 10:02:03 +08:00
|
|
|
|
router.get('/values', async (req, res) => {
|
2025-09-26 14:38:44 +08:00
|
|
|
|
try {
|
|
|
|
|
|
const {spec_name_id, status = 'active'} = req.query;
|
|
|
|
|
|
|
|
|
|
|
|
let query = `
|
|
|
|
|
|
SELECT sv.*, sn.name as spec_name, sn.display_name as spec_display_name
|
|
|
|
|
|
FROM spec_values sv
|
|
|
|
|
|
LEFT JOIN spec_names sn ON sv.spec_name_id = sn.id
|
|
|
|
|
|
WHERE 1 = 1
|
|
|
|
|
|
`;
|
|
|
|
|
|
const params = [];
|
|
|
|
|
|
|
|
|
|
|
|
if (spec_name_id) {
|
|
|
|
|
|
query += ' AND sv.spec_name_id = ?';
|
|
|
|
|
|
params.push(spec_name_id);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (status) {
|
|
|
|
|
|
query += ' AND sv.status = ?';
|
|
|
|
|
|
params.push(status);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
query += ' ORDER BY sv.spec_name_id, sv.sort_order, sv.id';
|
|
|
|
|
|
|
|
|
|
|
|
const [specValues] = await getDB().execute(query, params);
|
|
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
data: specValues
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取规格值失败:', error);
|
|
|
|
|
|
res.status(500).json({success: false, message: '获取规格值失败'});
|
2025-09-24 10:02:03 +08:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-09-26 14:38:44 +08:00
|
|
|
|
|
2025-09-24 10:02:03 +08:00
|
|
|
|
router.post('/values', auth, adminAuth, async (req, res) => {
|
2025-09-26 14:38:44 +08:00
|
|
|
|
try {
|
|
|
|
|
|
const {spec_name_id, value, display_value, color_code, image_url, sort_order = 0} = req.body;
|
|
|
|
|
|
|
|
|
|
|
|
if (!spec_name_id || !value || !display_value) {
|
|
|
|
|
|
return res.status(400).json({success: false, message: '规格名称ID、规格值和显示值不能为空'});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const [result] = await getDB().execute(
|
|
|
|
|
|
`INSERT INTO spec_values (spec_name_id, value, display_value, color_code, image_url, sort_order)
|
|
|
|
|
|
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
|
|
|
|
[spec_name_id, value, display_value, color_code || null, image_url || null, sort_order]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
res.status(201).json({
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
message: '规格值创建成功',
|
|
|
|
|
|
data: {id: result.insertId}
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
if (error.code === 'ER_DUP_ENTRY') {
|
|
|
|
|
|
return res.status(400).json({success: false, message: '该规格名称下的规格值已存在'});
|
|
|
|
|
|
}
|
|
|
|
|
|
console.error('创建规格值失败:', error);
|
|
|
|
|
|
res.status(500).json({success: false, message: '创建规格值失败'});
|
2025-09-24 10:02:03 +08:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-09-26 14:38:44 +08:00
|
|
|
|
|
2025-09-24 10:02:03 +08:00
|
|
|
|
router.get('/combinations/:productId', async (req, res) => {
|
2025-09-26 14:38:44 +08:00
|
|
|
|
try {
|
|
|
|
|
|
const {productId} = req.params;
|
|
|
|
|
|
const {status = 'active'} = req.query;
|
|
|
|
|
|
|
|
|
|
|
|
// 获取商品的规格组合
|
|
|
|
|
|
let query = `
|
|
|
|
|
|
SELECT psc.*,
|
|
|
|
|
|
p.name as product_name,
|
|
|
|
|
|
p.price as base_price,
|
|
|
|
|
|
p.points_price as base_points_price,
|
|
|
|
|
|
p.rongdou_price as base_rongdou_price
|
|
|
|
|
|
FROM product_spec_combinations psc
|
|
|
|
|
|
LEFT JOIN products p ON psc.product_id = p.id
|
|
|
|
|
|
WHERE psc.product_id = ?
|
|
|
|
|
|
`;
|
|
|
|
|
|
const params = [productId];
|
|
|
|
|
|
|
|
|
|
|
|
if (status) {
|
|
|
|
|
|
query += ' AND psc.status = ?';
|
|
|
|
|
|
params.push(status);
|
2025-09-24 10:02:03 +08:00
|
|
|
|
}
|
2025-09-26 14:38:44 +08:00
|
|
|
|
|
|
|
|
|
|
query += ' ORDER BY psc.combination_key';
|
|
|
|
|
|
|
|
|
|
|
|
const [combinations] = await getDB().execute(query, params);
|
|
|
|
|
|
|
|
|
|
|
|
// 为每个组合获取详细的规格值信息
|
|
|
|
|
|
for (let combination of combinations) {
|
|
|
|
|
|
let specValueIds;
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 处理不同的数据格式
|
|
|
|
|
|
if (!combination.spec_values) {
|
|
|
|
|
|
specValueIds = [];
|
|
|
|
|
|
} else if (typeof combination.spec_values === 'string') {
|
|
|
|
|
|
// 如果是字符串,尝试JSON解析,失败则按逗号分隔处理
|
|
|
|
|
|
try {
|
|
|
|
|
|
specValueIds = JSON.parse(combination.spec_values);
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
// 按逗号分隔的字符串处理
|
|
|
|
|
|
specValueIds = combination.spec_values.split(',').map(id => parseInt(id.trim())).filter(id => !isNaN(id));
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if (Buffer.isBuffer(combination.spec_values)) {
|
|
|
|
|
|
// 如果是Buffer,转换为字符串后处理
|
|
|
|
|
|
const strValue = combination.spec_values.toString();
|
|
|
|
|
|
try {
|
|
|
|
|
|
specValueIds = JSON.parse(strValue);
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
specValueIds = strValue.split(',').map(id => parseInt(id.trim())).filter(id => !isNaN(id));
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 其他情况,尝试直接使用
|
|
|
|
|
|
specValueIds = Array.isArray(combination.spec_values) ? combination.spec_values : [];
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('解析规格值失败:', combination.spec_values, error);
|
|
|
|
|
|
specValueIds = [];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (specValueIds && specValueIds.length > 0) {
|
|
|
|
|
|
const placeholders = specValueIds.map(() => '?').join(',');
|
|
|
|
|
|
const [specDetails] = await getDB().execute(
|
|
|
|
|
|
`SELECT sv.*, sn.name as spec_name, sn.display_name as spec_display_name
|
|
|
|
|
|
FROM spec_values sv
|
|
|
|
|
|
LEFT JOIN spec_names sn ON sv.spec_name_id = sn.id
|
|
|
|
|
|
WHERE sv.id IN (${placeholders})
|
|
|
|
|
|
ORDER BY sn.sort_order, sv.sort_order`,
|
|
|
|
|
|
specValueIds
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
combination.spec_details = specDetails;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
combination.spec_details = [];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 计算实际价格
|
|
|
|
|
|
combination.actual_price = combination.base_price + (combination.price_adjustment || 0);
|
|
|
|
|
|
combination.actual_points_price = combination.base_points_price + (combination.points_adjustment || 0);
|
|
|
|
|
|
combination.actual_rongdou_price = combination.base_rongdou_price + (combination.rongdou_adjustment || 0);
|
|
|
|
|
|
combination.is_available = combination.stock > 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
data: combinations
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取规格组合失败:', error);
|
|
|
|
|
|
res.status(500).json({success: false, message: '获取规格组合失败'});
|
2025-09-24 10:02:03 +08:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
router.get('/combinations/:id', async (req, res) => {
|
|
|
|
|
|
try {
|
2025-09-26 14:38:44 +08:00
|
|
|
|
const {id} = req.params;
|
|
|
|
|
|
|
|
|
|
|
|
// 获取规格组合详情
|
|
|
|
|
|
const [combinations] = await getDB().execute(
|
|
|
|
|
|
`SELECT psc.*,
|
|
|
|
|
|
p.name as product_name,
|
|
|
|
|
|
p.price as base_price,
|
|
|
|
|
|
p.points_price as base_points_price,
|
|
|
|
|
|
p.rongdou_price as base_rongdou_price
|
|
|
|
|
|
FROM product_spec_combinations psc
|
|
|
|
|
|
LEFT JOIN products p ON psc.product_id = p.id
|
|
|
|
|
|
WHERE psc.id = ?`,
|
|
|
|
|
|
[id]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (combinations.length === 0) {
|
|
|
|
|
|
return res.status(404).json({success: false, message: '规格组合不存在'});
|
2025-09-24 10:02:03 +08:00
|
|
|
|
}
|
2025-09-26 14:38:44 +08:00
|
|
|
|
|
|
|
|
|
|
const combination = combinations[0];
|
|
|
|
|
|
|
|
|
|
|
|
// 解析规格值并获取详细信息
|
|
|
|
|
|
let specValueIds;
|
2025-09-24 10:02:03 +08:00
|
|
|
|
try {
|
2025-09-26 14:38:44 +08:00
|
|
|
|
if (!combination.spec_values) {
|
|
|
|
|
|
specValueIds = [];
|
|
|
|
|
|
} else if (typeof combination.spec_values === 'string') {
|
|
|
|
|
|
try {
|
|
|
|
|
|
specValueIds = JSON.parse(combination.spec_values);
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
specValueIds = combination.spec_values.split(',').map(id => parseInt(id.trim())).filter(id => !isNaN(id));
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if (Buffer.isBuffer(combination.spec_values)) {
|
|
|
|
|
|
const strValue = combination.spec_values.toString();
|
|
|
|
|
|
try {
|
|
|
|
|
|
specValueIds = JSON.parse(strValue);
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
specValueIds = strValue.split(',').map(id => parseInt(id.trim())).filter(id => !isNaN(id));
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
specValueIds = Array.isArray(combination.spec_values) ? combination.spec_values : [];
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('解析规格值失败:', combination.spec_values, error);
|
|
|
|
|
|
specValueIds = [];
|
2025-09-24 10:02:03 +08:00
|
|
|
|
}
|
2025-09-26 14:38:44 +08:00
|
|
|
|
|
|
|
|
|
|
if (specValueIds && specValueIds.length > 0) {
|
|
|
|
|
|
const placeholders = specValueIds.map(() => '?').join(',');
|
|
|
|
|
|
const [specDetails] = await getDB().execute(
|
|
|
|
|
|
`SELECT sv.*, sn.name as spec_name, sn.display_name as spec_display_name
|
|
|
|
|
|
FROM spec_values sv
|
|
|
|
|
|
LEFT JOIN spec_names sn ON sv.spec_name_id = sn.id
|
|
|
|
|
|
WHERE sv.id IN (${placeholders})
|
|
|
|
|
|
ORDER BY sn.sort_order, sv.sort_order`,
|
|
|
|
|
|
specValueIds
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
combination.spec_details = specDetails;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
combination.spec_details = [];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 计算实际价格
|
|
|
|
|
|
combination.actual_price = combination.base_price + (combination.price_adjustment || 0);
|
|
|
|
|
|
combination.actual_points_price = combination.base_points_price + (combination.points_adjustment || 0);
|
|
|
|
|
|
combination.actual_rongdou_price = combination.base_rongdou_price + (combination.rongdou_adjustment || 0);
|
|
|
|
|
|
combination.is_available = combination.stock > 0;
|
|
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
data: combination
|
|
|
|
|
|
});
|
2025-09-24 10:02:03 +08:00
|
|
|
|
} catch (error) {
|
2025-09-26 14:38:44 +08:00
|
|
|
|
console.error('获取规格组合详情失败:', error);
|
|
|
|
|
|
res.status(500).json({success: false, message: '获取规格组合详情失败'});
|
2025-09-24 10:02:03 +08:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-09-26 14:38:44 +08:00
|
|
|
|
|
2025-09-24 10:02:03 +08:00
|
|
|
|
router.delete('/combinations/:id', auth, adminAuth, async (req, res) => {
|
2025-09-26 14:38:44 +08:00
|
|
|
|
try {
|
|
|
|
|
|
const {id} = req.params;
|
|
|
|
|
|
|
|
|
|
|
|
// 检查规格组合是否存在
|
|
|
|
|
|
const [existingCombination] = await getDB().execute(
|
|
|
|
|
|
'SELECT id FROM product_spec_combinations WHERE id = ?',
|
|
|
|
|
|
[id]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (existingCombination.length === 0) {
|
|
|
|
|
|
return res.status(404).json({success: false, message: '规格组合不存在'});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 删除规格组合
|
|
|
|
|
|
await getDB().execute(
|
|
|
|
|
|
'DELETE FROM product_spec_combinations WHERE id = ?',
|
|
|
|
|
|
[id]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
message: '规格组合删除成功'
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('删除规格组合失败:', error);
|
|
|
|
|
|
res.status(500).json({success: false, message: '删除规格组合失败'});
|
2025-09-24 10:02:03 +08:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-09-26 14:38:44 +08:00
|
|
|
|
|
2025-09-24 10:02:03 +08:00
|
|
|
|
router.put('/combinations/:id', auth, adminAuth, async (req, res) => {
|
2025-09-26 14:38:44 +08:00
|
|
|
|
try {
|
|
|
|
|
|
const {id} = req.params;
|
|
|
|
|
|
const {
|
|
|
|
|
|
price_adjustment,
|
|
|
|
|
|
points_adjustment,
|
|
|
|
|
|
rongdou_adjustment,
|
|
|
|
|
|
stock,
|
|
|
|
|
|
sku_code,
|
|
|
|
|
|
barcode,
|
|
|
|
|
|
weight,
|
|
|
|
|
|
volume,
|
|
|
|
|
|
status
|
|
|
|
|
|
} = req.body;
|
|
|
|
|
|
|
|
|
|
|
|
// 检查规格组合是否存在
|
|
|
|
|
|
const [existing] = await getDB().execute(
|
|
|
|
|
|
'SELECT id FROM product_spec_combinations WHERE id = ?',
|
|
|
|
|
|
[id]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (existing.length === 0) {
|
|
|
|
|
|
return res.status(404).json({success: false, message: '规格组合不存在'});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 构建更新字段
|
|
|
|
|
|
const updateFields = [];
|
|
|
|
|
|
const updateValues = [];
|
|
|
|
|
|
|
|
|
|
|
|
if (price_adjustment !== undefined) {
|
|
|
|
|
|
updateFields.push('price_adjustment = ?');
|
|
|
|
|
|
updateValues.push(price_adjustment);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (points_adjustment !== undefined) {
|
|
|
|
|
|
updateFields.push('points_adjustment = ?');
|
|
|
|
|
|
updateValues.push(points_adjustment);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (rongdou_adjustment !== undefined) {
|
|
|
|
|
|
updateFields.push('rongdou_adjustment = ?');
|
|
|
|
|
|
updateValues.push(rongdou_adjustment);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (stock !== undefined) {
|
|
|
|
|
|
updateFields.push('stock = ?');
|
|
|
|
|
|
updateValues.push(stock);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (sku_code !== undefined) {
|
|
|
|
|
|
updateFields.push('sku_code = ?');
|
|
|
|
|
|
updateValues.push(sku_code);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (barcode !== undefined) {
|
|
|
|
|
|
updateFields.push('barcode = ?');
|
|
|
|
|
|
updateValues.push(barcode);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (weight !== undefined) {
|
|
|
|
|
|
updateFields.push('weight = ?');
|
|
|
|
|
|
updateValues.push(weight);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (volume !== undefined) {
|
|
|
|
|
|
updateFields.push('volume = ?');
|
|
|
|
|
|
updateValues.push(volume);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (status !== undefined) {
|
|
|
|
|
|
updateFields.push('status = ?');
|
|
|
|
|
|
updateValues.push(status);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (updateFields.length === 0) {
|
|
|
|
|
|
return res.status(400).json({success: false, message: '没有提供要更新的字段'});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
updateFields.push('updated_at = NOW()');
|
|
|
|
|
|
updateValues.push(id);
|
|
|
|
|
|
|
|
|
|
|
|
const updateQuery = `UPDATE product_spec_combinations
|
|
|
|
|
|
SET ${updateFields.join(', ')}
|
|
|
|
|
|
WHERE id = ?`;
|
|
|
|
|
|
|
|
|
|
|
|
await getDB().execute(updateQuery, updateValues);
|
|
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
message: '规格组合更新成功'
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('更新规格组合失败:', error);
|
|
|
|
|
|
res.status(500).json({success: false, message: '更新规格组合失败'});
|
2025-09-24 10:02:03 +08:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-09-26 14:38:44 +08:00
|
|
|
|
|
2025-09-24 10:02:03 +08:00
|
|
|
|
router.post('/combinations', auth, adminAuth, async (req, res) => {
|
2025-09-26 14:38:44 +08:00
|
|
|
|
try {
|
|
|
|
|
|
const {
|
|
|
|
|
|
product_id,
|
|
|
|
|
|
spec_values,
|
|
|
|
|
|
price_adjustment = 0,
|
|
|
|
|
|
points_adjustment = 0,
|
|
|
|
|
|
rongdou_adjustment = 0,
|
|
|
|
|
|
stock = 0,
|
|
|
|
|
|
sku_code,
|
|
|
|
|
|
barcode,
|
|
|
|
|
|
weight,
|
|
|
|
|
|
volume
|
|
|
|
|
|
} = req.body;
|
|
|
|
|
|
|
|
|
|
|
|
if (!product_id || !spec_values || !Array.isArray(spec_values) || spec_values.length === 0) {
|
|
|
|
|
|
return res.status(400).json({success: false, message: '商品ID和规格值数组不能为空'});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 生成组合键
|
|
|
|
|
|
const sortedSpecValues = [...spec_values].sort((a, b) => a - b);
|
|
|
|
|
|
const combinationKey = sortedSpecValues.join('-');
|
|
|
|
|
|
|
|
|
|
|
|
const [result] = await getDB().execute(
|
|
|
|
|
|
`INSERT INTO product_spec_combinations
|
|
|
|
|
|
(product_id, combination_key, spec_values, price_adjustment, points_adjustment,
|
|
|
|
|
|
rongdou_adjustment, stock, sku_code, barcode, weight, volume)
|
|
|
|
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
|
|
|
|
[
|
|
|
|
|
|
product_id,
|
|
|
|
|
|
combinationKey,
|
|
|
|
|
|
JSON.stringify(sortedSpecValues),
|
|
|
|
|
|
price_adjustment,
|
|
|
|
|
|
points_adjustment,
|
|
|
|
|
|
rongdou_adjustment,
|
|
|
|
|
|
stock,
|
|
|
|
|
|
sku_code,
|
|
|
|
|
|
barcode,
|
|
|
|
|
|
weight,
|
|
|
|
|
|
volume
|
|
|
|
|
|
]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
res.status(201).json({
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
message: '规格组合创建成功',
|
|
|
|
|
|
data: {id: result.insertId, combination_key: combinationKey}
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
if (error.code === 'ER_DUP_ENTRY') {
|
|
|
|
|
|
return res.status(400).json({success: false, message: '该规格组合已存在'});
|
|
|
|
|
|
}
|
|
|
|
|
|
console.error('创建规格组合失败:', error);
|
|
|
|
|
|
res.status(500).json({success: false, message: '创建规格组合失败'});
|
2025-09-24 10:02:03 +08:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-09-26 14:38:44 +08:00
|
|
|
|
|
2025-09-24 10:02:03 +08:00
|
|
|
|
router.post('/generate-combinations', auth, adminAuth, async (req, res) => {
|
2025-09-26 14:38:44 +08:00
|
|
|
|
try {
|
|
|
|
|
|
const {product_id, spec_name_ids, default_stock = 0} = req.body;
|
|
|
|
|
|
|
|
|
|
|
|
if (!product_id || !spec_name_ids || !Array.isArray(spec_name_ids) || spec_name_ids.length === 0) {
|
|
|
|
|
|
return res.status(400).json({success: false, message: '商品ID和规格名称ID数组不能为空'});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取每个规格名称下的所有活跃规格值
|
|
|
|
|
|
const specValueGroups = [];
|
|
|
|
|
|
|
|
|
|
|
|
for (const specNameId of spec_name_ids) {
|
|
|
|
|
|
const [specValues] = await getDB().execute(
|
|
|
|
|
|
'SELECT id FROM spec_values WHERE spec_name_id = ? AND status = "active" ORDER BY sort_order, id',
|
|
|
|
|
|
[specNameId]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (specValues.length === 0) {
|
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
|
success: false,
|
|
|
|
|
|
message: `规格名称ID ${specNameId} 下没有活跃的规格值`
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
specValueGroups.push(specValues.map(sv => sv.id));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 生成笛卡尔积
|
|
|
|
|
|
function cartesianProduct(arrays) {
|
|
|
|
|
|
return arrays.reduce((acc, curr) => {
|
|
|
|
|
|
const result = [];
|
|
|
|
|
|
acc.forEach(a => {
|
|
|
|
|
|
curr.forEach(c => {
|
|
|
|
|
|
result.push([...a, c]);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}, [[]]);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const combinations = cartesianProduct(specValueGroups);
|
|
|
|
|
|
|
|
|
|
|
|
// 生成所有组合键
|
|
|
|
|
|
const combinationData = combinations.map(combination => {
|
|
|
|
|
|
const sortedCombination = [...combination].sort((a, b) => a - b);
|
|
|
|
|
|
const combinationKey = sortedCombination.join('-');
|
|
|
|
|
|
return {
|
|
|
|
|
|
combination: sortedCombination,
|
|
|
|
|
|
key: combinationKey
|
|
|
|
|
|
};
|
2025-09-24 10:02:03 +08:00
|
|
|
|
});
|
2025-09-26 14:38:44 +08:00
|
|
|
|
|
|
|
|
|
|
// 批量检查已存在的组合
|
|
|
|
|
|
const existingKeys = new Set();
|
|
|
|
|
|
if (combinationData.length > 0) {
|
|
|
|
|
|
const keys = combinationData.map(item => item.key);
|
|
|
|
|
|
const placeholders = keys.map(() => '?').join(',');
|
|
|
|
|
|
|
|
|
|
|
|
const [existingCombinations] = await getDB().execute(
|
|
|
|
|
|
`SELECT combination_key
|
|
|
|
|
|
FROM product_spec_combinations
|
|
|
|
|
|
WHERE product_id = ?
|
|
|
|
|
|
AND combination_key IN (${placeholders})`,
|
|
|
|
|
|
[product_id, ...keys]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
existingCombinations.forEach(row => {
|
|
|
|
|
|
existingKeys.add(row.combination_key);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 过滤出需要插入的新组合
|
|
|
|
|
|
const newCombinations = combinationData.filter(item => !existingKeys.has(item.key));
|
|
|
|
|
|
|
|
|
|
|
|
// 批量插入新的规格组合
|
|
|
|
|
|
let createdCount = 0;
|
|
|
|
|
|
const skippedCount = combinationData.length - newCombinations.length;
|
|
|
|
|
|
|
|
|
|
|
|
if (newCombinations.length > 0) {
|
|
|
|
|
|
// 使用批量插入提高性能
|
|
|
|
|
|
const values = [];
|
|
|
|
|
|
const placeholders = [];
|
|
|
|
|
|
|
|
|
|
|
|
newCombinations.forEach(item => {
|
|
|
|
|
|
values.push(
|
|
|
|
|
|
product_id,
|
|
|
|
|
|
item.key,
|
|
|
|
|
|
JSON.stringify(item.combination),
|
|
|
|
|
|
default_stock
|
|
|
|
|
|
);
|
|
|
|
|
|
placeholders.push('(?, ?, ?, ?)');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const sql = `INSERT INTO product_spec_combinations
|
|
|
|
|
|
(product_id, combination_key, spec_values, stock)
|
|
|
|
|
|
VALUES ${placeholders.join(', ')}`;
|
|
|
|
|
|
|
|
|
|
|
|
const [result] = await getDB().execute(sql, values);
|
|
|
|
|
|
createdCount = result.affectedRows;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
res.status(201).json({
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
message: '规格组合生成完成',
|
|
|
|
|
|
data: {
|
|
|
|
|
|
total_combinations: combinations.length,
|
|
|
|
|
|
created: createdCount,
|
|
|
|
|
|
skipped: skippedCount
|
|
|
|
|
|
}
|
2025-09-24 10:02:03 +08:00
|
|
|
|
});
|
2025-09-26 14:38:44 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('生成规格组合失败:', error);
|
|
|
|
|
|
res.status(500).json({success: false, message: '生成规格组合失败'});
|
2025-09-24 10:02:03 +08:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
module.exports = router;
|