PHP项目怎么实现药品库存管理?

wen java案例 2

本文目录导读:

PHP项目怎么实现药品库存管理?

  1. 数据库设计(核心)
  2. 核心业务逻辑(PHP实现)
  3. 前端页面功能建议
  4. 高可用与安全建议
  5. 技术选型建议
  6. 一个完整的简化示例(用 Laravel)

实现药品库存管理是药品管理系统的核心功能,在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、库存不足、过期药品
盘点模块 生成盘点差异表,自动调账
流水追溯 查询某个药品所有历史操作记录

高可用与安全建议

  1. 并发安全

    • 使用 SELECT ... FOR UPDATE 锁定批次行,避免超卖。
    • PHP代码层面建议用事务+行级锁
  2. 合规性(GSP)

    • 采购记录、销售记录不可删除(软删除或归档)。
    • 批号、效期必须追踪到每一盒(如果是拆零销售,需要额外设计拆零表)。
    • 记录操作日志:谁、什么时候、做了什么。
  3. 性能优化

    • 库存查询可用 Redis 缓存总库存数量,但批次明细必须查库。
    • 定时清理过期批次数据(转移到归档表)。
    • stock_movements 表要按月份或年份分区。
  4. 权限控制

    入库/出库/报损/盘点/调价,应在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', '入库成功');
}

药品库存管理的核心难点在于:

  1. 批次与效期的管理(FIFO算法)。
  2. 数据一致性(事务保护,防止并发冲突)。
  3. 合规追溯(流水表设计)。

建议先搭建好数据库结构和核心的服务层(Service),再开发具体的界面,如果你的项目规模较小,可以用类似上面的简化方案;如果业务量大,建议考虑微服务架构拆分库存服务。

抱歉,评论功能暂时关闭!