本文目录导读:

实现药品库存管理是药品管理系统的核心功能,在PHP项目中,通常需要结合数据库设计、业务逻辑、安全控制以及合规性(如GSP认证)来设计。
以下是一个完整的实现方案,分为数据库设计、核心业务逻辑、代码实现示例和高级注意事项四个部分。
数据库设计(核心)
药品管理比普通商品更严格,需要记录批次、效期、批准文号等。
-- 1. 药品信息表 (products) CREATE TABLE `products` ( `id` int(11) NOT NULL AUTO_INCREMENT, `drug_code` varchar(50) NOT NULL COMMENT '药品编码', `drug_name` varchar(100) NOT NULL COMMENT '通用名', `specification` varchar(100) DEFAULT NULL COMMENT '规格 (如: 0.25g*12片)', `manufacturer` varchar(200) DEFAULT NULL COMMENT '生产厂家', `approval_number` varchar(50) DEFAULT NULL COMMENT '批准文号', `unit` varchar(20) DEFAULT '盒' COMMENT '单位', `category_id` int(11) DEFAULT NULL COMMENT '分类', `min_stock` int(11) DEFAULT 0 COMMENT '最低库存预警', `max_stock` int(11) DEFAULT 0 COMMENT '最高库存限制', `status` tinyint(1) DEFAULT 1 COMMENT '0下架 1上架', `created_at` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `drug_code` (`drug_code`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 2. 库存批次表 (最核心,记录每个批次) CREATE TABLE `inventory_batches` ( `id` int(11) NOT NULL AUTO_INCREMENT, `product_id` int(11) NOT NULL, `batch_no` varchar(50) NOT NULL COMMENT '批号', `expiry_date` date NOT NULL COMMENT '有效期至', `quantity` int(11) NOT NULL DEFAULT 0 COMMENT '当前库存数量', `purchase_price` decimal(10,2) DEFAULT NULL COMMENT '采购单价', `sale_price` decimal(10,2) DEFAULT NULL COMMENT '零售单价', `warehouse_location` varchar(50) DEFAULT NULL COMMENT '库位 (如A-01-03)', `supplier_id` int(11) DEFAULT NULL, `created_at` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), INDEX `idx_expiry` (`expiry_date`), INDEX `idx_product_batch` (`product_id`, `batch_no`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 3. 库存变动流水表 (审计和追溯) CREATE TABLE `stock_movements` ( `id` int(11) NOT NULL AUTO_INCREMENT, `product_id` int(11) NOT NULL, `batch_id` int(11) NOT NULL, `type` varchar(20) NOT NULL COMMENT '入库/出库/报损/退货', `quantity` int(11) NOT NULL COMMENT '变动数量 (正数入库,负数出库)', `before_quantity` int(11) DEFAULT NULL, `after_quantity` int(11) DEFAULT NULL, `ref_order_no` varchar(100) DEFAULT NULL COMMENT '关联单号 (采购单/销售单)', `operator_id` int(11) DEFAULT NULL COMMENT '操作人', `remark` text, `created_at` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), INDEX `idx_product` (`product_id`), INDEX `idx_batch` (`batch_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
核心业务逻辑(PHP实现)
入库流程(采购入库)
需要处理的逻辑:增加批次库存、记录流水、校验效期。
// WarehouseService.php (仓库服务类)
public function stockIn(int $productId, string $batchNo, string $expiryDate, int $quantity, float $price, int $operatorId): bool
{
$this->db->beginTransaction();
try {
// 1. 检查是否有相同批号 (有的系统允许合并,有的要求一盒一批)
$batch = $this->db->query(
"SELECT id, quantity FROM inventory_batches
WHERE product_id = ? AND batch_no = ?",
[$productId, $batchNo]
)->fetch();
if ($batch) {
// 同批号追加数量
$this->db->update('inventory_batches', [
'quantity' => $batch['quantity'] + $quantity
], ['id' => $batch['id']]);
$batchId = $batch['id'];
} else {
// 新增批次记录
$batchId = $this->db->insert('inventory_batches', [
'product_id' => $productId,
'batch_no' => $batchNo,
'expiry_date' => $expiryDate,
'quantity' => $quantity,
'purchase_price' => $price,
'sale_price' => 0 // 后续定价
]);
}
// 2. 记录流水
$this->db->insert('stock_movements', [
'product_id' => $productId,
'batch_id' => $batchId,
'type' => 'in',
'quantity' => $quantity,
'before_quantity' => $batch['quantity'] ?? 0,
'after_quantity' => ($batch['quantity'] ?? 0) + $quantity,
'ref_order_no' => 'PO-20241001-001',
'operator_id' => $operatorId
]);
$this->db->commit();
return true;
} catch (\Exception $e) {
$this->db->rollback();
// 记录错误日志
throw $e;
}
}
出库流程(销售出库)
关键点:先进先出(FIFO),必须优先消耗近效期批次。
// 获取出库批次列表 (FIFO)
public function getOutboundBatches(int $productId, int $neededQuantity): array
{
// SQL: 先按有效期排序,再按入库时间排序
$batches = $this->db->query(
"SELECT id, batch_no, expiry_date, quantity
FROM inventory_batches
WHERE product_id = ? AND quantity > 0 AND expiry_date >= CURDATE()
ORDER BY expiry_date ASC, created_at ASC",
[$productId]
)->fetchAll();
$allocations = [];
$remaining = $neededQuantity;
foreach ($batches as $batch) {
if ($remaining <= 0) break;
$take = min($batch['quantity'], $remaining);
$allocations[] = [
'batch_id' => $batch['id'],
'batch_no' => $batch['batch_no'],
'quantity' => $take,
'expiry_date' => $batch['expiry_date']
];
$remaining -= $take;
}
if ($remaining > 0) {
throw new \Exception("库存不足,缺少 {$remaining} 件");
}
return $allocations;
}
// 执行出库
public function stockOut(int $productId, int $quantity, int $operatorId): void
{
$allocations = $this->getOutboundBatches($productId, $quantity);
$this->db->beginTransaction();
try {
foreach ($allocations as $alloc) {
// 扣减批次库存
$this->db->execute(
"UPDATE inventory_batches
SET quantity = quantity - ?
WHERE id = ? AND quantity >= ?",
[$alloc['quantity'], $alloc['batch_id'], $alloc['quantity']]
);
// 记录流水(注意出库数量为负数)
$this->db->insert('stock_movements', [
'product_id' => $productId,
'batch_id' => $alloc['batch_id'],
'type' => 'out',
'quantity' => -$alloc['quantity'],
'ref_order_no' => 'SO-20241001-001',
'operator_id'=> $operatorId
]);
}
$this->db->commit();
} catch (\Exception $e) {
$this->db->rollback();
throw $e;
}
}
效期预警与近效期锁定
// 每日定时任务(CronJob)
public function checkExpiryWarnings(): void
{
// 近效期提醒(比如90天内到期)
$warningDate = date('Y-m-d', strtotime('+90 days'));
$expiredDate = date('Y-m-d');
// 1. 锁定过期药品(自动设置库存为0或下架)
$this->db->execute(
"UPDATE inventory_batches
SET quantity = 0, status = 'expired'
WHERE expiry_date < ? AND quantity > 0",
[$expiredDate]
);
// 2. 发出近效期告警(插入告警表)
$warnings = $this->db->query(
"SELECT p.drug_name, b.batch_no, b.quantity, b.expiry_date
FROM inventory_batches b
JOIN products p ON p.id = b.product_id
WHERE b.expiry_date BETWEEN CURDATE() AND ?
AND b.quantity > 0",
[$warningDate]
)->fetchAll();
// 发送通知(邮件/系统消息)
foreach ($warnings as $w) {
// 记录到 warning_logs 表
}
}
前端页面功能建议
用PHP + Bootstrap 构建管理界面,至少包含:
| 功能模块 | 说明 |
|---|---|
| 药品档案 | CRUD,支持上传说明书、图片 |
| 入库单 | 选择药品、填写批号/效期/数量,支持Excel导入 |
| 出库单 | 选择药品,自动分配批次(FIFO),打印单据 |
| 库存查询 | 可查:按药品/库位/批次/效期,显示当前库存 |
| 预警看板 | 近效期Top10、库存不足、过期药品 |
| 盘点模块 | 生成盘点差异表,自动调账 |
| 流水追溯 | 查询某个药品所有历史操作记录 |
高可用与安全建议
-
并发安全
- 使用
SELECT ... FOR UPDATE锁定批次行,避免超卖。 - PHP代码层面建议用事务+行级锁。
- 使用
-
合规性(GSP)
- 采购记录、销售记录不可删除(软删除或归档)。
- 批号、效期必须追踪到每一盒(如果是拆零销售,需要额外设计拆零表)。
- 记录操作日志:谁、什么时候、做了什么。
-
性能优化
- 库存查询可用 Redis 缓存总库存数量,但批次明细必须查库。
- 定时清理过期批次数据(转移到归档表)。
stock_movements表要按月份或年份分区。
-
权限控制
入库/出库/报损/盘点/调价,应在PHP后端进行角色权限验证(如RBAC)。
技术选型建议
- 框架:Laravel(推荐,Eloquent ORM + 队列 + 任务调度)或 ThinkPHP。
- 数据库:MySQL/PostgreSQL(支持事务和行锁)。
- 队列:处理大批量入库/出库请求(防止超时)。
- 报表:PHPExcel/PhpSpreadsheet 导出出入库报表。
一个完整的简化示例(用 Laravel)
// InventoryController.php
public function storeIn(Request $request)
{
$validated = $request->validate([
'product_id' => 'required|exists:products,id',
'batch_no' => 'required|string',
'expiry_date' => 'required|date|after:today',
'quantity' => 'required|integer|min:1',
'purchase_price' => 'required|numeric|min:0',
]);
app(InventoryService::class)->stockIn(
$validated['product_id'],
$validated['batch_no'],
$validated['expiry_date'],
$validated['quantity'],
$validated['purchase_price'],
auth()->id()
);
return redirect()->back()->with('success', '入库成功');
}
药品库存管理的核心难点在于:
- 批次与效期的管理(FIFO算法)。
- 数据一致性(事务保护,防止并发冲突)。
- 合规追溯(流水表设计)。
建议先搭建好数据库结构和核心的服务层(Service),再开发具体的界面,如果你的项目规模较小,可以用类似上面的简化方案;如果业务量大,建议考虑微服务架构拆分库存服务。