本文目录导读:

使用PHP实现采购计划(采购申请与审批)通常需要一个完整的Web应用,包含数据库、后端逻辑和前端界面。
下面是一个从零开始的标准化实现方案,涵盖了需求分析、数据库设计、核心代码示例和业务流程,你可以根据这个框架来构建自己的项目。
核心功能与业务流程
一个简单的采购计划系统通常包含以下流程:
- 需求发起:各部门提交采购申请单。
- 审批流转:部门主管 -> 财务 -> 总经理(或多级审批)。
- 计划汇总:审批通过的申请汇总成“采购计划”。
- 采购执行:计划单转为采购订单(可选,本方案聚焦于计划生成)。
数据库设计 (MySQL)
这是最关键的部分,至少需要三张核心表。
采购申请表 (purchase_requests)
CREATE TABLE `purchase_requests` ( `id` int(11) NOT NULL AUTO_INCREMENT, `pr_number` varchar(50) NOT NULL COMMENT '申请单号,如 PR-20231027-001', `applicant_id` int(11) NOT NULL COMMENT '申请人(关联用户表)', `department_id` int(11) DEFAULT NULL COMMENT '部门ID', varchar(200) NOT NULL COMMENT '采购事由/标题', `total_amount` decimal(12,2) DEFAULT '0.00' COMMENT '预估总金额', `status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '状态: 0-草稿, 1-待主管审批, 2-待财务审批, 3-已批准, 4-已拒绝', `created_at` datetime DEFAULT CURRENT_TIMESTAMP, `updated_at` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `pr_number` (`pr_number`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
申请明细表 (purchase_request_items)
CREATE TABLE `purchase_request_items` ( `id` int(11) NOT NULL AUTO_INCREMENT, `request_id` int(11) NOT NULL COMMENT '关联申请单ID', `material_name` varchar(100) NOT NULL COMMENT '物料/服务名称', `specification` varchar(255) DEFAULT NULL COMMENT '规格型号', `quantity` int(11) NOT NULL COMMENT '数量', `unit` varchar(20) DEFAULT NULL COMMENT '单位', `estimated_price` decimal(10,2) DEFAULT NULL COMMENT '预估单价', `remark` text COMMENT '备注/用途说明', PRIMARY KEY (`id`), KEY `request_id` (`request_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
审批记录表 (approval_logs)
CREATE TABLE `approval_logs` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`request_id` int(11) NOT NULL COMMENT '申请单ID',
`approver_id` int(11) NOT NULL COMMENT '审批人ID',
`action` enum('approve','reject','return') NOT NULL COMMENT '动作',
`comment` text COMMENT '审批意见',
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
提示:如果系统复杂,建议将“状态”独立成一张 workflow_steps(工作流步骤)表,方便配置多级审批。
PHP 后端核心代码示例 (面向过程 / Laravel风格思路)
假设你使用 原生PHP 或 ThinkPHP/Laravel 框架。
创建采购申请 (Controller - create 方法)
// 接收 POST 数据
$title = $_POST['title'];
$items = $_POST['items']; // 这是一个数组,包含多个物料的 json
// 1. 生成唯一的申请单号
$pr_number = 'PR-' . date('Ymd') . '-' . str_pad($this->getNextSequence(), 4, '0', STR_PAD_LEFT);
// 2. 开启事务
$this->db->beginTransaction();
try {
// 3. 插入主表
$sql = "INSERT INTO purchase_requests (pr_number, applicant_id, title, total_amount, status) VALUES (?, ?, ?, ?, 0)";
$this->db->execute($sql, [$pr_number, $_SESSION['user_id'], $title, 0]);
$request_id = $this->db->lastInsertId();
$total = 0;
// 4. 循环插入明细表
foreach ($items as $item) {
$sql = "INSERT INTO purchase_request_items (request_id, material_name, specification, quantity, unit, estimated_price) VALUES (?, ?, ?, ?, ?, ?)";
$this->db->execute($sql, [$request_id, $item['name'], $item['spec'], $item['qty'], $item['unit'], $item['price']]);
$total += $item['qty'] * $item['price'];
}
// 5. 更新主表的预估总金额
$sql = "UPDATE purchase_requests SET total_amount = ? WHERE id = ?";
$this->db->execute($sql, [$total, $request_id]);
$this->db->commit();
echo json_encode(['success' => true, 'message' => '采购申请已提交']);
} catch (Exception $e) {
$this->db->rollback();
echo json_encode(['success' => false, 'message' => '提交失败: ' . $e->getMessage()]);
}
审核操作 (Controller - approve 方法)
public function approve($request_id) {
$action = $_POST['action']; // 'approve' 或 'reject'
$comment = $_POST['comment'];
$current_user_id = $_SESSION['user_id'];
$current_role = $this->getUserRole($current_user_id);
// 1. 获取当前申请单状态
$request = $this->db->fetch("SELECT * FROM purchase_requests WHERE id = ?", [$request_id]);
// 2. 权限校验(只有主管才能批准待主管审批的状态)
if ($current_role == 'manager' && $request['status'] == 1) {
$new_status = ($action == 'approve') ? 2 : 4; // 转到财务审批 或 拒绝
} elseif ($current_role == 'finance' && $request['status'] == 2) {
$new_status = ($action == 'approve') ? 3 : 4; // 最终批准 或 拒绝
} else {
die('权限不足或状态异常');
}
// 3. 更新申请单状态
$sql = "UPDATE purchase_requests SET status = ? WHERE id = ?";
$this->db->execute($sql, [$new_status, $request_id]);
// 4. 记录审批日志
$sql = "INSERT INTO approval_logs (request_id, approver_id, action, comment) VALUES (?, ?, ?, ?)";
$this->db->execute($sql, [$request_id, $current_user_id, $action, $comment]);
// 5. 如果最终批准,可以自动生成“采购计划单” (可选)
if ($new_status == 3) {
$this->generatePurchasePlan($request_id);
}
echo json_encode(['success' => true]);
}
前端界面 (HTML + JavaScript/jQuery)
采购申请表单
<form id="purchaseForm">
<div>
<label>采购事由:</label>
<input type="text" name="title" required>
</div>
<div id="itemsContainer">
<div class="item-row">
<input type="text" name="items[0][name]" placeholder="物料名称" required>
<input type="text" name="items[0][spec]" placeholder="规格">
<input type="number" name="items[0][qty]" placeholder="数量" required>
<input type="text" name="items[0][unit]" placeholder="单位" value="个">
<input type="number" step="0.01" name="items[0][price]" placeholder="预估单价" required>
</div>
</div>
<button type="button" onclick="addItem()">添加物料</button>
<button type="submit">提交申请</button>
</form>
审批列表界面
<table>
<thead>
<tr>
<th>单号</th>
<th>申请人</th>
<th>总金额</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<?php foreach ($requests as $req): ?>
<tr>
<td><?= $req['pr_number'] ?></td>
<td><?= $req['applicant_name'] ?></td>
<td><?= $req['total_amount'] ?></td>
<td>
<?php
$status_map = [0 => '草稿', 1 => '待主管审批', 2 => '待财务审批', 3 => '已批准', 4 => '已拒绝'];
echo $status_map[$req['status']];
?>
</td>
<td>
<!-- 审批按钮,通常通过 session 判断当前用户角色来控制显示 -->
<button onclick="approve(<?= $req['id'] ?>, 'approve')">通过</button>
<button onclick="approve(<?= $req['id'] ?>, 'reject')">拒绝</button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
注意事项与进阶优化
-
安全性:
- 所有SQL操作必须使用预处理语句(PDO或MySQLi prepared statements)防止SQL注入。
- 权限管理:在每一个controller方法开头检查用户角色(如
if($_SESSION['role'] != 'manager') die('无权限');)。
-
工作流复杂性:
- 上述代码是硬编码的审批流程,如果公司需要灵活的审批流(如超过3000元需总经理审批),你需要设计一个工作流引擎表:
workflow_rules表:存储条件(金额范围)、当前角色、下一步角色。- 审批时通过动态查询该表来决定
$new_status。
- 上述代码是硬编码的审批流程,如果公司需要灵活的审批流(如超过3000元需总经理审批),你需要设计一个工作流引擎表:
-
状态机模式:
- 强烈建议使用状态机来管理
status字段的变化,而不是用if...else,可以编写一个StateMachine类,定义每个状态允许的转换,避免逻辑混乱。
- 强烈建议使用状态机来管理
-
框架选择:
- Laravel:内置了
Gate(权限)、Event(记录日志)、Model(ORM事务),非常适合此类业务系统。 - ThinkPHP 6+:国内常用,快速开发。
- Laravel:内置了
-
效率:
- 当数据量较大时(如10万条申请),需要对
purchase_requests表根据status和created_at建立复合索引。
- 当数据量较大时(如10万条申请),需要对
一个可以直接运行的采购计划系统,核心思路是:
- 数据流:表单提交 -> 数据库事务写入(主表+明细表) -> 审批后更新状态 -> 生成计划。
- 代码基础:安全(防SQL注入) + 事务 + 状态控制。
- 关键文件:
控制器(CRUD逻辑)+模型(数据库操作)+视图(表单与列表)。
你可以先按照这个结构搭建一个最小可行产品(MVP),然后逐步增加:邮件通知、导出Excel、与仓库系统对接等功能。