Files

254 lines
13 KiB
JavaScript

const express = require('express');
const router = express.Router();
const multer = require('multer');
const db = require('../db');
router.get('/', async (req, res) => {
try {
const { category_id, status, keyword } = req.query;
let query = `
SELECT p.*, pc.name as category_name, pc2.name as category_level1_name
FROM products p
LEFT JOIN product_categories pc ON p.category_id = pc.id
LEFT JOIN product_categories pc2 ON pc.parent_id = pc2.id
WHERE 1=1
`;
const params = [];
let paramIndex = 1;
if (category_id) {
const catResult = await db.query('SELECT parent_id FROM product_categories WHERE id = $1', [category_id]);
if (catResult.rows.length > 0 && !catResult.rows[0].parent_id) {
const childCategories = await db.query('SELECT id FROM product_categories WHERE parent_id = $1', [category_id]);
if (childCategories.rows.length > 0) {
const childIds = childCategories.rows.map(row => row.id);
const placeholders = childIds.map(() => '$' + paramIndex++).join(',');
query += ` AND p.category_id IN (${placeholders})`;
params.push(...childIds);
} else {
query += ' AND 1=0';
}
} else {
query += ` AND p.category_id = $${paramIndex++}`;
params.push(category_id);
}
}
if (status) {
query += ` AND p.status = $${paramIndex++}`;
params.push(status);
}
if (keyword) {
const searchTerm = `%${keyword}%`;
query += ` AND (p.name LIKE $${paramIndex} OR p.model LIKE $${paramIndex + 1} OR p.brand LIKE $${paramIndex + 2})`;
params.push(searchTerm, searchTerm, searchTerm);
paramIndex += 3;
}
query += ' ORDER BY p.created_at DESC';
const result = await db.query(query, params);
res.json({ success: true, data: result.rows, count: result.rows.length });
} catch (error) {
console.error('获取商品失败:', error);
res.status(500).json({ success: false, message: '获取商品失败' });
}
});
router.get('/template', (req, res) => {
try {
const XLSX = require('xlsx');
const templateData = [
{ '商品名称': 'JKLYJ-35-22kV', '型号': 'Model-001', '一级分类': '电缆电线', '二级分类': '高压电缆', '单位': '米', '成本单价': 12.50, '销售单价': 15.50, '品牌': '云南线缆', '规格参数': '35mm², 22kV', '来源': '中国', '备注': '示例商品' },
{ '商品名称': 'XP-70', '型号': 'XP-70', '一级分类': '电杆横担', '二级分类': '横担', '单位': '个', '成本单价': 20.00, '销售单价': 25.00, '品牌': '江西电瓷', '规格参数': '70kN', '来源': '老挝', '备注': '' }
];
const worksheet = XLSX.utils.json_to_sheet(templateData);
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, '商品导入模板');
const buffer = XLSX.write(workbook, { type: 'buffer', bookType: 'xlsx' });
res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
res.setHeader('Content-Disposition', 'attachment; filename="product_template.xlsx"');
res.send(buffer);
} catch (error) {
console.error('生成模板失败:', error);
res.status(500).json({ success: false, message: '生成模板失败' });
}
});
router.post('/batch-import', multer().single('file'), async (req, res) => {
try {
if (!req.file) {
return res.status(400).json({ success: false, message: '请上传文件' });
}
const XLSX = require('xlsx');
const workbook = XLSX.read(req.file.buffer, { type: 'buffer' });
const worksheet = workbook.Sheets[workbook.SheetNames[0]];
const data = XLSX.utils.sheet_to_json(worksheet);
const imported = [];
const errors = [];
for (let i = 0; i < data.length; i++) {
const row = data[i];
try {
let level1Category = null;
if (row['一级分类']) {
const catResult = await db.query('SELECT id FROM product_categories WHERE name = $1 AND parent_id IS NULL', [row['一级分类']]);
if (catResult.rows.length > 0) {
level1Category = catResult.rows[0].id;
} else {
const newCatResult = await db.query('INSERT INTO product_categories (name) VALUES ($1) RETURNING id', [row['一级分类']]);
level1Category = newCatResult.rows[0].id;
}
}
let categoryId = null;
if (row['二级分类']) {
const catResult = await db.query('SELECT id FROM product_categories WHERE name = $1 AND parent_id = $2', [row['二级分类'], level1Category]);
if (catResult.rows.length > 0) {
categoryId = catResult.rows[0].id;
} else {
const newCatResult = await db.query('INSERT INTO product_categories (name, parent_id) VALUES ($1, $2) RETURNING id', [row['二级分类'], level1Category]);
categoryId = newCatResult.rows[0].id;
}
}
const result = await db.query(
`INSERT INTO products (name, model, category_id, unit, cost_price, price, brand, specification, source, remark, stock_quantity) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) RETURNING id`,
[row['商品名称'] || '', row['型号'] || '', categoryId, row['单位'] || '件', row['成本单价'] || 0, row['销售单价'] || 0, row['品牌'] || '', row['规格参数'] || '', row['来源'] || '老挝', row['备注'] || '', row['库存数量'] || 0]
);
imported.push({ row: i + 2, name: row['商品名称'], id: result.rows[0].id });
} catch (error) {
errors.push({ row: i + 2, name: row['商品名称'] });
}
}
res.json({ success: true, message: `导入完成,成功 ${imported.length} 条,失败 ${errors.length} 条`, imported, errors });
} catch (error) {
console.error('批量导入商品失败:', error);
res.status(500).json({ success: false, message: '批量导入商品失败' });
}
});
router.post('/import', multer().single('file'), async (req, res) => {
try {
if (!req.file) {
return res.status(400).json({ success: false, message: '请上传文件' });
}
const XLSX = require('xlsx');
const workbook = XLSX.read(req.file.buffer, { type: 'buffer' });
const worksheet = workbook.Sheets[workbook.SheetNames[0]];
const data = XLSX.utils.sheet_to_json(worksheet);
const imported = [];
const errors = [];
for (let i = 0; i < data.length; i++) {
const row = data[i];
try {
let level1Category = null;
if (row['一级分类']) {
const catResult = await db.query('SELECT id FROM product_categories WHERE name = $1 AND parent_id IS NULL', [row['一级分类']]);
if (catResult.rows.length > 0) { level1Category = catResult.rows[0].id; }
else { const r = await db.query('INSERT INTO product_categories (name) VALUES ($1) RETURNING id', [row['一级分类']]); level1Category = r.rows[0].id; }
}
let categoryId = null;
if (row['二级分类']) {
const catResult = await db.query('SELECT id FROM product_categories WHERE name = $1 AND parent_id = $2', [row['二级分类'], level1Category]);
if (catResult.rows.length > 0) { categoryId = catResult.rows[0].id; }
else { const r = await db.query('INSERT INTO product_categories (name, parent_id) VALUES ($1, $2) RETURNING id', [row['二级分类'], level1Category]); categoryId = r.rows[0].id; }
}
const result = await db.query(
`INSERT INTO products (name, model, category_id, unit, cost_price, price, brand, specification, source, remark, stock_quantity) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) RETURNING id`,
[row['商品名称'] || '', row['型号'] || '', categoryId, row['单位'] || '件', row['成本单价'] || 0, row['销售单价'] || 0, row['品牌'] || '', row['规格参数'] || '', row['来源'] || '老挝', row['备注'] || '', row['库存数量'] || 0]
);
imported.push({ row: i + 2, name: row['商品名称'], id: result.rows[0].id });
} catch (error) {
errors.push({ row: i + 2, name: row['商品名称'] });
}
}
res.json({ success: true, message: `导入完成,成功 ${imported.length} 条,失败 ${errors.length} 条`, imported, errors });
} catch (error) {
console.error('导入商品失败:', error);
res.status(500).json({ success: false, message: '导入商品失败' });
}
});
router.get('/:id', async (req, res) => {
try {
const { id } = req.params;
const result = await db.query(`
SELECT p.*, pc.name as category_name, pc2.name as category_level1_name
FROM products p
LEFT JOIN product_categories pc ON p.category_id = pc.id
LEFT JOIN product_categories pc2 ON pc.parent_id = pc2.id
WHERE p.id = $1
`, [id]);
if (result.rows.length === 0) { return res.status(404).json({ success: false, message: '商品不存在' }); }
res.json({ success: true, data: result.rows[0] });
} catch (error) {
console.error('获取商品失败:', error);
res.status(500).json({ success: false, message: '获取商品失败' });
}
});
router.post('/', async (req, res) => {
try {
const { name, model, category_id, unit, cost_price, price, brand, specification, source, remark, stock_quantity, status } = req.body;
if (!name) { return res.status(400).json({ success: false, message: '商品名称不能为空' }); }
let categoryName = null;
if (category_id) {
const catResult = await db.query('SELECT name FROM product_categories WHERE id = $1', [category_id]);
if (catResult.rows.length > 0) { categoryName = catResult.rows[0].name; }
}
const result = await db.query(
`INSERT INTO products (name, model, category_id, category_name, unit, cost_price, price, brand, specification, source, remark, stock_quantity, status) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) RETURNING *`,
[name, model || '', category_id || null, categoryName, unit || '件', cost_price || null, price || 0, brand || '', specification || '', source || '老挝', remark || '', stock_quantity || 0, status || 'active']
);
res.json({ success: true, data: result.rows[0], message: '创建成功' });
} catch (error) {
console.error('创建商品失败:', error);
res.status(500).json({ success: false, message: '创建商品失败' });
}
});
router.put('/:id', async (req, res) => {
try {
const { id } = req.params;
const { name, model, category_id, unit, cost_price, price, brand, specification, source, remark, stock_quantity, stock_warning, status } = req.body;
let categoryName = null;
if (category_id !== undefined && category_id) {
const catResult = await db.query('SELECT name FROM product_categories WHERE id = $1', [category_id]);
if (catResult.rows.length > 0) { categoryName = catResult.rows[0].name; }
}
const updates = [];
const params = [];
let i = 1;
if (name !== undefined) { updates.push(`name = $${i++}`); params.push(name); }
if (model !== undefined) { updates.push(`model = $${i++}`); params.push(model || ''); }
if (category_id !== undefined) { updates.push(`category_id = $${i++}`); params.push(category_id || null); updates.push(`category_name = $${i++}`); params.push(categoryName); }
if (unit !== undefined) { updates.push(`unit = $${i++}`); params.push(unit || '件'); }
if (cost_price !== undefined) { updates.push(`cost_price = $${i++}`); params.push(cost_price || null); }
if (price !== undefined) { updates.push(`price = $${i++}`); params.push(price || 0); }
if (brand !== undefined) { updates.push(`brand = $${i++}`); params.push(brand || ''); }
if (specification !== undefined) { updates.push(`specification = $${i++}`); params.push(specification || ''); }
if (source !== undefined) { updates.push(`source = $${i++}`); params.push(source || '老挝'); }
if (remark !== undefined) { updates.push(`remark = $${i++}`); params.push(remark || ''); }
if (stock_quantity !== undefined) { updates.push(`stock_quantity = $${i++}`); params.push(stock_quantity || 0); }
if (stock_warning !== undefined) { updates.push(`stock_warning = $${i++}`); params.push(stock_warning || 10); }
if (status !== undefined) { updates.push(`status = $${i++}`); params.push(status || 'active'); }
if (updates.length === 0) { return res.status(400).json({ success: false, message: '没有提供更新数据' }); }
updates.push(`updated_at = CURRENT_TIMESTAMP`);
params.push(id);
const result = await db.query(`UPDATE products SET ${updates.join(', ')} WHERE id = $${i} RETURNING *`, params);
if (result.rows.length === 0) { return res.status(404).json({ success: false, message: '商品不存在' }); }
res.json({ success: true, data: result.rows[0], message: '更新成功' });
} catch (error) {
console.error('更新商品失败:', error);
res.status(500).json({ success: false, message: '更新商品失败' });
}
});
router.delete('/:id', async (req, res) => {
try {
const { id } = req.params;
const result = await db.query('DELETE FROM products WHERE id = $1 RETURNING id', [id]);
if (result.rows.length === 0) { return res.status(404).json({ success: false, message: '商品不存在' }); }
res.json({ success: true, message: '删除成功' });
} catch (error) {
console.error('删除商品失败:', error);
res.status(500).json({ success: false, message: '删除商品失败' });
}
});
module.exports = router;