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.

463 lines
11 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,
item_name,
search,
low_stock_only
} = req.query;
const result = await Inventory.findAll({
page: parseInt(page),
pageSize: parseInt(pageSize),
search: item_name || search,
low_stock_only: low_stock_only === 'true' || low_stock_only === '1'
});
// Map database fields to frontend fields
const mappedList = result.list.map(item => ({
...item,
item_name: item.name
}));
res.json(success({
list: mappedList,
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);
}
// Map database fields to frontend fields
const mappedItem = {
...item,
item_name: item.name
};
res.json(success(mappedItem, '获取库存详情成功'));
} catch (err) {
next(err);
}
};
/**
* 创建库存项
* POST /api/inventory
* 需要管理员权限
*/
const createInventory = async (req, res, next) => {
try {
const {
item_name,
name,
quantity,
unit,
unit_price,
expiry_date,
min_quantity
} = req.body;
// Use item_name if provided, otherwise use name
const itemName = item_name || name;
// 数据验证
const validation = validateBatch([
validateRequired(itemName, '商品名称'),
validateRequired(quantity, '数量'),
validateRequired(unit, '单位'),
validateRequired(unit_price, '单价'),
validateRequired(min_quantity, '最低库存')
]);
if (!validation.valid) {
throw new ValidationError('数据验证失败', validation.errors);
}
// 验证数量和价格
if (parseFloat(quantity) < 0) {
throw new ValidationError('数量不能为负数');
}
if (parseFloat(unit_price) <= 0) {
throw new ValidationError('单价必须大于0');
}
if (parseFloat(min_quantity) < 0) {
throw new ValidationError('最低库存不能为负数');
}
// 检查名称是否已存在
const exists = await Inventory.isNameExists(itemName);
if (exists) {
throw new ValidationError('该商品名称已存在');
}
const item = await Inventory.create({
name: itemName,
quantity: parseFloat(quantity),
unit,
unit_price: parseFloat(unit_price),
expiry_date: expiry_date || null,
min_quantity: parseFloat(min_quantity),
operator_id: req.user.id
});
// Map response fields
const mappedItem = {
...item,
item_name: item.name
};
res.status(201).json(success(mappedItem, '创建库存项成功'));
} catch (err) {
next(err);
}
};
/**
* 更新库存项
* PUT /api/inventory/:id
* 需要管理员权限
*/
const updateInventory = async (req, res, next) => {
try {
const { id } = req.params;
const {
item_name,
name,
unit,
unit_price,
expiry_date,
min_quantity,
quantity
} = req.body;
// Use item_name if provided, otherwise use name
const itemName = item_name || name;
// 检查库存项是否存在
const item = await Inventory.findById(id);
if (!item) {
throw new BusinessError('库存项不存在', 404);
}
// 验证数据
if (unit_price !== undefined && parseFloat(unit_price) <= 0) {
throw new ValidationError('单价必须大于0');
}
if (min_quantity !== undefined && parseFloat(min_quantity) < 0) {
throw new ValidationError('最低库存不能为负数');
}
if (quantity !== undefined && parseFloat(quantity) < 0) {
throw new ValidationError('库存数量不能为负数');
}
// 检查名称是否已存在
if (itemName && itemName !== item.name) {
const exists = await Inventory.isNameExists(itemName, id);
if (exists) {
throw new ValidationError('该商品名称已存在');
}
}
const updatedItem = await Inventory.update(id, {
name: itemName,
unit,
unit_price: unit_price !== undefined ? parseFloat(unit_price) : undefined,
expiry_date: expiry_date !== undefined ? expiry_date : undefined,
min_quantity: min_quantity !== undefined ? parseFloat(min_quantity) : undefined,
quantity: quantity !== undefined ? parseFloat(quantity) : undefined
});
// Map response fields
const mappedItem = {
...updatedItem,
item_name: updatedItem.name
};
res.json(success(mappedItem, '更新库存项成功'));
} 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);
}
};
/**
* 库存补货
* POST /api/inventory/:id/restock
* 需要管理员或服务员权限
*/
const restockInventory = async (req, res, next) => {
try {
const { id } = req.params;
const { quantity } = req.body;
// 数据验证
const validation = validateBatch([
validateRequired(quantity, '补货数量')
]);
if (!validation.valid) {
throw new ValidationError('数据验证失败', validation.errors);
}
// 验证数量
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,
'in',
parseFloat(quantity),
req.user.id,
'库存补货'
);
// Map response fields
const mappedItem = {
...updatedItem,
item_name: updatedItem.name
};
res.json(success(mappedItem, '补货成功'));
} 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/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,
restockInventory,
getLowStockAlerts,
getExpiringAlerts,
getExpiredAlerts,
getInventoryLogs,
getStatistics
};