forked from p4b8lshcr/ChefTronic
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
400 lines
9.1 KiB
400 lines
9.1 KiB
/**
|
|
* 库存控制器
|
|
*
|
|
* 功能:处理库存管理的业务逻辑
|
|
*/
|
|
|
|
const Inventory = require('../models/Inventory');
|
|
const { validateRequired, validateBatch } = require('../utils/validator');
|
|
const { success } = require('../utils/response');
|
|
const { BusinessError, ValidationError } = require('../middleware/errorHandler');
|
|
|
|
/**
|
|
* 获取库存列表(分页、筛选)
|
|
* GET /api/inventory
|
|
*/
|
|
const getInventory = async (req, res, next) => {
|
|
try {
|
|
const {
|
|
page = 1,
|
|
pageSize = 10,
|
|
category,
|
|
search,
|
|
low_stock_only
|
|
} = req.query;
|
|
|
|
const result = await Inventory.findAll({
|
|
page: parseInt(page),
|
|
pageSize: parseInt(pageSize),
|
|
category,
|
|
search,
|
|
low_stock_only: low_stock_only === 'true' || low_stock_only === '1'
|
|
});
|
|
|
|
res.json(success({
|
|
list: result.list,
|
|
pagination: {
|
|
total: result.total,
|
|
page: result.page,
|
|
pageSize: result.pageSize,
|
|
totalPages: Math.ceil(result.total / result.pageSize)
|
|
}
|
|
}, '获取库存列表成功'));
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 获取单个库存项详情
|
|
* GET /api/inventory/:id
|
|
*/
|
|
const getInventoryById = async (req, res, next) => {
|
|
try {
|
|
const { id } = req.params;
|
|
|
|
const item = await Inventory.findById(id);
|
|
if (!item) {
|
|
throw new BusinessError('库存项不存在', 404);
|
|
}
|
|
|
|
res.json(success(item, '获取库存详情成功'));
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 创建库存项
|
|
* POST /api/inventory
|
|
* 需要管理员权限
|
|
*/
|
|
const createInventory = async (req, res, next) => {
|
|
try {
|
|
const {
|
|
name,
|
|
category,
|
|
quantity,
|
|
unit,
|
|
purchase_price,
|
|
supplier,
|
|
expiry_date,
|
|
alert_quantity
|
|
} = req.body;
|
|
|
|
// 数据验证
|
|
const validation = validateBatch([
|
|
validateRequired(name, '原材料名称'),
|
|
validateRequired(category, '分类'),
|
|
validateRequired(quantity, '数量'),
|
|
validateRequired(unit, '单位'),
|
|
validateRequired(purchase_price, '进货价格'),
|
|
validateRequired(alert_quantity, '预警数量')
|
|
]);
|
|
|
|
if (!validation.valid) {
|
|
throw new ValidationError('数据验证失败', validation.errors);
|
|
}
|
|
|
|
// 验证数量和价格
|
|
if (parseFloat(quantity) < 0) {
|
|
throw new ValidationError('数量不能为负数');
|
|
}
|
|
if (parseFloat(purchase_price) <= 0) {
|
|
throw new ValidationError('进货价格必须大于0');
|
|
}
|
|
if (parseFloat(alert_quantity) < 0) {
|
|
throw new ValidationError('预警数量不能为负数');
|
|
}
|
|
|
|
// 检查名称是否已存在
|
|
const exists = await Inventory.isNameExists(name);
|
|
if (exists) {
|
|
throw new ValidationError('该原材料名称已存在');
|
|
}
|
|
|
|
const item = await Inventory.create({
|
|
name,
|
|
category,
|
|
quantity: parseFloat(quantity),
|
|
unit,
|
|
purchase_price: parseFloat(purchase_price),
|
|
supplier: supplier || null,
|
|
expiry_date: expiry_date || null,
|
|
alert_quantity: parseFloat(alert_quantity),
|
|
operator_id: req.user.id
|
|
});
|
|
|
|
res.status(201).json(success(item, '创建库存项成功'));
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 更新库存项
|
|
* PUT /api/inventory/:id
|
|
* 需要管理员权限
|
|
*/
|
|
const updateInventory = async (req, res, next) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const {
|
|
name,
|
|
category,
|
|
unit,
|
|
purchase_price,
|
|
supplier,
|
|
expiry_date,
|
|
alert_quantity
|
|
} = req.body;
|
|
|
|
// 检查库存项是否存在
|
|
const item = await Inventory.findById(id);
|
|
if (!item) {
|
|
throw new BusinessError('库存项不存在', 404);
|
|
}
|
|
|
|
// 验证数据
|
|
if (purchase_price !== undefined && parseFloat(purchase_price) <= 0) {
|
|
throw new ValidationError('进货价格必须大于0');
|
|
}
|
|
if (alert_quantity !== undefined && parseFloat(alert_quantity) < 0) {
|
|
throw new ValidationError('预警数量不能为负数');
|
|
}
|
|
|
|
// 检查名称是否已存在
|
|
if (name && name !== item.name) {
|
|
const exists = await Inventory.isNameExists(name, id);
|
|
if (exists) {
|
|
throw new ValidationError('该原材料名称已存在');
|
|
}
|
|
}
|
|
|
|
const updatedItem = await Inventory.update(id, {
|
|
name,
|
|
category,
|
|
unit,
|
|
purchase_price: purchase_price !== undefined ? parseFloat(purchase_price) : undefined,
|
|
supplier,
|
|
expiry_date: expiry_date !== undefined ? expiry_date : undefined,
|
|
alert_quantity: alert_quantity !== undefined ? parseFloat(alert_quantity) : undefined
|
|
});
|
|
|
|
res.json(success(updatedItem, '更新库存项成功'));
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 删除库存项
|
|
* DELETE /api/inventory/:id
|
|
* 需要管理员权限
|
|
*/
|
|
const deleteInventory = async (req, res, next) => {
|
|
try {
|
|
const { id } = req.params;
|
|
|
|
// 检查库存项是否存在
|
|
const item = await Inventory.findById(id);
|
|
if (!item) {
|
|
throw new BusinessError('库存项不存在', 404);
|
|
}
|
|
|
|
await Inventory.delete(id);
|
|
|
|
res.json(success(null, '删除库存项成功'));
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 调整库存数量(入库/出库/调整)
|
|
* POST /api/inventory/:id/adjust
|
|
* 需要管理员或服务员权限
|
|
*/
|
|
const adjustInventory = async (req, res, next) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const { change_type, quantity, remark } = req.body;
|
|
|
|
// 数据验证
|
|
const validation = validateBatch([
|
|
validateRequired(change_type, '变更类型'),
|
|
validateRequired(quantity, '数量')
|
|
]);
|
|
|
|
if (!validation.valid) {
|
|
throw new ValidationError('数据验证失败', validation.errors);
|
|
}
|
|
|
|
// 验证变更类型
|
|
const validTypes = ['in', 'out', 'adjust'];
|
|
if (!validTypes.includes(change_type)) {
|
|
throw new ValidationError('变更类型必须是 in、out 或 adjust');
|
|
}
|
|
|
|
// 验证数量
|
|
if (parseFloat(quantity) <= 0) {
|
|
throw new ValidationError('数量必须大于0');
|
|
}
|
|
|
|
// 检查库存项是否存在
|
|
const item = await Inventory.findById(id);
|
|
if (!item) {
|
|
throw new BusinessError('库存项不存在', 404);
|
|
}
|
|
|
|
const updatedItem = await Inventory.adjustQuantity(
|
|
id,
|
|
change_type,
|
|
parseFloat(quantity),
|
|
req.user.id,
|
|
remark || null
|
|
);
|
|
|
|
const messages = {
|
|
in: '入库成功',
|
|
out: '出库成功',
|
|
adjust: '库存调整成功'
|
|
};
|
|
|
|
res.json(success(updatedItem, messages[change_type]));
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 获取低库存预警列表
|
|
* GET /api/inventory/alerts/low-stock
|
|
*/
|
|
const getLowStockAlerts = async (req, res, next) => {
|
|
try {
|
|
const items = await Inventory.getLowStockItems();
|
|
|
|
res.json(success({
|
|
count: items.length,
|
|
items
|
|
}, '获取低库存预警成功'));
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 获取即将过期的库存列表
|
|
* GET /api/inventory/alerts/expiring
|
|
*/
|
|
const getExpiringAlerts = async (req, res, next) => {
|
|
try {
|
|
const { days = 7 } = req.query;
|
|
|
|
const items = await Inventory.getExpiringItems(parseInt(days));
|
|
|
|
res.json(success({
|
|
count: items.length,
|
|
days: parseInt(days),
|
|
items
|
|
}, '获取即将过期库存成功'));
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 获取已过期的库存列表
|
|
* GET /api/inventory/alerts/expired
|
|
*/
|
|
const getExpiredAlerts = async (req, res, next) => {
|
|
try {
|
|
const items = await Inventory.getExpiredItems();
|
|
|
|
res.json(success({
|
|
count: items.length,
|
|
items
|
|
}, '获取已过期库存成功'));
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 获取库存日志
|
|
* GET /api/inventory/logs 或 GET /api/inventory/:id/logs
|
|
*/
|
|
const getInventoryLogs = async (req, res, next) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const {
|
|
page = 1,
|
|
pageSize = 20,
|
|
change_type
|
|
} = req.query;
|
|
|
|
const result = await Inventory.getLogs(id || null, {
|
|
page: parseInt(page),
|
|
pageSize: parseInt(pageSize),
|
|
change_type
|
|
});
|
|
|
|
res.json(success({
|
|
list: result.list,
|
|
pagination: {
|
|
total: result.total,
|
|
page: result.page,
|
|
pageSize: result.pageSize,
|
|
totalPages: Math.ceil(result.total / result.pageSize)
|
|
}
|
|
}, '获取库存日志成功'));
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 获取库存分类列表
|
|
* GET /api/inventory/categories
|
|
*/
|
|
const getCategories = async (req, res, next) => {
|
|
try {
|
|
const categories = await Inventory.getCategories();
|
|
|
|
res.json(success(categories, '获取分类列表成功'));
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 获取库存统计信息
|
|
* GET /api/inventory/statistics
|
|
*/
|
|
const getStatistics = async (req, res, next) => {
|
|
try {
|
|
const stats = await Inventory.getStatistics();
|
|
|
|
res.json(success(stats, '获取库存统计成功'));
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
};
|
|
|
|
// 导出所有控制器函数
|
|
module.exports = {
|
|
getInventory,
|
|
getInventoryById,
|
|
createInventory,
|
|
updateInventory,
|
|
deleteInventory,
|
|
adjustInventory,
|
|
getLowStockAlerts,
|
|
getExpiringAlerts,
|
|
getExpiredAlerts,
|
|
getInventoryLogs,
|
|
getCategories,
|
|
getStatistics
|
|
};
|